diff --git a/.cspell.yaml b/.cspell.yaml index aacf710..4542447 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -9,14 +9,14 @@ # - NEVER add a misspelled word to the 'words' list # - PROPOSE only genuine technical terms/names as needed -version: "0.2" +version: '0.2' + language: en # Project-specific technical terms and tool names words: - - behaviour - - behaviours - buildmark + - CIFS - Dema - fileassert - fileshare @@ -25,38 +25,36 @@ words: - nendobj - nstartxref - pandoc - - parallelisation + - pdfsharp - Pdfs - planfile + - postconditions - Qube - - recognised - reportfile - reqstream + - rethrowing - reviewmark - Sarif - sarifmark - selftest - sonarmark - - summarising - - unrecognised - unreviewed - versionmark - weasy - weasyprint - xunit - yamlfix + - yamldotnet -# Exclude common build artifacts, dependencies, and vendored third-party code ignorePaths: - - "**/.git/**" - - "**/node_modules/**" - - "**/.venv/**" - - "**/thirdparty/**" - - "**/third-party/**" - - "**/3rd-party/**" - - "**/generated/**" - - "**/AGENT_REPORT_*.md" - - "**/.agent-logs/**" - - "**/bin/**" - - "**/obj/**" + - '**/.git/**' + - '**/node_modules/**' + - '**/.venv/**' + - '**/thirdparty/**' + - '**/third-party/**' + - '**/3rd-party/**' + - '**/.agent-logs/**' + - '**/bin/**' + - '**/obj/**' + - '**/generated/**' - package-lock.json diff --git a/.fileassert.yaml b/.fileassert.yaml index 6076d51..75872b2 100644 --- a/.fileassert.yaml +++ b/.fileassert.yaml @@ -36,7 +36,7 @@ tests: - field: "Author" contains: "DEMA Consulting" - field: "Subject" - contains: "Build notes" + contains: "Build Notes" pages: min: 1 text: @@ -201,7 +201,7 @@ tests: - field: "Author" contains: "DEMA Consulting" - field: "Subject" - contains: "Verification design document" + contains: "Verification Design Document" pages: min: 3 text: @@ -234,7 +234,7 @@ tests: - field: "Author" contains: "DEMA Consulting" - field: "Subject" - contains: "File-Review Evidence Management" + contains: "User Guide" pages: min: 3 text: @@ -302,7 +302,7 @@ tests: - field: "Author" contains: "DEMA Consulting" - field: "Subject" - contains: "Traceability" + contains: "Trace Matrix" pages: min: 1 text: diff --git a/.github/agents/developer.agent.md b/.github/agents/developer.agent.md index a95c562..5f208eb 100644 --- a/.github/agents/developer.agent.md +++ b/.github/agents/developer.agent.md @@ -14,6 +14,10 @@ Perform software development tasks by determining and applying appropriate stand 2. **Read relevant standards** using the selection matrix in AGENTS.md 3. **Pre-flight verification** before making any changes: - List files that will be created, modified, or deleted + - For each file to be **created**, check whether a counterpart exists in the + template (URL in the `# Reference Template` section of `AGENTS.md`). + If one exists, fetch it as the starting point; adjust placeholder names and heading + depth to match the target path before writing the file - For each modified file, identify which companion artifacts need updating (requirements, design docs, tests, review-sets) - Include companion artifact updates in the work plan diff --git a/.github/agents/quality.agent.md b/.github/agents/quality.agent.md index da467d4..380d11f 100644 --- a/.github/agents/quality.agent.md +++ b/.github/agents/quality.agent.md @@ -54,21 +54,13 @@ Priority-ordered list of issues that MUST be resolved for the next retry: ## Requirements Compliance: (PASS|FAIL|N/A) -- Were requirements updated to reflect functional changes? -- Were new requirements created for new features? -- Do requirement IDs follow semantic naming standards? -- Do requirement files follow kebab-case naming convention? -- Are requirement files organized under `docs/reqstream/` with proper folder structure? -- Are OTS requirements properly placed in `docs/reqstream/ots/` subfolder? -- Were source filters applied appropriately for platform-specific requirements? +- Were requirements created/updated for all functional changes? +- Were source filters applied for platform-specific requirements? - Is requirements traceability maintained to tests? ## Design Documentation Compliance: (PASS|FAIL|N/A) -- Were design documents updated for architectural changes? -- Were new design artifacts created for new components? -- Do design folder names use kebab-case convention matching source structure? -- Are design files properly named ({subsystem-name}.md, {unit-name}.md patterns)? +- Were design artifacts created/updated for all new or changed components? - Is `docs/design/introduction.md` present with required Software Structure section? - Are design decisions documented with rationale? - Is system/subsystem/unit categorization maintained? @@ -76,55 +68,50 @@ Priority-ordered list of issues that MUST be resolved for the next retry: ## Code Quality Compliance: (PASS|FAIL|N/A) -- Are language-specific standards followed (from applicable standards files)? -- Are quality checks from standards files satisfied? -- Is code properly categorized (system/subsystem/unit/OTS)? -- Is appropriate separation of concerns maintained? -- Was language-specific build tooling executed and passing? +- Do language-specific quality checks from loaded standards pass? +- Is code properly categorized (system/subsystem/unit/OTS/Shared Package)? +- Does the build pass? ## Testing Compliance: (PASS|FAIL|N/A) - Were tests created/updated for all functional changes? - Is test coverage maintained for all requirements? -- Are testing standards followed (AAA pattern, etc.)? -- Do tests respect software item hierarchy boundaries (System/Subsystem/Unit scope)? +- Do tests respect software item hierarchy boundaries? - Are cross-hierarchy test dependencies documented in design docs? -- Does test categorization align with code structure? -- Do all tests pass without failures? +- Do all tests pass? ## Review Management Compliance: (PASS|FAIL|N/A) -- Were review-sets updated for structural changes (new/deleted systems, subsystems, or units)? -- Do file patterns follow include-then-exclude approach? +- Were review-sets updated for structural changes? - Is review scope appropriate for change magnitude? -- Was ReviewMark tooling executed and passing? -- Were review artifacts generated correctly? +- Does ReviewMark pass? ## Documentation Compliance: (PASS|FAIL|N/A) -- Was README.md updated for user-facing changes? -- Were user guides updated for feature changes? +- Were README.md and user guides updated for user-facing changes? - Does API documentation reflect code changes? - Was compliance documentation generated? -- Does documentation follow standards formatting? -- Is documentation organized under `docs/` following standard folder structure? -- Do Pandoc collections include proper `introduction.md` with Purpose and Scope sections? - Are auto-generated markdown files left unmodified? -- Do README.md files use absolute URLs and include concrete examples? -- Is documentation integrated into ReviewMark review-sets for formal review? +- Is documentation integrated into ReviewMark review-sets? ## Software Item Completeness: (PASS|FAIL|N/A) +- Load `software-items.md` before evaluating this section. + - Does every identified software unit have its own requirements file? - Does every identified software unit have its own design document? - Does every identified subsystem have its own requirements file? - Does every identified subsystem have its own design document? +## Repository Structure Compliance: (PASS|FAIL|N/A) + +- Load `repository-map.md` from the template URL in the `# Reference Template` + section of `AGENTS.md` before evaluating this section. + +- Are parallel artifact trees in sync (reqstream/design/verification/src/test)? +- Does the repository conform to the template `repository-map.md`? + ## Process Compliance: (PASS|FAIL|N/A) -- Was Continuous Compliance workflow followed? -- Did all quality gates execute successfully? -- Were appropriate tools used for validation? -- Were standards consistently applied across work? -- Was compliance evidence generated and preserved? +- Was compliance evidence (test results, review artifacts, generated docs) generated and preserved? ``` diff --git a/.github/agents/software-architect.agent.md b/.github/agents/software-architect.agent.md index 494568d..de5efa2 100644 --- a/.github/agents/software-architect.agent.md +++ b/.github/agents/software-architect.agent.md @@ -13,7 +13,7 @@ Interview the user and produce evolving architecture documentation with prioriti # Standards Read `.github/standards/software-items.md` before starting. Use its definitions -(Software Package, System, Subsystem, Unit, OTS) as vocabulary throughout. +(Software Package, System, Subsystem, Unit, OTS, Shared Package) as vocabulary throughout. # Approach diff --git a/.github/agents/template-sync.agent.md b/.github/agents/template-sync.agent.md new file mode 100644 index 0000000..fc5c62a --- /dev/null +++ b/.github/agents/template-sync.agent.md @@ -0,0 +1,109 @@ +--- +name: template-sync +description: Audits or synchronizes repository files against the canonical template. + Supports four modes - Audit, Sync, Scaffold, and Recreate. +user-invocable: true +--- + +# Template Sync Agent + +This agent is an orchestrator supporting four modes: + +- **Audit** - report structural deviations; no changes +- **Sync** - patch missing sections into existing files +- **Scaffold** - create files that do not yet exist; skip existing files +- **Recreate** - rebuild existing files from the template, migrating old content + +Read the template URL and `repository-map.md` from the `# Reference Template` +section in `AGENTS.md`, then map the requested scope onto the work groups below. +Delegate each group to a sub-agent. + +# Work Groups + +- **Root config files** - all non-collection files at the repository root +- **One group per flat `docs/` folder** - e.g. `docs/build_notes/`, `docs/user_guide/` +- **One group per system subtree** in `docs/design/`, `docs/verification/`, `docs/reqstream/` - + each subtree and all its descendants is one group + +# Orchestration + +For each group intersecting the requested scope, call a sub-agent with: + +- **context**: + - Group scope and template URL from the `# Reference Template` section in `AGENTS.md` + - Applicable standards from the `# Standards Application` matrix in `AGENTS.md` + for the file types in the group scope + - Project-specific names substitute for placeholders at matching path depth + (e.g. `SystemName` → `{SystemName}`, `system-name` → `{system-name}`) + - For files within `{system-name}/` subtrees in `docs/design/`, `docs/verification/`, + and `docs/reqstream/`: consult `docs/design/introduction.md` to determine whether + each item is a subsystem or unit, then select the appropriate template + (`subsystem-name.*` or `unit-name.*`) regardless of the item's folder depth — + do not infer item type from path depth alone + - If a template counterpart cannot be fetched, skip the file and report it +- **goal**: + - Based on the given mode: + - **Audit** - fetch each template counterpart; compare headings; report missing + sections and depth mismatches; no changes + - **Sync** - as Audit, then insert each missing section; run `pwsh ./fix.ps1` + - **Scaffold** - fetch `repository-map.md` from the template URL in `AGENTS.md` + to identify files that should exist but don't; for each, fetch the template, + populate all sections, write the file; run `pwsh ./fix.ps1` + - **Recreate** - fetch the template and use it as the blueprint for a + freshly authored document: + - Work through the template section by section; for each section, find + any `TEMPLATE-DIRECTIVE` blocks (both `` + in markdown and `# ` in YAML) — execute + each directive (read specified standards, apply structural guidance, + substitute content), then **remove the directive block entirely** from + the output; gather the relevant technical details from all available + sources — the old file, README, related docs, sibling files, and any + other repo context — to populate that section correctly; the old file's + structure and headings are irrelevant; only its factual content is mined + as a source + - **Gap-check**: after all template sections are filled, scan the old + file once more for any technical information not yet captured; if + found, preserve it by appending new relevant sections at the end + - **Before writing**: do a mandatory self-check — for every section that + has a `TEMPLATE-DIRECTIVE` block in the template, explicitly state what + format the directive requires, then verify the drafted content matches + that format exactly (e.g. if the directive says "no sub-headings", + confirm there are no `###` headings inside that section; if it says + "bold-name paragraph blocks", confirm each entry is `**Name**: prose` + with no sub-heading); fix any mismatches before writing the file + - Write the rebuilt file; run `pwsh ./fix.ps1` + - When writing any section: `TEMPLATE-DIRECTIVE` blocks are directives — + execute them (read specified standards, apply structural guidance, substitute + content) and **remove the block entirely** from the written file; inline + `TODO:` placeholders in YAML string values (e.g. `title:`, `justification:`) + are content placeholders — always resolve them to real content; infer from + README, related files, sibling docs, and path; if confident write directly; + if ambiguous offer 2–3 concrete options and ask the user; keep asking until + they answer - never leave a TODO or TEMPLATE-DIRECTIVE in the output unless + the user explicitly requests it + +Collect sub-agent results and assemble the final report. + +# Report Template + +```markdown +# Template Sync Report + +**Result**: (SUCCEEDED|FAILED) +**Mode**: (Audit|Sync|Scaffold|Recreate) + +## Files + +### {file-path} + +- **Template**: {template path} +- **Missing sections**: {list or "none"} +- **Heading depth issues**: {list or "none"} +- **Content format issues**: {list of sections where intra-section content did not + match the template comment's prescribed format, or "none"} *(Recreate only)* +- **Action**: (Reported | Sections added | Created | Rebuilt | No template found) + +## Summary + +- **Conformant**: {count} | **Deviations**: {count} | **Updated**: {count} +``` diff --git a/.github/standards/design-documentation.md b/.github/standards/design-documentation.md index 768bf3f..1fd9893 100644 --- a/.github/standards/design-documentation.md +++ b/.github/standards/design-documentation.md @@ -4,214 +4,112 @@ description: Follow these standards when creating design documentation. globs: ["docs/design/**/*.md"] --- -# Design Documentation Standards - -This document defines standards for design documentation within Continuous -Compliance environments, extending the general technical documentation -standards with specific requirements for software design artifacts. - -## Required Standards - -Read these standards first before applying this standard: +# Required Standards - **`technical-documentation.md`** - General technical documentation standards -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) - -# Core Principles - -Design documentation serves as the bridge between requirements and -implementation, providing detailed technical specifications that enable: +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) -- **Formal Code Review**: Reviewers can verify implementation matches design -- **Compliance Evidence**: Auditors can trace requirements through design to code -- **Maintenance Support**: Developers can understand system structure and interactions -- **Quality Assurance**: Testing teams can validate against detailed specifications - -# Required Structure and Documents - -Design documentation must be organized under `docs/design/` with folder structure -mirroring source code organization because reviewers need clear navigation from -design to implementation: +# Folder Structure ```text docs/design/ -├── introduction.md # Document overview - heading depth # -├── {system-name}.md # System-level design - heading depth # -└── {system-name}/ # System folder (one per system) - ├── {subsystem-name}.md # Subsystem overview - heading depth ## - ├── {subsystem-name}/ # Subsystem folder (kebab-case); may nest recursively - │ ├── {child-subsystem}.md # Child subsystem overview - heading depth ### - │ ├── {child-subsystem}/ # Child subsystem folder (same structure as parent) - │ └── {unit-name}.md # Unit design - heading depth ### - └── {unit-name}.md # System-level unit design - heading depth ## +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## ``` -Each scope's overview file lives in its **parent** folder, not inside the scope's own -subfolder - this aligns heading depth with folder depth so the compiled PDF has a -meaningful multi-level outline (see Heading Depth Rule in `technical-documentation.md`). +Subsystems may nest recursively. Each file's heading depth equals its folder depth under `docs/design/`. -## introduction.md (MANDATORY) +# introduction.md (MANDATORY) -The `introduction.md` file serves as the design entry point and MUST include -these sections because auditors need clear scope boundaries and architectural -overview: +Must include: -### Purpose Section +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Software Structure**: text tree showing all Systems/Subsystems/Units/OTS/Shared items +- **Folder Layout**: text tree showing source folder structure +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` -Clear statement of the design document's purpose, audience, and regulatory -or compliance drivers. +# System Design (MANDATORY) -### Scope Section +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder. All sections mandatory; +write "N/A - {justification}" rather than removing any section: -Define what software items are covered and what is explicitly excluded. -Design documentation must NOT include test projects, test classes, or test -infrastructure because design documentation documents the architecture of -shipping product code, not ancillary content used to validate it. +- **Architecture**: software items, relationships, and collaboration +- **External Interfaces**: name, direction, format, constraints +- **Dependencies**: OTS and Shared Packages used; cross-reference their design docs +- **Risk Control Measures**: segregation required for risk control (IEC 62304 §5.3.3) +- **Data Flow**: inputs to outputs +- **Design Constraints**: platform, performance, security, regulatory -### Software Structure Section (MANDATORY) +# Subsystem Design (MANDATORY) -Include a text-based tree diagram showing the software organization across -System, Subsystem, and Unit levels. Agents MUST read `software-items.md` -to understand these classifications before creating this section. +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children. +**Important**: A file at `{system-name}/{name}.md` may be either a subsystem or a unit. Always +determine the correct classification from `docs/design/introduction.md` — folder depth does not +determine classification. +All sections mandatory; write "N/A - {justification}" rather than removing any section: -Example format: +- **Overview**: responsibility, boundaries, contained units +- **Interfaces**: what it exposes and consumes +- **Design**: how internal units collaborate -```text -Project1Name (System) -├── ComponentA (Subsystem) -│ ├── SubComponentP (Subsystem) -│ │ └── ClassW (Unit) -│ ├── ClassX (Unit) -│ └── ClassY (Unit) -├── ComponentB (Subsystem) -│ └── ClassZ (Unit) -└── UtilityClass (Unit) - -Project2Name (System) -└── HelperClass (Unit) -``` +# Unit Design (MANDATORY) -### Folder Layout Section (MANDATORY) +Place `{unit-name}.md` in the **parent** folder. +**Important**: A file at `{system-name}/{name}.md` may be either a subsystem or a unit. Always +determine the correct classification from `docs/design/introduction.md` — folder depth does not +determine classification. +All sections mandatory; write "N/A - {justification}" rather than removing any section: -Include a text-based tree diagram showing how the source code folders -mirror the software structure, with file paths and brief descriptions. +- **Purpose**: single responsibility +- **Data Model**: fields, properties, types, invariants (IEC 62304 §5.4.2) +- **Key Methods**: name, purpose, algorithm, preconditions, postconditions, parameter types +- **Error Handling**: detection and handling; what is propagated vs. handled locally +- **Interactions**: dependencies on other units/subsystems/OTS; who calls this unit -Example format: +# OTS Integration Design (when OTS items exist) -```text -src/Project1Name/ -├── ComponentA/ -│ ├── SubComponentP/ -│ │ └── ClassW.cs - Specialized processing engine -│ ├── ClassX.cs - Core business logic handler -│ └── ClassY.cs - Data validation service -├── ComponentB/ -│ └── ClassZ.cs - Integration interface -└── UtilityClass.cs - Common utility functions - -src/Project2Name/ -└── HelperClass.cs - Helper functions -``` - -### References Section (RECOMMENDED) - -If the design references external documents (standards, specifications), include -a `## References` section in `introduction.md`. This is the **only** place in the -design document collection where a References section should appear - do not add -one to any other design file. +Create `docs/design/ots.md` (`#` heading) covering the overall OTS integration strategy. -### Companion Artifact Structure (RECOMMENDED) - -Include a brief note explaining that each software item has parallel artifacts -across the repository, so agents and reviewers can navigate from any one -artifact to all related files: - -Example format: - -```text -Each in-house software item has corresponding artifacts in parallel directory trees: +For each OTS item, create `docs/design/ots/{ots-name}.md` (`##` heading) covering: +why chosen, which features/APIs used, integration patterns, version constraints. -- Requirements: `docs/reqstream/{system-name}.yaml`, `docs/reqstream/{system-name}/.../{item}.yaml` -- Design docs: `docs/design/{system-name}.md`, `docs/design/{system-name}/.../{item}.md` -- Verification: `docs/verification/{system-name}.md`, `docs/verification/{system-name}/.../{item}.md` -- Source code: `src/{SystemName}/.../{Item}.{ext}` (cased per language - see `software-items.md`) -- Tests: `test/{SystemName}.Tests/.../{Item}Tests.{ext}` (cased per language) +# Shared Package Integration Design (when Shared Packages exist) -OTS items have no design documentation; their artifacts sit parallel to system folders: +Create `docs/design/shared.md` (`#` heading) covering the overall consumption strategy. -- Requirements: `docs/reqstream/ots/{ots-name}.yaml` -- Verification: `docs/verification/ots/{ots-name}.md` -- Tests (optional): `test/{OtsSoftwareTests}/...` (cased per language - see `software-items.md`) - -Review-sets: defined in `.reviewmark.yaml` -``` - -## System Design Documentation (MANDATORY) - -For each system identified in the repository: - -- Create `{system-name}.md` directly under `docs/design/` (heading depth `#`) -- Create a kebab-case folder `{system-name}/` to hold its subsystems and units -- `{system-name}.md` must cover: - - System architecture and major components - - External interfaces and dependencies - - Data flow and control flow - - System-wide design constraints and decisions - - Integration patterns and communication protocols - -## Subsystem and Unit Design Documents - -For each subsystem identified in the software structure: - -- Place `{subsystem-name}.md` inside the **parent** folder (the system folder, or parent - subsystem folder) - not inside its own subfolder -- Create a kebab-case folder `{subsystem-name}/` to hold its child units and subsystems -- `{subsystem-name}.md` must cover subsystem overview and design - -For every unit identified in the software structure: - -- Place `{unit-name}.md` inside its parent scope's folder (system or subsystem folder) -- Document data models, algorithms, and key methods -- Describe interactions with other units -- Include sufficient detail for formal code review - -Follow the Heading Depth Rule from `technical-documentation.md` - a file's top-level -heading depth equals its folder depth under `docs/design/`. - -# Software Items Integration (CRITICAL) - -Read `software-items.md` before creating design documentation - correct -System/Subsystem/Unit categorization is required for software structure -diagrams and folder layout. +For each Shared Package, create `docs/design/shared/{package-name}.md` (`##` heading) covering: +which advertised features are consumed, integration pattern, configuration/initialization. # Writing Guidelines -Design documentation must be technical and specific because it serves as the -implementation specification for formal code review: - -- **Implementation Detail**: Provide sufficient detail for code review and implementation -- **Architectural Clarity**: Clearly define component boundaries and interfaces -- **Traceability**: Link to requirements where applicable using ReqStream patterns -- **Verbal Cross-References**: Reference other parts of the design by name (e.g., - "See *Parser Design* for more details") - do not use markdown hyperlinks, which - break in compiled PDFs - -# Mermaid Diagram Integration - -Use Mermaid diagrams to supplement text descriptions (diagrams must not replace text content). +- Use Mermaid diagrams to supplement (not replace) text +- Use verbal cross-references ("see _Parser Design_") - not markdown hyperlinks (break in PDF) +- Provide sufficient detail for formal code review # Quality Checks -Before submitting design documentation, verify: - -- [ ] `introduction.md` includes both Software Structure and Folder Layout sections -- [ ] Software structure correctly categorizes items as System/Subsystem/Unit per `software-items.md` -- [ ] Folder layout mirrors software structure organization -- [ ] Files organized under `docs/design/` following the folder structure pattern above -- [ ] Each file's top-level heading depth matches its folder depth per the Heading Depth Rule -- [ ] Design documents provide sufficient detail for code review -- [ ] System documentation provides comprehensive system-level design -- [ ] All documentation folders use kebab-case names mirroring source code structure -- [ ] All documents follow technical documentation formatting standards -- [ ] Content is current with implementation and requirements -- [ ] Documents are integrated into ReviewMark review-sets for formal review +- [ ] `introduction.md` includes Software Structure, Folder Layout, and Companion Artifact Structure +- [ ] Software structure correctly categorizes items per `software-items.md` +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] System design includes all mandatory sections (Architecture, External Interfaces, Dependencies, + Risk Control Measures, Data Flow, Design Constraints) +- [ ] Subsystem design includes all mandatory sections (Overview, Interfaces, Design) +- [ ] Unit design includes all mandatory sections (Purpose, Data Model, Key Methods, Error Handling, Interactions) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] `docs/design/ots.md` and `docs/design/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/design/shared.md` and `docs/design/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.github/standards/reqstream-usage.md b/.github/standards/reqstream-usage.md index 303bb43..0b30dfa 100644 --- a/.github/standards/reqstream-usage.md +++ b/.github/standards/reqstream-usage.md @@ -9,7 +9,7 @@ globs: ["requirements.yaml", "docs/reqstream/**/*.yaml"] Read these standards first before applying this standard: - **`requirements-principles.md`** - Requirements principles and unidirectionality -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) # Requirements Organization @@ -29,48 +29,68 @@ docs/reqstream/ │ │ ├── {child-subsystem}/ # Child subsystem folder │ │ └── {unit-name}.yaml # Unit requirements │ └── {unit-name}.yaml # System-level unit requirements -└── ots/ # OTS items appear as a distinct section in reports - └── {ots-name}.yaml # Requirements for OTS components +├── ots/ # OTS items appear as a distinct section in reports +│ └── {ots-name}.yaml # Requirements for OTS components +└── shared/ # Shared Packages appear as a distinct section in reports + └── {package-name}.yaml # Requirements for Shared Package dependencies ``` -In-house items have matching relative paths across `docs/reqstream/`, `docs/design/`, and -`docs/verification/`. OTS items appear only in `docs/reqstream/ots/` and -`docs/verification/ots/` - they have no design documentation. +Local items have matching paths across `docs/reqstream/`, `docs/design/`, and `docs/verification/`. # Requirements File Format -```yaml -sections: - - title: Functional Requirements - requirements: - - id: System-Component-Feature # Used as-is in all reports - make it readable - title: The system shall perform the required function. - justification: | - Business rationale and any regulatory references. - # ReqStream extracts this field into the justifications report (--justifications) - children: # ReqStream validates this decomposition chain - - ChildSystem-Feature-Behavior # Downward links only (see requirements-principles.md) - tests: # ReqStream matches these by method name in test results - - TestMethodName - - windows@PlatformSpecificTest # Only test runs on Windows count as evidence -``` +Each file adds requirements at exactly one level of the hierarchy. The file spells out +its full ancestry as nested `{ItemName} Requirements` sections down to that level, then +places requirements there. ReqStream merges identical section title paths across included +files automatically. Always determine item classification from `docs/design/introduction.md` - +folder depth does not determine whether an item is a subsystem or unit. -# OTS Software Requirements +Valid section nestings (names in `{braces}` are placeholders): + +```text +{System} Requirements # system-level requirements +├── {Subsystem} Requirements # root subsystem requirements +│ ├── {Subsystem} Requirements # nested subsystem (may recurse) +│ │ └── {Unit} Requirements # unit under a nested subsystem +│ └── {Unit} Requirements # unit under a root subsystem +└── {Unit} Requirements # unit directly under the system +OTS Software Requirements # OTS root section (fixed title) +└── {OtsName} Requirements # requirements for one OTS item +Shared Package Requirements # shared package root section (fixed title) +└── {PackageName} Requirements # requirements for one shared package +``` -Use nested sections in `docs/reqstream/ots/` because ReqStream renders the `ots/` -subtree as a distinct section in generated reports, separate from in-house -system requirements: +Each file implements one path through this tree: ```yaml sections: - - title: OTS Software Requirements + - title: '{SystemName} Requirements' sections: - - title: System.Text.Json + - title: '{SubsystemName} Requirements' requirements: - - id: SystemTextJson-Core-ReadJson - title: System.Text.Json shall be able to read JSON files. - tests: - - JsonReaderTests.TestReadValidJson + - id: System-Subsystem-Feature # Used as-is in all reports - make it readable + title: The subsystem shall perform the required function. + justification: | # ReqStream extracts this into the justifications report (--justifications) + Business rationale and any regulatory references. + tags: # Optional: categorize for filtering with --filter + - security + children: # Optional: ReqStream validates this decomposition chain + - System-Subsystem-Unit-Feat # Downward links only (see requirements-principles.md) + tests: # ReqStream matches these by method name in test results + - TestMethodName + - windows@PlatformSpecificTest # Only test runs on Windows count as evidence +``` + +# Tags (OPTIONAL) + +Tags are free-form - no mandatory vocabulary. Common tags: `security`, `safety`, `performance`, +`compliance`, `reliability`, `critical`. Use `--filter` to selectively export or enforce subsets +(OR logic across comma-separated tags): + +```bash +dotnet reqstream --requirements requirements.yaml \ + --filter security,critical \ + --report docs/requirements_doc/generated/security_requirements.md ``` # Semantic IDs (MANDATORY) @@ -125,12 +145,9 @@ dotnet reqstream --requirements requirements.yaml \ Before submitting requirements, verify: -- [ ] All requirements have semantic IDs (`System-Section-Feature` pattern) -- [ ] Every requirement links to at least one passing test +- [ ] All requirements have semantic IDs (`System-Component-Feature` pattern) +- [ ] Every requirement has a justification explaining business/regulatory need +- [ ] Every requirement links to at least one test - [ ] Platform-specific requirements use source filters (`platform@TestName`) -- [ ] Comprehensive justification explains business/regulatory need -- [ ] Files organized under `docs/reqstream/` following the folder structure pattern above -- [ ] All documentation folders use kebab-case names matching source code structure -- [ ] OTS requirements placed in `ots/` subfolder -- [ ] Valid YAML syntax passes yamllint validation -- [ ] Test result formats compatible (TRX, JUnit XML) +- [ ] All files and folders use kebab-case names matching source code structure +- [ ] All files are organized under `docs/reqstream/` following the folder structure above diff --git a/.github/standards/reviewmark-usage.md b/.github/standards/reviewmark-usage.md index 2d95832..17952ed 100644 --- a/.github/standards/reviewmark-usage.md +++ b/.github/standards/reviewmark-usage.md @@ -9,7 +9,7 @@ description: Follow these standards when configuring file reviews with ReviewMar Read these standards first before applying this standard: -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) ## Purpose @@ -139,6 +139,23 @@ Reviews architectural and design consistency: - Design introduction: `docs/design/introduction.md` - System design: `docs/design/{system-name}.md` - System design files: `docs/design/{system-name}/**/*.md` + - OTS overview: `docs/design/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/design/shared.md` _(only if Shared Package items exist)_ + +## `{System}-Verification` Review (one per system) + +Reviews verification completeness and consistency: + +- **Purpose**: Proves the system verification design is consistent and covers all requirements +- **Title**: "Review that {System} Verification is Consistent and Complete" +- **Scope**: Only brings in top-level requirements and all verification docs for the system +- **File Path Patterns**: + - System requirements: `docs/reqstream/{system-name}.yaml` + - Verification introduction: `docs/verification/introduction.md` + - System verification: `docs/verification/{system-name}.md` + - System verification files: `docs/verification/{system-name}/**/*.md` + - OTS overview: `docs/verification/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/verification/shared.md` _(only if Shared Package items exist)_ ## `{System}-AllRequirements` Review (one per system) @@ -182,16 +199,29 @@ Reviews individual software unit implementation: ## `OTS-{OtsName}` Review (one per OTS item) -Reviews OTS item requirements and verification evidence: +Reviews OTS item integration design, requirements, and verification evidence: -- **Purpose**: Proves that the OTS item provides the required functionality +- **Purpose**: Proves that the OTS item provides the required functionality and is correctly integrated - **Title**: "Review that {OtsName} Provides Required Functionality" -- **Scope**: OTS items have no in-house design or source; review covers requirements and - verification evidence only +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence - **File Path Patterns**: - OTS requirements: `docs/reqstream/ots/{ots-name}.yaml` + - OTS integration design: `docs/design/ots/{ots-name}.md` - OTS verification: `docs/verification/ots/{ots-name}.md` - - Tests (if applicable): `test/{OtsSoftwareTests}/...` (cased per language) + - Tests (if applicable): `test/OtsSoftwareTests/...` (C#) or `test/ots_software_tests/...` + (Python/other) — fixed repo-level name, no system prefix + +## `Shared-{PackageName}` Review (one per Shared Package) + +Reviews Shared Package integration design, requirements, and verification evidence: + +- **Purpose**: Proves that the Shared Package provides the required advertised features and is correctly integrated +- **Title**: "Review that {PackageName} Provides Required Features" +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence +- **File Path Patterns**: + - Shared Package requirements: `docs/reqstream/shared/{package-name}.yaml` + - Shared Package integration design: `docs/design/shared/{package-name}.md` + - Shared Package verification: `docs/verification/shared/{package-name}.md` **Note**: File path patterns use `{ext}` as a placeholder for language-specific extensions (`.cs`, `.cpp`/`.hpp`, `.py`, etc.). Adapt to your repository's languages. @@ -207,9 +237,12 @@ Before submitting ReviewMark configuration, verify: - [ ] Subsystem reviews follow hierarchical scope principle (exclude unit source code) - [ ] Only unit reviews include actual source code files - [ ] Architecture review-sets include system verification design alongside system design +- [ ] Design review-sets include all system design files +- [ ] Verification review-sets include all system verification files - [ ] Subsystem review-sets include subsystem verification design - [ ] Unit review-sets include unit verification design -- [ ] OTS review-sets include OTS requirements and verification evidence +- [ ] OTS review-sets include OTS requirements, integration design, and verification evidence +- [ ] Shared Package review-sets include Shared Package requirements, integration design, and verification evidence - [ ] Each review-set focuses on a single compliance question (single focus principle) - [ ] File patterns use correct glob syntax and match intended files - [ ] Review-set file counts remain manageable (context management principle) diff --git a/.github/standards/software-items.md b/.github/standards/software-items.md index 6be029f..07c4013 100644 --- a/.github/standards/software-items.md +++ b/.github/standards/software-items.md @@ -11,12 +11,14 @@ requirements management approach, testing strategy, and review scope. # Software Item Categories -Categorize all software into five primary groups: +Categorize all software into six primary groups: - **Software Package**: Distributable unit delivered to end users or dependent systems, containing one software system with all its components. All software - systems are delivered as a software package. When consumed by another system, - our software package is treated as an OTS Software Item by that system. + systems are delivered as a software package. When consumed by a system outside + the producing program, our software package is treated as an OTS Software Item + by that system. When consumed by another repository within the same program, + it is treated as a Shared Package. - **Software System**: Complete deliverable product including all components and external interfaces, contained within a software package - **Software Subsystem**: Major architectural component with well-defined @@ -24,7 +26,11 @@ Categorize all software into five primary groups: - **Software Unit**: Individual class, function, or tightly coupled set of functions that can be tested in isolation - **OTS Software Item**: Third-party component (library, framework, tool, or - published software package) providing functionality not developed in-house + published software package) providing functionality not developed within the program +- **Shared Package**: A software package produced by a different repository within + the same program, consumed as a dependency. Referenced by its advertised features + rather than internal design; traceability to program-level requirements runs + through the top-level project. **Naming**: When names collide in hierarchy, add descriptive suffix to higher-level entity: @@ -75,20 +81,38 @@ Choose the appropriate category based on scope and testability: ## OTS Software Item -- External dependency not developed in-house - typically a third-party published +- External dependency from outside the program - typically a third-party published software package (NuGet, npm, etc.), hosted service, or tool -- Our own published software package becomes an OTS item to any system that - consumes it +- A package produced by an unrelated program (inside or outside the organization) + is treated as OTS by any consuming system - Tested through integration tests proving required functionality works - Examples: System.Text.Json, Entity Framework, third-party APIs -- **Artifact locations** (OTS items have no design documentation): +- **Artifact locations** (OTS items have no internal design documentation): - Requirements: `docs/reqstream/ots/{ots-name}.yaml` + - Design: `docs/design/ots/{ots-name}.md` (integration/usage design - how the local system uses this item) - Verification: `docs/verification/ots/{ots-name}.md` - These folders sit parallel to system folders (not inside any system folder) - System design documentation records which OTS items each system depends on - **OTS test project**: If no other verification evidence is available (e.g., vendor test results, - published compliance reports), a dedicated test project (`OtsSoftwareTests` / `ots_software_tests`, - cased per language) holds OTS integration tests - one test file per OTS item requiring tests. + published compliance reports), a dedicated test project holds OTS integration tests - one test + file per OTS item requiring tests. OTS items are repo-level (not per-system), so the project + uses a fixed repo-level name: `test/OtsSoftwareTests/` (C#) or `test/ots_software_tests/` + (Python/other) — never prefixed with a system or project name. + +## Shared Package + +- A software package produced by a different repository within the same program +- The consuming repository references advertised features, not internal design or source +- Traceability to program-level requirements runs through the top-level project, + not directly between repositories +- Verified through any appropriate approach in the consuming repository - most commonly + downstream integration tests that transitively prove the advertised features are functional +- **Artifact locations** (no internal design documentation in the consuming repository): + - Requirements: `docs/reqstream/shared/{package-name}.yaml` + - Design: `docs/design/shared/{package-name}.md` (integration/usage design - which features are consumed and how) + - Verification: `docs/verification/shared/{package-name}.md` + - These folders sit parallel to system and OTS folders +- System design documentation records which Shared Packages each system depends on # Software Item Artifact Model @@ -97,7 +121,8 @@ unit - because reviewing any one artifact in isolation cannot determine whether the item is correct, well-designed, and proven to work: - **Requirements** - WHAT the item must do (drives all other artifacts; applies to all item types) -- **Design** - HOW the item satisfies its requirements (in-house items only: system, subsystem, unit) +- **Design** - HOW the item satisfies its requirements (full design for local items: system, + subsystem, unit; integration/usage design for OTS and Shared Package) - **Verification Design** - HOW the requirements will be tested (applies to all item types) -- **Source code** - The implementation of the design (in-house units only) +- **Source code** - The implementation of the design (local units only; not applicable to OTS or Shared Package) - **Tests** - PROOF the item does WHAT it is required to do (applies to all item types) diff --git a/.github/standards/technical-documentation.md b/.github/standards/technical-documentation.md index 2ac29f4..0dc4455 100644 --- a/.github/standards/technical-documentation.md +++ b/.github/standards/technical-documentation.md @@ -43,6 +43,10 @@ When creating a new document collection, create these three files together and u the existing collections under `docs/` as templates - they share a consistent structure across all collections. +The `generated/` folder is **never committed** to the repository - it is created +locally and in CI by the build pipeline. Do not flag its absence as a conformance +issue. + **`title.txt`** - YAML front matter with document metadata. Use the existing files under `docs/` as a pattern and keep fields consistent with the rest of the repository. diff --git a/.github/standards/verification-documentation.md b/.github/standards/verification-documentation.md index 8eea3b7..f3d950e 100644 --- a/.github/standards/verification-documentation.md +++ b/.github/standards/verification-documentation.md @@ -6,139 +6,111 @@ globs: ["docs/verification/**/*.md"] # Required Standards -Read these standards first before applying this standard: - - **`technical-documentation.md`** - General technical documentation standards -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) - -# Core Principles - -Verification design is the bridge between requirements and tests - it documents HOW -requirements will be verified, enabling reviewers to confirm test completeness without -reading implementation code. - -# Required Structure and Documents +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) -Organize under `docs/verification/` mirroring the software item hierarchy: +# Folder Structure ```text docs/verification/ -├── introduction.md # Document overview - heading depth # -├── {system-name}.md # System-level verification - heading depth # -├── {system-name}/ # System folder (one per system) -│ ├── {subsystem-name}.md # Subsystem verification - heading depth ## -│ ├── {subsystem-name}/ # Subsystem folder (kebab-case); may nest recursively -│ │ ├── {child-subsystem}.md # Child subsystem verification - heading depth ### -│ │ ├── {child-subsystem}/ # Child subsystem folder (same structure as parent) -│ │ └── {unit-name}.md # Unit verification - heading depth ### -│ └── {unit-name}.md # System-level unit verification - heading depth ## -├── ots.md # OTS section overview - heading depth # (MANDATORY if OTS items exist) -└── ots/ # OTS items - parallel to system folders (not inside them) - └── {ots-name}.md # OTS item verification evidence - heading depth ## +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## ``` -Each scope's overview file lives in its **parent** folder, not inside the scope's own -subfolder - this keeps artifact locations consistent with design and requirements trees -so any item's files are deterministically locatable, and aligns heading depth with folder -depth for correct PDF structure (see Heading Depth Rule in `technical-documentation.md`). +Subsystems may nest recursively. Each file's heading depth equals its folder depth under `docs/verification/`. -## introduction.md (MANDATORY) +# introduction.md (MANDATORY) -Follow the standard `introduction.md` format from `technical-documentation.md`. Scope -covers all software items including OTS items (via self-validation if appropriate). +Must include: -Include a Companion Artifact Structure note so agents and reviewers can navigate from any -artifact to all related files: +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` -```text -In-house items have parallel artifacts in: -- Requirements: `docs/reqstream/{system-name}.yaml`, `docs/reqstream/{system-name}/.../{item}.yaml` -- Design: `docs/design/{system-name}.md`, `docs/design/{system-name}/.../{item}.md` -- Verification: `docs/verification/{system-name}.md`, `docs/verification/{system-name}/.../{item}.md` -- Source: `src/{SystemName}/.../{Item}.{ext}` (cased per language) -- Tests: `test/{SystemName}.Tests/.../{Item}Tests.{ext}` (cased per language) - -OTS items (no design documentation) have artifacts parallel to system folders: -- Requirements: `docs/reqstream/ots/{ots-name}.yaml` -- Verification: `docs/verification/ots/{ots-name}.md` -- Tests (if required): `test/{OtsSoftwareTests}/...` (cased per language - see `software-items.md`) - -Review-sets: defined in `.reviewmark.yaml` -``` +# System Verification Design (MANDATORY) -If the verification design references external documents (standards, specifications), include -a `## References` section in `introduction.md` only - do not add one to any other verification file. +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder. All sections mandatory; +write "N/A - {justification}" rather than removing any section: -## System Verification Design (MANDATORY) +- **Verification Strategy**: test types (unit, integration, end-to-end), framework, project structure +- **Test Environment**: OS, runtime, external services, files, or configuration required +- **Acceptance Criteria**: what constitutes a passing system test (IEC 62304 §5.7.2) +- **System-Level Test Scenarios**: named scenarios for each system requirement +- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping -For each system, create `{system-name}.md` at `docs/verification/` root and a -`{system-name}/` folder for subsystems. Cover: +# Subsystem Verification Design (MANDATORY) -- System verification strategy and overall test approach -- Test environments and configuration required -- External interface simulation and test-harness design -- End-to-end and integration test scenarios covering system requirements -- Acceptance criteria and pass/fail conditions at the system boundary -- Coverage mapping of system requirements to system-level test scenarios +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children. +**Important**: A file at `{system-name}/{name}.md` may be either a subsystem or a unit. Always +determine the correct classification from `docs/design/introduction.md` — folder depth does not +determine classification. +All sections mandatory; write "N/A - {justification}" rather than removing any section: -## Subsystem Verification Design (MANDATORY) +- **Verification Strategy**: integration test approach and mocking at subsystem boundary +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes a passing subsystem test (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary conditions, error paths, and normal operation +- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping -For each subsystem, place `{subsystem-name}.md` in the parent (system or subsystem) -folder and create a `{subsystem-name}/` folder for its units. Cover: +# Unit Verification Design (MANDATORY) -- Subsystem verification strategy and integration test approach -- Dependencies that must be mocked or stubbed at the subsystem boundary -- Integration test scenarios covering subsystem requirements -- Coverage mapping of subsystem requirements to subsystem-level test scenarios +Place `{unit-name}.md` in the **parent** folder. +**Important**: A file at `{system-name}/{name}.md` may be either a subsystem or a unit. Always +determine the correct classification from `docs/design/introduction.md` — folder depth does not +determine classification. +All sections mandatory; write "N/A - {justification}" rather than removing any section: -## Unit Verification Design (MANDATORY) +- **Verification Approach**: what is mocked/stubbed and why; injected vs. real dependencies +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes passing unit tests (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary values, error paths, and normal operation +- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping -Place `{unit-name}.md` in the parent (system or subsystem) folder. Cover: +# OTS Verification Evidence (when OTS items exist) -- Verification approach for each unit requirement -- Named test scenarios including boundary conditions, error paths, and normal-operation cases -- Which dependencies are mocked and how they are configured -- Coverage mapping of every unit requirement to at least one named test scenario +Create `docs/verification/ots.md` (`#` heading) covering the overall OTS verification strategy. -## OTS Verification Evidence (when OTS items are used) +For each OTS item, create `docs/verification/ots/{ots-name}.md` (`##` heading) covering: +verification approach (self-validation, integration tests, vendor evidence) and requirements coverage. -Create `docs/verification/ots.md` at the collection root with a `#` top-level heading. This -file introduces the OTS verification approach and ensures OTS items compile as a top-level -section in the PDF rather than as subsystems of the last in-house system. +# Shared Package Verification Evidence (when Shared Packages exist) -For each OTS item, create `docs/verification/ots/{ots-name}.md` covering: +Create `docs/verification/shared.md` (`#` heading) covering the overall Shared Package verification strategy. -- The OTS item's required functionality (reference `docs/reqstream/ots/{ots-name}.yaml`) -- Verification of each requirement (using self-validation evidence if appropriate) -- Coverage mapping of OTS requirements to test scenarios +For each Shared Package, create `docs/verification/shared/{package-name}.md` (`##` heading) covering: +verification approach and requirements coverage. # Writing Guidelines -- **Test Coverage**: Map every requirement to at least one named test scenario so - reviewers can verify completeness without reading test code -- **Scenario Clarity**: Name each scenario clearly - "Valid input returns parsed result" not "Test 1" -- **Boundary Conditions**: Call out boundary values, error inputs, and edge cases explicitly -- **Isolation Strategy**: Describe what is mocked or stubbed and why at each level -- **Traceability**: Link to requirements where applicable using ReqStream patterns -- **Verbal Cross-References**: Reference other documents by name - do not use markdown - hyperlinks, which break in compiled PDFs - -Mermaid diagrams may supplement text descriptions where test flow benefits from visual -representation, but must not replace text content. +- Name scenarios clearly ("Valid input returns parsed result", not "Test 1") +- Use verbal cross-references - not markdown hyperlinks (break in PDF) +- Use Mermaid diagrams to supplement (not replace) text # Quality Checks -Before submitting verification documentation, verify: - -- [ ] Every requirement at each level is mapped to at least one named test scenario -- [ ] System verification documents cover end-to-end and integration scenarios -- [ ] Subsystem verification documents identify mocked boundaries and integration scenarios -- [ ] Unit verification documents identify individual scenarios including boundary and error paths -- [ ] Files organized under `docs/verification/` following the folder structure pattern above -- [ ] Each file's top-level heading depth matches its folder depth per the Heading Depth Rule -- [ ] All documentation folders use kebab-case names mirroring source code structure -- [ ] All documents follow technical documentation formatting standards -- [ ] Content is current with requirements and test implementation -- [ ] Every OTS item has `docs/verification/ots/{ots-name}.md` with requirement coverage -- [ ] `docs/verification/ots.md` exists with a `#` heading when OTS items are present -- [ ] Documents are integrated into ReviewMark review-sets for formal review +- [ ] `introduction.md` includes Companion Artifact Structure +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] System verification includes all mandatory sections (Verification Strategy, Test Environment, + Acceptance Criteria, System-Level Test Scenarios, Requirements Coverage) +- [ ] Subsystem verification includes all mandatory sections (Verification Strategy, Test Environment, + Acceptance Criteria, Test Scenarios, Requirements Coverage) +- [ ] Unit verification includes all mandatory sections (Verification Approach, Test Environment, + Acceptance Criteria, Test Scenarios, Requirements Coverage) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] Every requirement is mapped to at least one named test scenario +- [ ] `docs/verification/ots.md` and `docs/verification/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/verification/shared.md` and `docs/verification/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index 4942746..60f37d9 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -1,55 +1,43 @@ --- -# Markdown Linting Standards +# Markdown Linting Configuration # -# PURPOSE: -# - Maintain professional technical documentation standards -# - Ensure consistent formatting for readability and maintenance -# - Support automated documentation generation and publishing -# -# DO NOT MODIFY: These rules represent coding standards -# - If files fail linting, fix the files to meet these standards -# - Do not relax rules to accommodate existing non-compliant files -# - Consistency across repositories is critical for documentation quality - -# Disable the banner message (e.g., version info) on stdout -noBanner: true +# DO NOT MODIFY: These rules represent coding standards. -# Disable the progress indicator on stdout -noProgress: true +ignores: + - '**/.git/**' + - '**/node_modules/**' + - '**/.venv/**' + - '**/thirdparty/**' + - '**/third-party/**' + - '**/3rd-party/**' + - '**/generated/**' + - '**/.agent-logs/**' config: - # Enable all default rules - default: true - - # Require ATX-style headers (# Header) instead of Setext-style + # Require ATX-style headers MD003: style: atx + # Consistent unordered list markers + MD004: + style: dash + # Set consistent indentation for nested lists MD007: indent: 2 - # Allow longer lines for URLs and technical content - MD013: - line_length: 120 + # Line length - disabled (no limit enforced) + MD013: false # Allow multiple top-level headers per document MD025: false - # Allow inline HTML for enhanced documentation + # Allow inline HTML (XML comments used in templates) MD033: false - # Allow documents without top-level header (for fragments) + # Disable first-line-heading: this project uses the "fragment markdown" method where + # subsystem, unit, OTS, and shared-package files intentionally start with ## or ### + # (not #) because they are assembled by Pandoc into a single document via definition.yaml. + # In the assembled document the heading hierarchy is correct; markdownlint sees fragments + # in isolation and would otherwise raise false positives on every sub-document file. MD041: false - -# Exclude common build artifacts, dependencies, and vendored third-party code -ignores: - - "**/.git/**" - - "**/node_modules/**" - - "**/.venv/**" - - "**/thirdparty/**" - - "**/third-party/**" - - "**/3rd-party/**" - - "**/generated/**" - - "**/AGENT_REPORT_*.md" - - "**/.agent-logs/**" diff --git a/.reviewmark.yaml b/.reviewmark.yaml index c0d7a11..b433354 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -59,7 +59,10 @@ reviews: - "docs/reqstream/review-mark.yaml" # system requirements - "docs/reqstream/review-mark/platform-requirements.yaml" # platform requirements - "docs/design/introduction.md" # design introduction + - "docs/design/review-mark.md" # system-level design document - "docs/design/review-mark/**/*.md" # system design documents + - "docs/design/ots.md" # OTS integration strategy + - "docs/design/ots/**/*.md" # OTS integration design documents - "docs/verification/introduction.md" # verification introduction - "docs/verification/ots.md" # OTS verification overview @@ -85,7 +88,7 @@ reviews: - id: ReviewMark-Cli title: Review that ReviewMark Cli Satisfies Subsystem Requirements paths: - - "docs/reqstream/review-mark/cli/cli.yaml" # subsystem requirements + - "docs/reqstream/review-mark/cli.yaml" # subsystem requirements - "docs/design/review-mark/cli.md" # Cli subsystem design - "docs/verification/review-mark/cli.md" # Cli subsystem verification - "test/**/Cli/CliTests.cs" # Cli subsystem tests @@ -103,7 +106,7 @@ reviews: - id: ReviewMark-Configuration title: Review that ReviewMark Configuration Satisfies Subsystem Requirements paths: - - "docs/reqstream/review-mark/configuration/configuration.yaml" # subsystem requirements + - "docs/reqstream/review-mark/configuration.yaml" # subsystem requirements - "docs/design/review-mark/configuration.md" # Configuration subsystem design - "docs/verification/review-mark/configuration.md" # Configuration subsystem verification - "test/**/Configuration/ConfigurationTests.cs" # Configuration subsystem tests @@ -130,7 +133,7 @@ reviews: - id: ReviewMark-Indexing title: Review that ReviewMark Indexing Satisfies Subsystem Requirements paths: - - "docs/reqstream/review-mark/indexing/indexing.yaml" # subsystem requirements + - "docs/reqstream/review-mark/indexing.yaml" # subsystem requirements - "docs/design/review-mark/indexing.md" # Indexing subsystem design - "docs/verification/review-mark/indexing.md" # Indexing subsystem verification - "test/**/Indexing/IndexingTests.cs" # Indexing subsystem tests @@ -157,7 +160,7 @@ reviews: - id: ReviewMark-SelfTest title: Review that ReviewMark SelfTest Satisfies Subsystem Requirements paths: - - "docs/reqstream/review-mark/self-test/self-test.yaml" # subsystem requirements + - "docs/reqstream/review-mark/self-test.yaml" # subsystem requirements - "docs/design/review-mark/self-test.md" # SelfTest subsystem design - "docs/verification/review-mark/self-test.md" # SelfTest subsystem verification - "test/**/SelfTest/SelfTestTests.cs" # SelfTest subsystem tests @@ -178,18 +181,42 @@ reviews: - "docs/reqstream/ots/buildmark.yaml" - "docs/verification/ots/buildmark.md" + - id: OTS-DemaConsultingTestResults + title: Review that DemaConsulting.TestResults Provides Required Functionality + paths: + - "docs/reqstream/ots/dema-consulting-test-results.yaml" + - "docs/design/ots/dema-consulting-test-results.md" + - "docs/verification/ots/dema-consulting-test-results.md" + - "test/OtsSoftwareTests/DemaConsultingTestResultsTests.cs" + - id: OTS-FileAssert title: Review that FileAssert Provides Required Functionality paths: - "docs/reqstream/ots/fileassert.yaml" - "docs/verification/ots/fileassert.md" + - id: OTS-FileSystemGlobbing + title: Review that Microsoft.Extensions.FileSystemGlobbing Provides Required Functionality + paths: + - "docs/reqstream/ots/microsoft-extensions-file-system-globbing.yaml" + - "docs/design/ots/microsoft-extensions-file-system-globbing.md" + - "docs/verification/ots/microsoft-extensions-file-system-globbing.md" + - "test/OtsSoftwareTests/MicrosoftExtensionsFileSystemGlobbingTests.cs" + - id: OTS-Pandoc title: Review that Pandoc Provides Required Functionality paths: - "docs/reqstream/ots/pandoc.yaml" - "docs/verification/ots/pandoc.md" + - id: OTS-PDFsharp + title: Review that PDFsharp Provides Required Functionality + paths: + - "docs/reqstream/ots/pdfsharp.yaml" + - "docs/design/ots/pdfsharp.md" + - "docs/verification/ots/pdfsharp.md" + - "test/OtsSoftwareTests/PDFsharpTests.cs" + - id: OTS-ReqStream title: Review that ReqStream Provides Required Functionality paths: @@ -231,3 +258,11 @@ reviews: paths: - "docs/reqstream/ots/xunit.yaml" - "docs/verification/ots/xunit.md" + + - id: OTS-YamlDotNet + title: Review that YamlDotNet Provides Required Functionality + paths: + - "docs/reqstream/ots/yamldotnet.yaml" + - "docs/design/ots/yamldotnet.md" + - "docs/verification/ots/yamldotnet.md" + - "test/OtsSoftwareTests/YamlDotNetTests.cs" diff --git a/.yamllint.yaml b/.yamllint.yaml index 79c3aee..1cde40e 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -1,19 +1,11 @@ --- # YAML Linting Standards # -# PURPOSE: -# - Maintain consistent code quality and readability standards -# - Support CI/CD workflows with reliable YAML parsing -# - Ensure professional documentation and configuration files -# -# DO NOT MODIFY: These rules represent coding standards -# - If files fail linting, fix the files to meet these standards -# - Do not relax rules to accommodate existing non-compliant files -# - Consistency across repositories is critical for maintainability +# DO NOT MODIFY: These rules represent coding standards. +# If files fail linting, fix the files to meet these standards. extends: default -# Exclude common build artifacts, dependencies, and vendored third-party code ignore: | **/.git/** **/node_modules/** @@ -27,12 +19,11 @@ ignore: | rules: # Allow 'on:' in GitHub Actions workflows (not a boolean value) truthy: - allowed-values: ['true', 'false', 'on', 'off'] + allowed-values: ['true', 'false'] + check-keys: false - # Allow longer lines for URLs and complex expressions - line-length: - max: 120 - level: error + # Disable line-length rule + line-length: disable # Ensure proper indentation indentation: diff --git a/AGENTS.md b/AGENTS.md index 8251c1e..ddb1e50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,8 @@ # Project Overview -- **name**: ReviewMark +- **project-name**: ReviewMark +- **organization**: DEMA Consulting +- **project-tagline**: Automated file-review evidence management - **description**: DEMA Consulting command-line tool for automated file-review evidence management in regulated environments - **languages**: C#, Markdown, YAML, PowerShell - **technologies**: .NET 8/9/10, xUnit, ReqStream, ReviewMark, Pandoc @@ -22,9 +24,23 @@ ├── src/ │ └── DemaConsulting.ReviewMark/ └── test/ - └── DemaConsulting.ReviewMark.Tests/ + ├── DemaConsulting.ReviewMark.Tests/ + └── OtsSoftwareTests/ ``` +# Language and Spelling (ALL Agents) + +Always use **US English** spelling in all output (code, comments, documentation, +commit messages, and reports). + +# Reference Template + +This repository follows a reference template for structure and file conventions. + +- **Template URL**: `https://github.com/demaconsulting/Agents/raw/refs/heads/template` +- **Repository map**: `{template-url}/repository-map.md` +- **Template files**: `{template-url}/{file-path}` for files described in the map + # Codebase Navigation (ALL Agents) When working with source code, design, or requirements artifacts, read @@ -53,17 +69,15 @@ before searching the filesystem. Before performing any work, agents must read and apply the relevant standards from `.github/standards/`. Use this matrix to determine which to load: -| Work involves... | Load these standards | -|----------------------|------------------------------------------------------------------------------------| -| Any code | `coding-principles.md` | -| C# code | `coding-principles.md`, `csharp-language.md` | -| Any tests | `testing-principles.md` | -| C# tests | `testing-principles.md`, `csharp-testing.md` | -| Requirements | `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` | -| Design docs | `software-items.md`, `design-documentation.md`, `technical-documentation.md` | -| Verification docs | `software-items.md`, `verification-documentation.md`, `technical-documentation.md` | -| Review configuration | `software-items.md`, `reviewmark-usage.md` | -| Any documentation | `technical-documentation.md` | +- **Any code**: `coding-principles.md` +- **C# code**: `coding-principles.md`, `csharp-language.md` +- **Any tests**: `testing-principles.md` +- **C# tests**: `testing-principles.md`, `csharp-testing.md` +- **Requirements**: `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` +- **Design docs**: `software-items.md`, `design-documentation.md`, `technical-documentation.md` +- **Verification docs**: `software-items.md`, `verification-documentation.md`, `technical-documentation.md` +- **Review configuration**: `software-items.md`, `reviewmark-usage.md` +- **Any documentation**: `technical-documentation.md` Load only the standards relevant to your specific task scope. @@ -78,6 +92,7 @@ Delegate to specialized agents only for specific scenarios: - **Formal feature implementation** (complex, multi-step) → Call the implementation agent - **Formal bug resolution** (complex debugging, systematic fixes) → Call the implementation agent - **Formal reviews** (compliance verification, detailed analysis) → Call the formal-review agent +- **Structural audit**: (repository layout vs. template) → Call the template-sync agent # Agent Reporting (Specialized Agents Must Follow) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2bf173c..1ddff0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ # Contributing to ReviewMark -Thank you for your interest in contributing to ReviewMark! We welcome contributions from the community and appreciate -your help in making this project better. +Thank you for your interest in contributing to ReviewMark! We welcome contributions from the +community and appreciate your help in making this project better. ## Code of Conduct -This project adheres to a [Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. -Please report unacceptable behavior through GitHub. +This project adheres to a [Code of Conduct][code-of-conduct]. By participating, you are expected +to uphold this code. Please report unacceptable behavior through GitHub. ## How to Contribute @@ -126,7 +126,7 @@ Note the spaces after `///` for proper indentation in summary blocks. ### Test Framework -We use MSTest v4 for unit and integration tests. +We use xUnit v3 for unit and integration tests. ### Test Naming Convention @@ -141,10 +141,11 @@ Examples: ### Writing Tests - Write tests that are clear and focused -- Use modern MSTest v4 assertions: - - `Assert.HasCount(expectedCount, collection)` - - `Assert.IsEmpty(collection)` +- Use xUnit assertions: + - `Assert.Equal(expected, actual)` + - `Assert.Contains(item, collection)` - `Assert.DoesNotContain(item, collection)` + - `Assert.Empty(collection)` - Always clean up resources (use `try/finally` for console redirection) - Link tests to requirements in `requirements.yaml` when applicable @@ -188,13 +189,15 @@ All markdown files must follow these rules (enforced by markdownlint): ### Spell Checking -All files are spell-checked using cspell. **Never** add a word to the `.cspell.yaml` word list in order to silence a -spell-checking failure. Doing so defeats the purpose of spell-checking and reduces the quality of the repository. +All files are spell-checked using cspell. **Never** add a word to the `.cspell.yaml` word list +in order to silence a spell-checking failure. Doing so defeats the purpose of spell-checking +and reduces the quality of the repository. - If cspell flags a word that is **misspelled**, fix the spelling in the source file. -- If cspell flags a word that is a **genuine technical term** (tool name, project identifier, etc.) and is spelled - correctly, raise a **proposal** (e.g. comment in a pull request) explaining why the word should be added. The - proposal must be reviewed and approved before the word is added to the list. +- If cspell flags a word that is a **genuine technical term** (tool name, project identifier, + etc.) and is spelled correctly, raise a **proposal** (e.g. comment in a pull request) explaining + why the word should be added. The proposal must be reviewed and approved before the word is + added to the list. ## Quality Checks @@ -224,7 +227,8 @@ pwsh ./lint.ps1 ### 3. Code Coverage -Maintain or improve code coverage. Use the `--collect "XPlat Code Coverage"` option when running tests. +Maintain or improve code coverage. Use the `--collect "XPlat Code Coverage"` option when +running tests. ## Commit Messages @@ -293,7 +297,8 @@ Releases are managed by project maintainers. The process includes: ## License -By contributing to ReviewMark, you agree that your contributions will be licensed under the MIT License. +By contributing to ReviewMark, you agree that your contributions will be licensed under the +MIT License. Thank you for contributing to ReviewMark! diff --git a/DemaConsulting.ReviewMark.slnx b/DemaConsulting.ReviewMark.slnx index 86616d5..523e26f 100644 --- a/DemaConsulting.ReviewMark.slnx +++ b/DemaConsulting.ReviewMark.slnx @@ -4,5 +4,6 @@ + diff --git a/README.md b/README.md index 7b65a78..2daeb72 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # ReviewMark + + [![GitHub forks][badge-forks]][link-forks] [![GitHub stars][badge-stars]][link-stars] [![GitHub contributors][badge-contributors]][link-contributors] @@ -9,7 +12,12 @@ [![Security][badge-security]][link-security] [![NuGet][badge-nuget]][link-nuget] -DEMA Consulting tool for automated file-review evidence management in regulated environments. +## Overview + +DEMA Consulting command-line tool for automated file-review evidence management in regulated +environments. ReviewMark generates cryptographic fingerprints of source files and matches them +against a catalogue of review evidence, producing a compliant review plan and review report on +every CI/CD run. ## Features @@ -17,7 +25,7 @@ DEMA Consulting tool for automated file-review evidence management in regulated - 📂 **Evidence Querying** - Queries URL or file-share evidence stores via an `index.json` catalogue - 📋 **Coverage Reporting** - Review plan shows which files are covered and flags uncovered files - 📊 **Status Reporting** - Review report shows whether each review-set is Current, Stale, Missing, or Failed -- 🔍 **Review Elaboration** - `--elaborate` prints the ID, fingerprint, and file list for a review set +- 🔍 **Review Elaboration** - `--elaborate` prints the ID, title, fingerprint, and file list for a review set - 🔎 **Configuration Linting** - `--lint` validates the definition file and reports all structural and semantic issues - 🚦 **Enforcement** - `--enforce` exits non-zero if any review-set is stale, missing, or failed, or any file is uncovered - 🔄 **Re-indexing** - `--index` scans PDF evidence files and writes an up-to-date `index.json` @@ -80,14 +88,19 @@ evidence indexing, and compliance report formats. ## Installation -Install the tool globally using the .NET CLI: - ```bash dotnet tool install -g DemaConsulting.ReviewMark ``` ## Usage +```bash +# Generate review plan and report, and enforce compliance (typical CI/CD usage) +reviewmark --plan docs/review/review-plan.md --report docs/review/review-report.md --enforce +``` + +Additional usage examples: + ```bash # Display version reviewmark --version @@ -109,8 +122,21 @@ reviewmark --lint --definition path/to/definition.yaml # Silent mode with logging reviewmark --silent --log output.log + +# Generate review plan and report without enforcement +reviewmark --plan docs/review/review-plan.md --report docs/review/review-report.md + +# Index PDF evidence files matching a glob pattern +reviewmark --dir \\reviews.example.com\evidence\ --index "**/*.pdf" + +# Index evidence organized across multiple year sub-directories +reviewmark --dir \\reviews.example.com\evidence\ --index "2025/**/*.pdf" --index "2026/**/*.pdf" ``` +> **Note**: The `\\server\share\` path syntax above is Windows UNC notation. On Linux and macOS, +> mount the network share first (for example via NFS or CIFS) and use the resulting mount point +> instead, e.g. `--dir /mnt/reviews/evidence/`. + ## Command-Line Options | Option | Description | @@ -184,6 +210,12 @@ See the [User Guide][link-guide] for more details on the self-validation tests. On validation failure the tool will exit with a non-zero exit code. +## Building + +```pwsh +pwsh ./build.ps1 +``` + ## Documentation Generated documentation includes: @@ -195,12 +227,27 @@ Generated documentation includes: - **Requirements Justifications**: Detailed requirement rationale - **Trace Matrix**: Requirements to test traceability +## User Guide + +The ReviewMark User Guide is available on the +[ReviewMark releases page](https://github.com/demaconsulting/ReviewMark/releases). + +## Contributing + +See [CONTRIBUTING.md](https://github.com/demaconsulting/ReviewMark/blob/main/CONTRIBUTING.md) for guidelines. + ## License -Copyright (c) DEMA Consulting. Licensed under the MIT License. See [LICENSE][link-license] for details. +Copyright (c) DEMA Consulting. Licensed under the MIT License. +See [LICENSE](https://github.com/demaconsulting/ReviewMark/blob/main/LICENSE) for details. By contributing to this project, you agree that your contributions will be licensed under the MIT License. +## Support + +- [Report a bug or request a feature](https://github.com/demaconsulting/ReviewMark/issues) +- [Ask a question or start a discussion](https://github.com/demaconsulting/ReviewMark/discussions) + [badge-forks]: https://img.shields.io/github/forks/demaconsulting/ReviewMark?style=plastic [badge-stars]: https://img.shields.io/github/stars/demaconsulting/ReviewMark?style=plastic diff --git a/SECURITY.md b/SECURITY.md index 487bf7e..176d2f3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,8 +11,8 @@ We release patches for security vulnerabilities in the following versions: ## Reporting a Vulnerability -We take the security of ReviewMark seriously. If you believe you have found a -security vulnerability, please report it to us as described below. +We take the security of ReviewMark seriously. If you believe you have found a security +vulnerability, please report it to us as described below. ### How to Report @@ -20,12 +20,13 @@ security vulnerability, please report it to us as described below. Instead, please report them using one of the following methods: -- **Preferred**: [GitHub Security Advisories][security-advisories] - Use the private vulnerability reporting feature +- **Preferred**: [GitHub Security Advisories][security-advisories] - Use the private vulnerability + reporting feature - **Alternative**: Contact the project maintainers directly through GitHub Please include the following information in your report: -- **Type of vulnerability** (e.g., SQL injection, cross-site scripting, etc.) +- **Type of vulnerability** (e.g., path traversal, arbitrary code execution, etc.) - **Full path** of source file(s) related to the vulnerability - **Location** of the affected source code (tag/branch/commit or direct URL) - **Step-by-step instructions** to reproduce the issue @@ -60,7 +61,7 @@ Security updates will be released as: ## Security Best Practices -When using ReviewMark, we recommend following these security best practices: +When using ReviewMark, we recommend following these security best practices. ### Input Validation @@ -99,7 +100,8 @@ When we receive a security bug report, we will: 3. Prepare fixes for all supported versions 4. Release patches as soon as possible -We will credit security researchers who report vulnerabilities responsibly. If you would like to be credited: +We will credit security researchers who report vulnerabilities responsibly. If you would like +to be credited: - Provide your name or pseudonym - Optionally provide a link to your website or GitHub profile @@ -121,8 +123,8 @@ dotnet list package --vulnerable ## Contact -For security concerns, please use [GitHub Security Advisories][security-advisories] or contact the project -maintainers directly through GitHub. +For security concerns, please use [GitHub Security Advisories][security-advisories] or contact +the project maintainers directly through GitHub. For general bugs and feature requests, please use [GitHub Issues][issues]. diff --git a/docs/build_notes/definition.yaml b/docs/build_notes/definition.yaml index ba1360b..429e18d 100644 --- a/docs/build_notes/definition.yaml +++ b/docs/build_notes/definition.yaml @@ -5,8 +5,8 @@ resource-path: input-files: - docs/build_notes/title.txt - docs/build_notes/introduction.md - - docs/build_notes/generated/build_notes.md - - docs/build_notes/generated/versions.md + - docs/build_notes/generated/build_notes.md # Generated by BuildMark (git history, release notes) + - docs/build_notes/generated/versions.md # Generated by VersionMark (tool versions) template: template.html table-of-contents: true number-sections: true diff --git a/docs/build_notes/introduction.md b/docs/build_notes/introduction.md index 75fb30c..e3ec06a 100644 --- a/docs/build_notes/introduction.md +++ b/docs/build_notes/introduction.md @@ -1,33 +1,22 @@ # Introduction -This document contains the build notes for the ReviewMark project. +This document contains the build notes for ReviewMark. ## Purpose -This report serves as a comprehensive record of changes and bug fixes for this -release of ReviewMark. It provides transparency about what has changed since the -previous version and helps users understand the improvements and fixes included -in this build. +This document provides a record of the changes, new features, and bug fixes included in this +release of ReviewMark. It also records the versions of all tools used in the build pipeline, +providing traceability between the software artifacts and the environment that produced them. ## Scope This build notes report covers: -- Version information and commit details -- Changes and new features implemented +- Version information and commit details for this release +- Changes and new features implemented since the previous version - Bugs fixed in this release +- Versions of all tools used in the build and compliance pipeline -## Generation Source +## References -This report is automatically generated by the BuildMark tool, analyzing the -Git repository history and issue tracking information. It serves as evidence of -changes and improvements included in this release. - -## Audience - -This document is intended for: - -- Software developers working on ReviewMark -- Users evaluating what has changed in this release -- Project stakeholders tracking progress -- Contributors understanding recent changes +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) diff --git a/docs/build_notes/title.txt b/docs/build_notes/title.txt index 7e9fcb1..dde5f52 100644 --- a/docs/build_notes/title.txt +++ b/docs/build_notes/title.txt @@ -1,8 +1,8 @@ --- -title: ReviewMark -subtitle: Build Notes +title: ReviewMark Build Notes +subtitle: Automated file-review evidence management author: DEMA Consulting -description: Build notes for ReviewMark +description: Build Notes for ReviewMark lang: en-US keywords: - ReviewMark @@ -10,5 +10,5 @@ keywords: - Release Notes - C# - .NET - - Documentation + - Compliance --- diff --git a/docs/code_quality/introduction.md b/docs/code_quality/introduction.md index a35aa6e..e815603 100644 --- a/docs/code_quality/introduction.md +++ b/docs/code_quality/introduction.md @@ -1,35 +1,18 @@ # Introduction -This document contains the code quality analysis report for the ReviewMark project. +This document records the static analysis results for ReviewMark. ## Purpose -This report provides a comprehensive analysis of the ReviewMark codebase including quality gate status, -code issues, and security hot-spots. The analysis helps ensure code quality and security standards are -maintained throughout development. +To provide evidence that ReviewMark has been analyzed for code quality issues and that any findings +have been reviewed and resolved or accepted. ## Scope -This code quality report covers: +Covers static analysis of all source code in `src/` for ReviewMark. Test code is excluded from +static analysis requirements. -- Quality gate status and conditions -- Code issues categorized by type and severity -- Security hot-spots requiring review -- Security analysis results -- Technical debt assessment -- Code coverage and duplication metrics +## References -## Analysis Source - -This report contains quality analysis results captured at the time this version of ReviewMark was built. -It serves as evidence that the code maintains good quality standards and provides transparency about the -project's code health. The analysis includes results from various quality tools run during the build process. - -## Audience - -This document is intended for: - -- Software developers working on ReviewMark -- Quality assurance teams reviewing code quality -- Project stakeholders evaluating project health -- Contributors understanding quality standards +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) +- [SonarCloud analysis dashboard](https://sonarcloud.io/dashboard?id=demaconsulting_ReviewMark) diff --git a/docs/code_quality/title.txt b/docs/code_quality/title.txt index 5496f98..d71804d 100644 --- a/docs/code_quality/title.txt +++ b/docs/code_quality/title.txt @@ -1,13 +1,14 @@ --- -title: ReviewMark Code Quality Report -subtitle: Code Quality Analysis Report -author: DEMA Consulting -description: Code Quality Analysis Report for ReviewMark +title: "ReviewMark Code Quality Report" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +description: "Code Quality Report for ReviewMark" lang: en-US keywords: - - ReviewMark - Code Quality - - Analysis + - Static Analysis - .NET - - Documentation + - C# + - CodeQL + - SonarCloud --- diff --git a/docs/code_review_plan/definition.yaml b/docs/code_review_plan/definition.yaml index 56989bf..5b612b8 100644 --- a/docs/code_review_plan/definition.yaml +++ b/docs/code_review_plan/definition.yaml @@ -1,11 +1,9 @@ --- -resource-path: - - docs/code_review_plan - - docs/template +resource-path: [docs/code_review_plan, docs/template] input-files: - docs/code_review_plan/title.txt - docs/code_review_plan/introduction.md - - docs/code_review_plan/generated/plan.md + - docs/code_review_plan/generated/plan.md # Generated by ReviewMark (formal review plan) template: template.html table-of-contents: true number-sections: true diff --git a/docs/code_review_plan/introduction.md b/docs/code_review_plan/introduction.md index 807b8c4..33852b4 100644 --- a/docs/code_review_plan/introduction.md +++ b/docs/code_review_plan/introduction.md @@ -1,33 +1,23 @@ # Introduction -This document contains the review plan for the ReviewMark project. +This document defines the formal code review plan for ReviewMark. ## Purpose -This review plan provides a comprehensive overview of all files requiring formal review -in the ReviewMark project. It identifies which review-sets cover which -files and serves as evidence that every file requiring review is covered by at least -one named review-set. +To define review-sets and the evidence required to demonstrate that each software item has been +reviewed according to the project's compliance requirements. This plan is automatically generated +by the ReviewMark tool, analyzing the `.reviewmark.yaml` configuration and the review evidence +store, and serves as evidence that every file requiring review is covered by at least one named +review-set. ## Scope -This review plan covers: +This document covers all review-sets defined in `.reviewmark.yaml` for ReviewMark, including: - C# source code files requiring formal review - YAML configuration and requirements files requiring formal review - Mapping of reviewed files to named review-sets -## Generation Source +## References -This plan is automatically generated by the ReviewMark tool, analyzing the -`.reviewmark.yaml` configuration and the review evidence store. It serves as evidence -that every file requiring review is covered by a current, valid review. - -## Audience - -This document is intended for: - -- Software developers working on ReviewMark -- Quality assurance teams validating review coverage -- Project stakeholders reviewing compliance status -- Auditors verifying that all required files have been reviewed +- [DemaConsulting.ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) diff --git a/docs/code_review_plan/title.txt b/docs/code_review_plan/title.txt index bfe74cd..4c20a61 100644 --- a/docs/code_review_plan/title.txt +++ b/docs/code_review_plan/title.txt @@ -1,13 +1,13 @@ --- -title: ReviewMark Review Plan -subtitle: File Review Plan for ReviewMark -author: DEMA Consulting -description: File Review Plan for ReviewMark +title: "DemaConsulting.ReviewMark Code Review Plan" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +description: "Code Review Plan for DemaConsulting.ReviewMark" lang: en-US keywords: + - Code Review - ReviewMark - Review Plan - - File Reviews - .NET - Tool --- diff --git a/docs/code_review_report/definition.yaml b/docs/code_review_report/definition.yaml index b238d43..b097a50 100644 --- a/docs/code_review_report/definition.yaml +++ b/docs/code_review_report/definition.yaml @@ -5,7 +5,7 @@ resource-path: input-files: - docs/code_review_report/title.txt - docs/code_review_report/introduction.md - - docs/code_review_report/generated/report.md + - docs/code_review_report/generated/report.md # Generated by ReviewMark (completed review records) template: template.html table-of-contents: true number-sections: true diff --git a/docs/code_review_report/introduction.md b/docs/code_review_report/introduction.md index a669629..f761a05 100644 --- a/docs/code_review_report/introduction.md +++ b/docs/code_review_report/introduction.md @@ -1,32 +1,23 @@ # Introduction -This document contains the review report for the ReviewMark project. +This document records the completed formal code reviews for ReviewMark. ## Purpose -This review report provides evidence that each review-set is current — the review -evidence matches the current file fingerprints. It confirms that all formal reviews -conducted for ReviewMark remain valid for the current state of the reviewed files. +To provide compliance evidence that all required review-sets have been reviewed and approved +according to the project's review plan. This report confirms that all formal reviews conducted +for ReviewMark remain valid for the current state of the reviewed files by verifying that +review evidence matches current file fingerprints. ## Scope -This review report covers: +This document covers all completed reviews for review-sets defined in `.reviewmark.yaml`, +including: - Current review-set status (current, stale, failed, or missing) - File fingerprints and review evidence matching - Review coverage verification -## Generation Source +## References -This report is automatically generated by the ReviewMark tool, comparing the current -file fingerprints against the review evidence store. It serves as evidence that all -review-sets are current and no reviewed file has changed since its review was conducted. - -## Audience - -This document is intended for: - -- Software developers working on ReviewMark -- Quality assurance teams validating review currency -- Project stakeholders reviewing compliance status -- Auditors verifying that all reviews remain valid for the current release +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) diff --git a/docs/code_review_report/title.txt b/docs/code_review_report/title.txt index 3a8f95b..c17e7e2 100644 --- a/docs/code_review_report/title.txt +++ b/docs/code_review_report/title.txt @@ -1,13 +1,13 @@ --- -title: ReviewMark Review Report -subtitle: File Review Report for ReviewMark -author: DEMA Consulting -description: File Review Report for ReviewMark +title: "ReviewMark Code Review Report" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +description: "Code Review Report for ReviewMark" lang: en-US keywords: + - Code Review - ReviewMark - Review Report - - File Reviews - .NET - Tool --- diff --git a/docs/design/definition.yaml b/docs/design/definition.yaml index 805bcdb..e9f0aa8 100644 --- a/docs/design/definition.yaml +++ b/docs/design/definition.yaml @@ -1,12 +1,5 @@ --- -resource-path: - - docs/design - - docs/design/review-mark - - docs/design/review-mark/cli - - docs/design/review-mark/configuration - - docs/design/review-mark/indexing - - docs/design/review-mark/self-test - - docs/template +resource-path: [docs/design, docs/template] input-files: - docs/design/title.txt @@ -23,6 +16,11 @@ input-files: - docs/design/review-mark/indexing/path-helpers.md - docs/design/review-mark/self-test.md - docs/design/review-mark/self-test/validation.md + - docs/design/ots.md + - docs/design/ots/yamldotnet.md + - docs/design/ots/pdfsharp.md + - docs/design/ots/dema-consulting-test-results.md + - docs/design/ots/microsoft-extensions-file-system-globbing.md template: template.html table-of-contents: true diff --git a/docs/design/introduction.md b/docs/design/introduction.md index 880fb3f..503b5c6 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -47,6 +47,12 @@ ReviewMark (System) │ └── PathHelpers (Unit) └── SelfTest (Subsystem) └── Validation (Unit) + +OTS Software Items (integration design in docs/design/ots/): +├── YamlDotNet (OTS) — YAML deserialization for the Configuration subsystem +├── PDFsharp (OTS) — PDF metadata reading for the Indexing subsystem +├── DemaConsulting.TestResults (OTS) — TRX/JUnit serialization for the SelfTest subsystem +└── Microsoft.Extensions.FileSystemGlobbing (OTS) — glob-pattern file matching for GlobMatcher ``` Each unit is described in detail in its own companion design document, linked from the folder layout below. @@ -79,22 +85,28 @@ The design documentation follows the same hierarchy under `docs/design/review-ma docs/design/ ├── introduction.md — this document (software structure and folder layout) ├── review-mark.md — system-level design -└── review-mark/ - ├── program.md — Program unit design - ├── cli.md — Cli subsystem overview - ├── cli/ - │ └── context.md — Context unit design - ├── configuration.md — Configuration subsystem overview - ├── configuration/ - │ ├── review-mark-configuration.md — ReviewMarkConfiguration unit design - │ └── glob-matcher.md — GlobMatcher unit design - ├── indexing.md — Indexing subsystem overview - ├── indexing/ - │ ├── review-index.md — ReviewIndex unit design - │ └── path-helpers.md — PathHelpers unit design - ├── self-test.md — SelfTest subsystem overview - └── self-test/ - └── validation.md — Validation unit design +├── review-mark/ +│ ├── program.md — Program unit design +│ ├── cli.md — Cli subsystem overview +│ ├── cli/ +│ │ └── context.md — Context unit design +│ ├── configuration.md — Configuration subsystem overview +│ ├── configuration/ +│ │ ├── review-mark-configuration.md — ReviewMarkConfiguration unit design +│ │ └── glob-matcher.md — GlobMatcher unit design +│ ├── indexing.md — Indexing subsystem overview +│ ├── indexing/ +│ │ ├── review-index.md — ReviewIndex unit design +│ │ └── path-helpers.md — PathHelpers unit design +│ ├── self-test.md — SelfTest subsystem overview +│ └── self-test/ +│ └── validation.md — Validation unit design +├── ots.md — OTS integration strategy overview +└── ots/ + ├── yamldotnet.md — YamlDotNet integration design + ├── pdfsharp.md — PDFsharp integration design + ├── dema-consulting-test-results.md — DemaConsulting.TestResults integration design + └── microsoft-extensions-file-system-globbing.md — FileSystemGlobbing integration design ``` ## Companion Artifact Structure @@ -108,14 +120,14 @@ The list below shows how each artifact type maps to the same software structure: - **Program** — Req: `docs/reqstream/review-mark/program.yaml`, Design: `docs/design/review-mark/program.md`, Source: `src/.../Program.cs`, Tests: `test/.../ProgramTests.cs` -- **Cli subsystem** — Req: `docs/reqstream/review-mark/cli/cli.yaml`, +- **Cli subsystem** — Req: `docs/reqstream/review-mark/cli.yaml`, Design: `docs/design/review-mark/cli.md`, Source: `src/.../Cli/` - **Context** — Req: `docs/reqstream/review-mark/cli/context.yaml`, Design: `docs/design/review-mark/cli/context.md`, Source: `src/.../Cli/Context.cs`, Tests: `test/.../ContextTests.cs` - **Configuration subsystem** — - Req: `docs/reqstream/review-mark/configuration/configuration.yaml`, + Req: `docs/reqstream/review-mark/configuration.yaml`, Design: `docs/design/review-mark/configuration.md`, Source: `src/.../Configuration/` - **ReviewMarkConfiguration** — @@ -127,7 +139,7 @@ The list below shows how each artifact type maps to the same software structure: Design: `docs/design/review-mark/configuration/glob-matcher.md`, Source: `src/.../Configuration/GlobMatcher.cs`, Tests: `test/.../GlobMatcherTests.cs` -- **Indexing subsystem** — Req: `docs/reqstream/review-mark/indexing/indexing.yaml`, +- **Indexing subsystem** — Req: `docs/reqstream/review-mark/indexing.yaml`, Design: `docs/design/review-mark/indexing.md`, Source: `src/.../Indexing/` - **ReviewIndex** — Req: `docs/reqstream/review-mark/indexing/review-index.yaml`, @@ -136,27 +148,21 @@ The list below shows how each artifact type maps to the same software structure: - **PathHelpers** — Req: `docs/reqstream/review-mark/indexing/path-helpers.yaml`, Design: `docs/design/review-mark/indexing/path-helpers.md`, Source: `src/.../Indexing/PathHelpers.cs`, Tests: `test/.../IndexingTests.cs` -- **SelfTest subsystem** — Req: `docs/reqstream/review-mark/self-test/self-test.yaml`, +- **SelfTest subsystem** — Req: `docs/reqstream/review-mark/self-test.yaml`, Design: `docs/design/review-mark/self-test.md`, Source: `src/.../SelfTest/` - **Validation** — Req: `docs/reqstream/review-mark/self-test/validation.yaml`, Design: `docs/design/review-mark/self-test/validation.md`, Source: `src/.../SelfTest/Validation.cs`, Tests: `test/.../ValidationTests.cs` +- **YamlDotNet (OTS)** — Design: `docs/design/ots/yamldotnet.md` +- **PDFsharp (OTS)** — Design: `docs/design/ots/pdfsharp.md` +- **DemaConsulting.TestResults (OTS)** — Design: `docs/design/ots/dema-consulting-test-results.md` +- **Microsoft.Extensions.FileSystemGlobbing (OTS)** — + Design: `docs/design/ots/microsoft-extensions-file-system-globbing.md` Requirement IDs referenced in the design chapters match identifiers in the ReqStream YAML files under `docs/reqstream/`. -## Document Conventions - -Throughout this document: - -- Class names, method names, property names, and file names appear in `monospace` font. -- The word **shall** denotes a design constraint that the implementation must satisfy. -- Section headings within each unit chapter follow a consistent structure: overview, data model, - methods/algorithms, and interactions with other units. -- Text tables are used in preference to diagrams, which may not render in all PDF viewers. - ## References -- See *ReviewMark System Design* (`docs/design/review-mark.md`) for the system-level design. -- See the *ReviewMark User Guide* (`docs/user_guide/introduction.md`) for usage information. -- See the ReviewMark repository at `https://github.com/demaconsulting/ReviewMark`. +- [Continuous Compliance](https://github.com/demaconsulting/ContinuousCompliance) — methodology + framework for automated compliance evidence generation diff --git a/docs/design/ots.md b/docs/design/ots.md new file mode 100644 index 0000000..4aa2f34 --- /dev/null +++ b/docs/design/ots.md @@ -0,0 +1,43 @@ +# OTS Dependencies + +This section describes the overall integration strategy for the off-the-shelf (OTS) runtime +libraries used by ReviewMark. Each OTS item's specific integration design is documented in +`docs/design/ots/{name}.md`. + +## Selection Criteria + +OTS items are selected based on the following criteria: + +- **License compatibility**: only MIT-licensed packages are acceptable, consistent with + ReviewMark's own MIT license +- **Maturity and maintenance**: packages must be actively maintained with a track record of + stable releases and published changelogs +- **NuGet availability**: packages must be published on NuGet.org with reproducible builds +- **API appropriateness**: the package must provide a focused, well-documented API for the + required functionality without introducing unnecessary transitive dependencies + +## Version Management Policy + +- Version constraints are declared in the project file (`DemaConsulting.ReviewMark.csproj`) + and managed using Dependabot for patch and minor upgrades +- Major version upgrades require a review of vendor release notes and, if the integration + surface changes, an update to the relevant design and verification documents before merging +- No lock files are used; the project file constraints are the authoritative version policy + +## General Integration Approach + +- OTS items are used directly through their public APIs; no adapter or wrapper classes are + introduced because the integration surface is isolated to a single unit in each case +- Each OTS item is consumed by exactly one subsystem, keeping the integration surface narrow + and auditable +- Error handling at OTS boundaries follows standard .NET patterns: library exceptions are + caught at the subsystem boundary, reported via `Context.WriteError`, and converted to a + non-zero application exit code + +## Qualification Strategy + +- Integration tests in `test/DemaConsulting.ReviewMark.Tests/` exercise each OTS integration + surface and confirm the expected behavior +- The full test suite is executed on every CI run (`dotnet test` in `build.yaml`) +- Vendor release notes are reviewed before any OTS version upgrade; if a consumed feature + changes, the affected design and verification documents are updated before merging the upgrade diff --git a/docs/design/ots/dema-consulting-test-results.md b/docs/design/ots/dema-consulting-test-results.md new file mode 100644 index 0000000..cda5d4d --- /dev/null +++ b/docs/design/ots/dema-consulting-test-results.md @@ -0,0 +1,42 @@ +## DemaConsulting.TestResults + +DemaConsulting.TestResults is a DEMA Consulting library that provides a language-neutral test +results object model together with serializers to MSTest TRX and JUnit XML formats. It is used by +the SelfTest subsystem to produce portable self-validation output. + +### Purpose + +DemaConsulting.TestResults was chosen because it provides a compact, well-typed model for +accumulating test results and a ready-made TRX serializer whose output format is accepted directly +by Azure DevOps, GitHub Actions, and the ReqStream `--enforce` pipeline. Introducing a bespoke TRX +serializer was avoided to keep the SelfTest subsystem focused on test logic rather than +XML serialization concerns. + +### Features Used + +- **`DemaConsulting.TestResults.TestResults`**: top-level container holding the test run name and + a mutable list of `TestResult` records +- **`DemaConsulting.TestResults.TestResult`**: individual test-case record with `Name`, + `Outcome`, `StartTime`, `EndTime`, and `Duration` properties +- **`DemaConsulting.TestResults.TestOutcome`**: enumeration providing `Passed` and `Failed` values + used to record the per-test verdict +- **`DemaConsulting.TestResults.IO.TrxSerializer.Serialize(TestResults)`**: serializes the results + object to an MSTest TRX XML string for `.trx` output files +- **`DemaConsulting.TestResults.IO.JUnitSerializer.Serialize(TestResults)`**: serializes the + results object to a JUnit XML string for `.xml` output files + +### Integration Pattern + +`Validation.Run()` creates a single `TestResults` instance named `"ReviewMark Self-Validation"`. +Each test method in `Validation.cs` receives the shared `TestResults` collection. +`CreateTestResult(testName)` constructs a new `TestResult` and records the start timestamp. After +the test body executes, `FinalizeTestResult()` records either `TestOutcome.Passed` or +`TestOutcome.Failed`, computes the elapsed duration, and appends the result to the collection. When +`context.ResultsFile` is set, `WriteResultsFile()` selects `TrxSerializer` for `.trx` files or +`JUnitSerializer` for `.xml` files, serializes the collection to a string, creates the output +directory if it does not yet exist, and writes the file. Any file-write failure is reported via +`Context.WriteError` and results in exit code 1. + +### Version + +DemaConsulting.TestResults 1.7.0 is the required version. diff --git a/docs/design/ots/microsoft-extensions-file-system-globbing.md b/docs/design/ots/microsoft-extensions-file-system-globbing.md new file mode 100644 index 0000000..ee87b98 --- /dev/null +++ b/docs/design/ots/microsoft-extensions-file-system-globbing.md @@ -0,0 +1,38 @@ +## Microsoft.Extensions.FileSystemGlobbing + +Microsoft.Extensions.FileSystemGlobbing is a Microsoft runtime library that provides glob-pattern +file matching against the local file system. It is used by the `GlobMatcher` unit in the +Configuration subsystem. + +### Purpose + +Microsoft.Extensions.FileSystemGlobbing was chosen because it is the canonical Microsoft-authored +glob library for .NET, distributed as part of the `Microsoft.Extensions` package family. It +supports the `**` double-wildcard pattern required by `.reviewmark.yaml` configurations, handles +path-separator normalization across platforms, and integrates directly with the host file system +without additional abstraction. Using the Microsoft-provided implementation avoids duplicating +glob semantics in application code. + +### Features Used + +- **`Microsoft.Extensions.FileSystemGlobbing.Matcher`**: the core matching class; accepts one or + more glob patterns and resolves matching file paths against a base directory +- **`Matcher.AddInclude(string pattern)`**: registers a single glob pattern as an include rule on + the `Matcher` instance +- **`Matcher.GetResultsInFullPath(string directoryPath)`**: executes the configured patterns + against the specified directory and returns the full absolute paths of all matching files + +### Integration Pattern + +`GlobMatcher.GetMatchingFiles()` maintains a `HashSet` of currently matched paths and +processes the ordered list of include/exclude patterns one at a time. For each include pattern +(no `!` prefix) a new `Matcher` is created, `AddInclude` is called with the pattern, and +`GetResultsInFullPath(baseDirectory)` is called; each returned full path is converted to a +repository-relative path and added to the set. For each exclude pattern (prefixed with `!`) a +separate `Matcher` is created for the stripped pattern and the matching paths are removed from the +set. After all patterns are processed, paths are normalized to forward slashes and sorted using +`StringComparer.Ordinal` to produce a deterministic, platform-independent file list. + +### Version + +Microsoft.Extensions.FileSystemGlobbing 10.0.8 is the required version. diff --git a/docs/design/ots/pdfsharp.md b/docs/design/ots/pdfsharp.md new file mode 100644 index 0000000..a5977c3 --- /dev/null +++ b/docs/design/ots/pdfsharp.md @@ -0,0 +1,33 @@ +## PDFsharp + +PDFsharp is the .NET PDF creation and manipulation library used by the Indexing subsystem to read +metadata from review evidence PDF files. + +### Purpose + +PDFsharp was chosen for its ability to open existing PDF documents in a lightweight read-only +import mode without loading full content streams into memory. ReviewMark requires only the +Keywords metadata field from each evidence PDF; PDFsharp's `PdfDocumentOpenMode.Import` provides +exactly this access path with minimal overhead and no risk of accidental document modification. + +### Features Used + +- **`PdfReader.Open(string path, PdfDocumentOpenMode mode)`**: opens a PDF file at the given path + using the specified open mode; ReviewMark always passes `PdfDocumentOpenMode.Import` +- **`PdfDocumentOpenMode.Import`**: open mode that loads only the cross-reference table and + document information dictionary; content streams are not decoded and the document cannot be + modified +- **`PdfDocument.Info.Keywords`**: the Keywords field from the PDF document information + dictionary; ReviewMark encodes four required fields in this field using a structured + space-separated `name=value` format: `id`, `fingerprint`, `date`, and `result` + +### Integration Pattern + +`ReviewIndex.ProcessPdf()` calls `PdfReader.Open(fullPath, PdfDocumentOpenMode.Import)` inside a +`using` statement to ensure the document handle is disposed immediately after the Keywords field is +read. The `doc.Info.Keywords` string is then parsed to extract the `id`, `fingerprint`, `date`, and +`result` fields. If the Keywords field is absent (`null`) it is treated as an empty string and the +PDF is skipped with a warning. If the field is present but any of the four required fields are +absent or empty, the PDF is also skipped with a warning identifying the missing field, allowing the +index scan to accumulate all valid evidence files even when the scanned directory contains +non-review PDFs or partially-tagged PDFs. diff --git a/docs/design/ots/yamldotnet.md b/docs/design/ots/yamldotnet.md new file mode 100644 index 0000000..0779ae4 --- /dev/null +++ b/docs/design/ots/yamldotnet.md @@ -0,0 +1,40 @@ +## YamlDotNet + +YamlDotNet is the YAML parsing and serialization library for .NET used by the Configuration +subsystem to deserialize `.reviewmark.yaml` configuration files. + +### Purpose + +YamlDotNet was chosen because it is the de-facto standard .NET YAML library with broad ecosystem +adoption, comprehensive YAML 1.1/1.2 support, and a stable public API. It provides the +deserialization path from raw YAML text to strongly-typed C# objects, removing the need to +hand-write a YAML parser for the configuration file format. No alternative .NET YAML library +offers equivalent ecosystem support or API maturity. + +### Features Used + +- **`DeserializerBuilder`**: fluent builder for constructing an `IDeserializer` instance; + configured with naming convention and node-type resolver settings before use +- **`NullNamingConvention`**: preserves YAML key names exactly as written, relying on + `YamlMember(Alias = "...")` attributes for all hyphenated or camelCase key mappings +- **`YamlMember(Alias = "...")` attribute**: maps hyphenated YAML key names (such as + `needs-review` and `evidence-source`) to their C# property counterparts +- **`IDeserializer.Deserialize(string)`**: strongly-typed deserialization from a YAML string + into a target C# model class +- **`YamlDotNet.Core.YamlException`**: exception type thrown on malformed or structurally invalid + YAML input; caught at the Configuration subsystem boundary + +### Integration Pattern + +`ReviewMarkConfigurationHelpers.DeserializeRaw()` constructs a `DeserializerBuilder` configured with +`NullNamingConvention` and `IgnoreUnmatchedProperties` to tolerate forward-compatible +configuration extensions. The `.reviewmark.yaml` content is read from disk as a string and passed +directly to `Deserialize()`. The deserialization models are private +`file sealed class` types declared inside `ReviewMarkConfiguration.cs` that mirror the raw YAML +structure and form no part of the public API. A `YamlException` from the deserialization step is +caught and reported as a descriptive error message via `Context.WriteError`, resulting in exit +code 1. + +### Version + +YamlDotNet 17.1.0 is the required version. diff --git a/docs/design/review-mark.md b/docs/design/review-mark.md index c7c02b9..495a43f 100644 --- a/docs/design/review-mark.md +++ b/docs/design/review-mark.md @@ -1,176 +1,183 @@ -# System Design +# ReviewMark + +ReviewMark is a .NET command-line tool for automated file-review evidence management in +regulated environments. It determines which files are subject to review, identifies the +review evidence that covers them, and generates Review Plan and Review Report compliance +documents. + +## Architecture + +ReviewMark is organized as a single system composed of four subsystems and one standalone +unit: + +| Item | Category | Responsibility | +| ---- | -------- | -------------- | +| Program | Unit | Process entry point; constructs the execution context and dispatches to subsystems | +| Cli | Subsystem | Command-line argument parsing and output channel ownership | +| Configuration | Subsystem | YAML configuration loading, validation, and review-set processing | +| Indexing | Subsystem | Review evidence loading, PDF scanning, and safe path utilities | +| SelfTest | Subsystem | Built-in self-validation test suite | + +There is no system-level source code beyond `Program.cs`; all processing logic resides +within the subsystems. `Program.Main()` creates a `Context` instance and delegates all +work through `Program.Run()`, which dispatches to the appropriate subsystem based on the +parsed command-line flags. + +```mermaid +flowchart TD + subgraph ReviewMark + Program + subgraph Cli + Context + end + subgraph Configuration + ReviewMarkConfiguration + GlobMatcher + end + subgraph Indexing + ReviewIndex + PathHelpers + end + subgraph SelfTest + Validation + end + end + Program --> Context + Program --> ReviewMarkConfiguration + Program --> ReviewIndex + Program --> Validation + ReviewMarkConfiguration --> GlobMatcher + ReviewIndex --> GlobMatcher + ReviewIndex --> PathHelpers +``` + +`Program.Run()` evaluates parsed flags in a fixed priority order: `--version` first, then +the application banner, then `--help`, then `--validate`, then `--lint`, and finally the +main tool logic. This ensures that diagnostic flags are always handled before +output-generating actions. Each operational mode is described below. + +**Review Plan and Report Generation**: When `--plan` and/or `--report` are supplied, +ReviewMark loads the definition file, resolves file lists, loads the evidence index, and +generates the requested documents. The `--enforce` flag can be combined with this mode to +exit non-zero when issues are detected. + +**Elaborate Mode (`--elaborate`)**: When `--elaborate ` is supplied, ReviewMark looks +up the named review-set and writes a Markdown elaboration to stdout containing the +review-set ID, title, current fingerprint, and the full sorted list of matched files. This +mode does not query the evidence store. + +**Lint Mode (`--lint`)**: When `--lint` is supplied, ReviewMark loads and validates the +definition file in a single pass, collecting all detectable structural and semantic issues. +The application banner is suppressed. Silence means the definition file is valid. + +**Validate Mode (`--validate`)**: When `--validate` is supplied, ReviewMark runs a +built-in self-test suite that exercises core tool behaviors and produces a pass/fail +summary, optionally writing results to a TRX or JUnit XML file via `--results`. + +**Version Mode (`--version`)**: Writes only the version string to stdout and exits. + +**Help Mode (`--help`)**: Writes the usage message listing all supported flags to stdout. -This section describes the high-level behavior of the ReviewMark system and the workflow -that connects its subsystems. - -## Overview - -ReviewMark automates the evidence-gathering step of software review processes used in -regulated environments. On each CI/CD run, it determines which files are subject to -review, identifies the review evidence that covers them, and generates two compliance -documents: a Review Plan and a Review Report. - -## Main Workflow - -The following steps describe the end-to-end processing flow. - -1. Parse CLI arguments -2. Load `.reviewmark.yaml` -3. Resolve file lists via glob patterns -4. Compute SHA-256 fingerprints -5. Load evidence index - - `none` — use an empty index (no evidence store configured) - - `fileshare` — load `index.json` from a local or network file path - - `url` — download `index.json` from an HTTP or HTTPS URL -6. Generate Review Plan and/or Review Report -7. If `--enforce` flag is set: - - If all review-sets are Current — return success - - Otherwise — return a non-zero exit code - -## Evidence Source Types - -ReviewMark supports three evidence source types, configured in `.reviewmark.yaml`: - -| Source Type | Description | -| ----------- | ----------- | -| `none` | No evidence store; all review-sets are treated as missing | -| `fileshare` | Evidence index loaded from a local or network file path | -| `url` | Evidence index loaded from an HTTP or HTTPS URL | - -## Output Documents - -### Review Plan - -The Review Plan lists every file that is subject to review and identifies which -review-sets provide coverage for each file. It is generated by the `--plan` flag -and written to a configurable output path. - -### Review Report - -The Review Report lists every review-set defined in the configuration, the current -fingerprint of its file-set, and the review status (Current, Stale, Missing, or Failed). -It is generated by the `--report` flag and written to a configurable output path. - -The statuses have the following meanings: - -- **Current** — Evidence exists for the current fingerprint and the recorded result is `pass`. -- **Stale** — Evidence exists, but it corresponds to an older fingerprint than the current one. -- **Missing** — No evidence exists for this review-set. -- **Failed** — Evidence exists for the current fingerprint, but the recorded result is not `pass`. - -## Enforcement - -When the `--enforce` flag is set, ReviewMark returns a non-zero exit code in either -of two situations: - -1. The Review Plan shows that one or more files matching the `needs-review` patterns - are not covered by any review-set. -2. The Review Report shows that any review-set has a status other than Current - (i.e., is Stale, Missing, or Failed). - -This allows CI/CD pipelines to fail builds when review coverage is incomplete, files -are uncovered, reviews are out of date, or review evidence has a failed result. - -## Index Management - -The `--index` flag causes ReviewMark to scan a directory for PDF evidence files and -write an `index.json` file suitable for use as a fileshare evidence source. This -supports workflows where review PDFs are stored alongside source code or on a -shared network location. - -## Operational Modes - -ReviewMark supports several distinct operational modes, each activated by a specific flag: - -### Review Plan and Report Generation - -The default operational mode. When `--plan` and/or `--report` are supplied, ReviewMark -loads the definition file, resolves file lists, and generates the requested documents. -The `--enforce` flag can be combined with this mode to fail the process when issues are -detected. - -### Elaborate Mode (`--elaborate`) - -When `--elaborate ` is supplied, ReviewMark loads the definition file, looks up the -named review-set, and writes a Markdown elaboration to stdout. The elaboration contains -the review-set ID, title, current fingerprint, and the full sorted list of files matched -by the review-set paths. This mode does not query the evidence store. When the supplied -`id` does not match any review-set, an error is written to stderr and the process exits -with a non-zero code. - -### Lint Mode (`--lint`) - -When `--lint` is supplied, ReviewMark loads and validates the definition file in a single -pass, collecting all detectable structural and semantic issues. The application banner is -suppressed so that only issue messages reach the console. - -- **Success (exit code 0)** — the definition file is valid; no output is produced. -- **Failure (exit code 1)** — one or more issues were found; only the issue messages are - printed, with no surrounding banner or summary text. - -Unlike normal operation, lint mode never queries the evidence store. - -### Validate Mode (`--validate`) - -When `--validate` is supplied, ReviewMark runs a built-in self-test suite that exercises -core tool behaviors and produces a pass/fail summary. Validation results can be written to -a TRX or JUnit XML file via `--results`. This mode is intended for tool qualification -in regulated environments. - -### Version Mode (`--version`) - -When `--version` is supplied, ReviewMark writes only the version string to stdout and -exits immediately. No banner or other output is produced. - -### Help Mode (`--help`) - -When `--help` is supplied, ReviewMark writes the usage message listing all supported flags -to stdout and exits. +## External Interfaces -## Command-Line Flags +**Command-Line Interface**: The sole external interface of the ReviewMark system. All +inputs are supplied as command-line arguments to the `reviewmark` executable; all outputs +are written to stdout, stderr, and optionally to files. There is no network listener, REST +API, or graphical interface. -The following flags are recognized at the system design level: +- *Type*: CLI (command-line interface) +- *Role*: Provider — the ReviewMark executable is invoked by users and CI/CD pipelines +- *Contract*: The following flags are recognized: | Flag | Description | | ---- | ----------- | | `--version` / `-v` | Display version string and exit | | `--help` / `-?` / `-h` | Display usage information and exit | -| `--silent` | Suppress all console output; the exit code still signals success or failure | +| `--silent` | Suppress all console output; exit code still signals success or failure | | `--lint` | Validate the definition file; print only issues; exit non-zero on failure | | `--validate` | Run built-in self-validation tests | -| `--results ` | Write validation results to a TRX or JUnit XML file (used with `--validate`) | +| `--results ` | Write validation results to a TRX or JUnit XML file (with `--validate`) | | `--log ` | Write all output to a persistent log file in addition to stdout | -| `--depth <#>` | Default Markdown heading depth (1–5) for all generated documents; default is 1 | -| `--dir ` | Set the working directory used for default paths and glob scanning | +| `--depth <#>` | Default Markdown heading depth (1–5) for all generated documents; default 1 | +| `--dir ` | Set the working directory for default paths and glob scanning | | `--definition ` | Override the default `.reviewmark.yaml` configuration file path | -| `--plan ` | Generate a Markdown review-plan document listing all review sets and their current status | -| `--plan-depth <#>` | Override the Markdown heading depth for the plan document (default: `--depth` value) | +| `--plan ` | Generate a Markdown review-plan document listing all review sets | +| `--plan-depth <#>` | Override the Markdown heading depth for the plan document | | `--report ` | Generate a Markdown review-report document summarizing review-set completion | -| `--report-depth <#>` | Override the Markdown heading depth for the report document (default: `--depth` value) | -| `--elaborate ` | Print a Markdown elaboration for the named review set (ID, title, fingerprint, and file list) | +| `--report-depth <#>` | Override the Markdown heading depth for the report document | +| `--elaborate ` | Print a Markdown elaboration for the named review set | | `--enforce` | Exit with code 1 if the plan has uncovered files or any review-set is non-current | -| `--index ` | Scan PDF evidence files matching the glob pattern and write an `index.json` file to `--dir` | - -## External Interfaces - -The command-line interface is the sole external interface of the ReviewMark system. -All inputs are supplied as command-line arguments to the `reviewmark` executable, and all -outputs are written to stdout, stderr, and optionally to files. There is no network -listener, no REST API, and no graphical interface. - -## Exit Codes and Error Handling - -| Exit Code | Meaning | -| --------- | ------- | -| `0` | Success — all requested operations completed without errors or enforcement failures | -| `1` | Failure — an error occurred or enforcement detected review issues | - -Unrecognized or malformed command-line arguments cause the argument parser to throw an -`ArgumentException`. `Program.Main` catches this exception, writes a descriptive error -message to `stderr`, and returns exit code 1. The process never exits silently on an -argument error. - -Expected operational errors (e.g., unreadable definition file, unknown review-set ID) -are reported as error messages to stderr and result in exit code 1. Unexpected -exceptions are also written to stderr and re-thrown so that the host environment -generates an event log entry. +| `--index ` | Scan PDF evidence files matching the glob pattern and write `index.json`; repeatable | + +- *Constraints*: Exit code 0 always means success; exit code 1 always means failure or + enforcement violation. Unrecognized arguments cause the process to exit with code 1 and + write a descriptive error to stderr. + +## Dependencies + +ReviewMark depends on the following OTS software items: + +- **YamlDotNet**: YAML deserialization used by the Configuration subsystem to parse + `.reviewmark.yaml` — see *YamlDotNet Integration Design* +- **PDFsharp**: PDF document reading used by the Indexing subsystem to extract metadata + from evidence PDF files — see *PDFsharp Integration Design* +- **DemaConsulting.TestResults**: TRX and JUnit XML test-results serialization used by the + SelfTest subsystem — see *DemaConsulting.TestResults Integration Design* +- **Microsoft.Extensions.FileSystemGlobbing**: glob-pattern file matching used by the + `GlobMatcher` unit in the Configuration subsystem — see + *Microsoft.Extensions.FileSystemGlobbing Integration Design* + +No shared packages are used. + +## Risk Control Measures + +N/A — ReviewMark is a standalone command-line tool that runs in a single process with no +concurrent threads beyond those managed by the .NET runtime. There are no software items +that require runtime segregation for risk control. The evidence store is read-only during +normal operation; the only files written by the tool are the output documents and, when +`--index` is used, `index.json`. + +## Data Flow + +The high-level data flow through the ReviewMark system: + +1. **Input**: `string[] args` from the OS shell → parsed by `Context.Create()` into + structured flags and paths. +2. **Configuration**: `ReviewMarkConfiguration.Load()` reads `.reviewmark.yaml` from disk + and resolves glob patterns via `GlobMatcher` into sorted file lists. +3. **Evidence**: `ReviewIndex.Load()` reads or downloads the evidence index, populating an + in-memory lookup keyed by `(id, fingerprint)`. Three source types are supported: + `none` (empty index), `fileshare` (local or network file-path read), and `url` (HTTP or + HTTPS download, with optional Basic-auth credentials from environment variables). +4. **Processing**: `PublishReviewPlan()` or `PublishReviewReport()` combines the resolved + file lists with the evidence index to compute status and generate Markdown output. + The Review Plan lists every file subject to review and which review-sets cover it. + The Review Report lists every review-set with status: Current, Stale, Missing, or Failed. +5. **Output**: Generated Markdown is written to files on disk; status messages and errors + are written to stdout/stderr via `Context`. + +For the `--index` workflow, the flow is: glob patterns → `GlobMatcher` → sorted PDF paths +→ `ReviewIndex.Scan()` → PDF metadata extraction → serialized `index.json`. + +For `--elaborate`, the flow is: review-set ID → configuration lookup → Markdown +elaboration (ID, title, fingerprint, file list) written to stdout. + +When `--enforce` is set, a non-zero exit code is returned if the Review Plan shows any +uncovered files, or if the Review Report shows any review-set has a status other than +Current (i.e., is Stale, Missing, or Failed). + +## Design Constraints + +- **Platform**: targets .NET 8, .NET 9, and .NET 10; runs on Windows, macOS, and Linux +- **Distribution**: distributed as a .NET global tool via NuGet; no external runtime + dependencies beyond the .NET SDK +- **Output format**: all generated documents are Markdown; no binary output is produced + except `index.json` written by `--index` +- **No GUI**: the sole external interface is the command-line; no interactive or graphical + interface is provided +- **Deterministic fingerprints**: the SHA-256 fingerprint algorithm is content-based and + platform-independent so that the same files produce the same fingerprint on any operating + system; fingerprints are stable across renames or moves that keep the same set of file + contents, and change only when file content changes +- **Exit code semantics**: exit code 0 always means success; exit code 1 always means + failure or enforcement violation; no other exit codes are used diff --git a/docs/design/review-mark/cli.md b/docs/design/review-mark/cli.md index ca90a9b..bbb7e6a 100644 --- a/docs/design/review-mark/cli.md +++ b/docs/design/review-mark/cli.md @@ -1,70 +1,44 @@ -## Cli Subsystem - -### Overview +## Cli The Cli subsystem is responsible for parsing and owning the command-line interface of -ReviewMark. It exposes a single software unit — Context — that processes the raw -`string[] args` array into a structured set of properties consumed by the rest of the -tool. - -### Responsibilities - -- Parse all supported command-line flags and arguments into a typed `Context` object -- Validate that no unrecognized arguments are supplied -- Own the output channels (stdout and optional log file) and the process exit code -- Propagate the `--silent` flag to suppress non-error output +ReviewMark. It exposes a single unit — `Context` — that processes the raw `string[] args` +array into a structured set of typed properties consumed by the rest of the tool. -### Units - -- **Context** (`Cli/Context.cs`) — Command-line argument parser and I/O owner; - see the Context unit design documentation - -### Dependencies - -- **Cli** (Subsystem) — `Program.Main` creates a `Context` instance via `Context.Create(args)` - and passes it to `Program.Run(Context)`. `Context` is a passive data carrier; the Cli - subsystem has no dependency on Program. +### Overview -### Supported Flags +The Cli subsystem solves the problem of translating raw process arguments into a +well-typed configuration carrier available to all processing subsystems. Its boundary is +tightly scoped: it performs no file I/O beyond optionally opening a log file, and it calls +no other ReviewMark subsystem. -All flags are parsed by `Context.Create(string[] args)`. The following table lists every -supported flag, its type, aliases, and constraints: +The subsystem contains a single unit: **Context** (`Cli/Context.cs`) — the command-line +argument parser and I/O owner. See the *Context Design* for full details. -| Flag | Alias(es) | Type | Constraint | Description | -| ------ | --------- | ------ | ---------- | ----------- | -| `--version` | `-v` | bool | — | Display version string only | -| `--help` | `-?`, `-h` | bool | — | Display usage information | -| `--silent` | — | bool | — | Suppress all console output | -| `--validate` | — | bool | — | Run self-validation tests | -| `--lint` | — | bool | — | Validate the definition file and report issues | -| `--log ` | — | string | Valid file path | Write all output to a log file | -| `--results ` | `--result` | string | Valid file path | Write validation results (TRX or JUnit) | -| `--definition ` | — | string | Valid file path | Override default `.reviewmark.yaml` path | -| `--plan ` | — | string | Valid file path | Output path for the Review Plan Markdown document | -| `--depth <#>` | — | int | 1–5 | Default heading depth for all generated documents (default: 1) | -| `--plan-depth <#>` | — | int | 1–5 | Heading depth for the Review Plan (overrides `--depth`) | -| `--report ` | — | string | Valid file path | Output path for the Review Report Markdown document | -| `--report-depth <#>` | — | int | 1–5 | Heading depth for the Review Report (overrides `--depth`) | -| `--index ` | — | string (repeatable) | Glob expression | Scan PDF evidence files matching the glob path | -| `--dir ` | — | string | Valid directory path | Set the working directory for file operations | -| `--enforce` | — | bool | — | Exit with non-zero code if any review-set is not Current | -| `--elaborate ` | — | string | Non-empty review-set ID | Print a Markdown elaboration of the specified review set | +### Interfaces -**Depth defaulting**: `PlanDepth` defaults to `Depth` when `--plan-depth` is not -specified; `ReportDepth` defaults to `Depth` when `--report-depth` is not specified. +**`Context.Create(string[] args)`** → `Context` -**`--index` is repeatable**: Multiple `--index ` arguments may be provided; -all matching PDF files are combined into a single index scan. +- *Type*: In-process .NET static factory method +- *Role*: Provider — other subsystems receive the `Context` instance created here +- *Contract*: Parses all supported command-line flags into a fully initialized `Context` + instance with typed properties for every recognized flag. All flags are listed in the + *ReviewMark Design* External Interfaces section. Also exposes `WriteLine(string)`, + `WriteError(string)`, the computed `ExitCode` property, and `IDisposable.Dispose()` +- *Constraints*: Throws `ArgumentException` for unrecognized or malformed arguments; throws + `InvalidOperationException` when the log file specified by `--log` cannot be opened -### Error Handling +### Design -Unrecognized or malformed arguments cause `Context.Create` to throw an `ArgumentException`. -`Program.Main` catches this exception, writes the error message to `Console.Error`, and -returns exit code 1. The process never exits silently on an argument error. +The `Context` unit owns all argument-parsing logic through its private `ArgumentParser` +inner class. `ArgumentParser.ParseArguments()` processes the argument array sequentially, +delegating individual tokens to `ParseArgument()`, which dispatches known flags via a +`switch` statement and accumulates their values. -Value arguments (`--log`, `--plan`, `--results`, etc.) require a non-empty following -token. If the token is missing, an `ArgumentException` is thrown with a message that -names the flag and describes what is expected. +Once `Context.Create()` returns, all parsed properties are immutable. The only mutable +state after construction is the internal error flag (set by `WriteError()`) and the log +file handle (released by `Dispose()`). The `IDisposable` contract ensures the log file is +always closed before the process exits. -Integer arguments (`--depth`, `--plan-depth`, `--report-depth`) require a positive -integer value in the range 1–5. Values outside this range cause an `ArgumentException`. +`PlanDepth` defaults to `Depth` when `--plan-depth` is not specified; `ReportDepth` +defaults to `Depth` when `--report-depth` is not specified. Multiple `--index` arguments +are accumulated into a `List` and exposed as `IReadOnlyList`. diff --git a/docs/design/review-mark/cli/context.md b/docs/design/review-mark/cli/context.md index 06ffe7c..c727c9c 100644 --- a/docs/design/review-mark/cli/context.md +++ b/docs/design/review-mark/cli/context.md @@ -2,88 +2,92 @@ #### Purpose -The `Context` software unit is responsible for parsing command-line arguments and -providing a unified interface for output and logging throughout the tool. It acts as -the primary configuration carrier, passing parsed options from the CLI entry point -to all processing subsystems. +`Context` is the command-line argument parser and output channel for ReviewMark. It +parses the raw `string[] args` array into a structured set of typed properties and +provides `WriteLine()` and `WriteError()` methods that optionally duplicate output to a +log file. It is created once by `Program.Main()` and passed to every processing subsystem +as both a configuration carrier and an output mechanism. -#### Properties - -The following properties are populated by `Context.Create()` from the command-line -arguments: +#### Data Model | Property | Type | Description | | -------- | ---- | ----------- | -| `Version` | bool | Requests version display | -| `Help` | bool | Requests help display | -| `Silent` | bool | Suppresses console output | -| `Validate` | bool | Requests self-validation run | -| `Lint` | bool | Requests configuration linting | -| `ResultsFile` | string? | Path for TRX/JUnit test results output | -| `DefinitionFile` | string? | Path to the `.reviewmark.yaml` configuration | -| `PlanFile` | string? | Output path for the Review Plan document | -| `Depth` | int | Default heading depth for all generated documents (default: 1; valid range: 1–5) | -| `PlanDepth` | int | Heading depth for the Review Plan (defaults to `Depth`) | -| `ReportFile` | string? | Output path for the Review Report document | -| `ReportDepth` | int | Heading depth for the Review Report (defaults to `Depth`) | -| `IndexPaths` | IReadOnlyList<string> | Scan paths for evidence index (empty when `--index` not specified) | -| `WorkingDirectory` | string? | Base directory for resolving relative paths | -| `Enforce` | bool | Fail if any review-set is not Current | -| `ElaborateId` | string? | Review-set ID to elaborate, or null if `--elaborate` was not specified | -| `ExitCode` | int | Computed output property; 0 = success, 1 = error. Set via `WriteError`. | - -The `--log ` argument is consumed during `Context.Create()` to open the log file handle; the -path is not retained as a public property after initialization. - -#### Argument Parsing - -`Context.Create(string[] args)` is a factory method that processes the argument -array sequentially, recognizing both flag arguments (e.g., `--validate`) and -value arguments (e.g., `--plan `). Internally, it delegates to the private -`ArgumentParser` inner class, which owns the actual parsing logic via its -`ParseArgument` method. Unrecognized or unsupported arguments cause -`ArgumentParser.ParseArgument` to throw an `ArgumentException`, which propagates -through `ArgumentParser.ParseArguments` to `Context.Create`. Callers of -`Context.Create` are expected to handle the exception and surface it as a CLI -error. The resulting `Context` instance holds the fully parsed state when -argument parsing succeeds. - -The `--result` flag is accepted as an alias for `--results`; both set the -`ResultsFile` property. - -The `--depth` flag sets the default heading depth for all generated documents. -`--plan-depth` and `--report-depth` override the default for their respective -documents when specified. The valid range for all three depth flags is 1–5 -inclusive; values outside this range cause `ArgumentException` to be thrown. -When `--plan-depth` or `--report-depth` is omitted, the value from `--depth` -(or its default of 1) is used for that document. - -#### Output Methods - -- **`WriteLine(string)`** — Writes a line to the console (unless `Silent` is set) and to the log file -- **`WriteError(string)`** — Sets the internal error flag (causing `ExitCode` to return non-zero), - writes error to console (unless `Silent`) and log file -- **`Dispose()`** — Closes the log file handle opened by `--log`, if any; called automatically at - the end of the `using` block in `Program.Main()` - -#### Exit Code - -`Context.ExitCode` reflects the current error status of the tool run. It is set to -a non-zero value when an error is detected. The value of `ExitCode` is returned from -`Program.Main()` as the process exit code. - -#### IDisposable Contract - -`Context` implements `IDisposable`. Callers must dispose the instance (typically via a -`using` statement) to ensure the log file handle opened by `--log` is closed promptly. -`Program.Main()` wraps the `Context` in a `using` block so the log is always flushed and -closed before the process exits. - -#### Logging - -When a log file path is provided via the `--log` CLI argument, `Context` opens and -holds the log file handle for the duration of the tool run. All output written through -`WriteLine` and `WriteError` is duplicated to the log file. If the log file cannot be -opened (for example, because the parent directory does not exist or permissions deny -access), `Context.Create` throws an `InvalidOperationException` wrapping the underlying -file-system exception. +| `Version` | `bool` | Requests version display | +| `Help` | `bool` | Requests help display | +| `Silent` | `bool` | Suppresses console output | +| `Validate` | `bool` | Requests self-validation run | +| `Lint` | `bool` | Requests configuration linting | +| `ResultsFile` | `string?` | Path for TRX/JUnit test results output | +| `DefinitionFile` | `string?` | Path to the `.reviewmark.yaml` configuration | +| `PlanFile` | `string?` | Output path for the Review Plan document | +| `Depth` | `int` | Default heading depth for all generated documents (default: 1; valid range: 1–5) | +| `PlanDepth` | `int` | Heading depth for the Review Plan (defaults to `Depth`) | +| `ReportFile` | `string?` | Output path for the Review Report document | +| `ReportDepth` | `int` | Heading depth for the Review Report (defaults to `Depth`) | +| `IndexPaths` | `IReadOnlyList` | Scan paths for evidence index (empty when `--index` not specified) | +| `WorkingDirectory` | `string?` | Base directory for resolving relative paths | +| `Enforce` | `bool` | Fail if any review-set is not Current | +| `ElaborateId` | `string?` | Review-set ID to elaborate, or null if `--elaborate` was not specified | +| `ExitCode` | `int` | Computed; 0 = success, 1 = one or more calls to `WriteError()` occurred | + +The `--log ` argument is consumed during `Context.Create()` to open the log file +handle; the path is not retained as a public property. + +The only post-construction mutable state is the internal error flag (set by `WriteError()`) +and the log file handle (released by `Dispose()`). + +#### Key Methods + +**`Context.Create(string[] args)`** + +- *Parameters*: `string[] args` — raw command-line arguments +- *Returns*: `Context` — fully initialized instance with all properties set +- *Preconditions*: `args` is not null +- *Postconditions*: All parsed properties are immutable; log file opened if `--log` was given + +Delegates to the private `ArgumentParser` inner class, which processes the argument array +sequentially via `ParseArguments()` → `ParseArgument()`. The `--result` alias is accepted +for `--results`. `PlanDepth` defaults to `Depth`; `ReportDepth` defaults to `Depth`. +Integer depth flags require positive integers in the range 1–5. + +**`Context.WriteLine(string message)`** + +Writes a line to stdout (unless `Silent` is set) and to the log file if one is open. + +**`Context.WriteError(string message)`** + +Sets the internal error flag (causing `ExitCode` to return 1), writes the message to +stderr in red (unless `Silent` is set), and writes to the log file if one is open. Once +set, the error flag is never cleared; `ExitCode` returns 1 for the remainder of the +process lifetime. + +**`Context.Dispose()`** + +Closes the log file handle opened by `--log`, if any. Called automatically at the end of +the `using` block in `Program.Main()`. + +#### Error Handling + +| Exception | Condition | Handling | +| --------- | --------- | -------- | +| `ArgumentException` | Unrecognized or malformed argument during `Context.Create()` | Propagated to `Program.Main()`, which writes the message to `Console.Error` and returns 1 | +| `InvalidOperationException` | Log file cannot be opened during `Context.Create()` | Propagated to `Program.Main()`, which writes the message to `Console.Error` and returns 1 | + +`WriteError()` does not throw; it sets the internal error flag and writes to output +streams. Errors during the tool run are communicated through `ExitCode`, not exceptions. + +Value arguments require a non-empty following token; missing tokens cause +`ArgumentException`. Integer arguments require a positive integer in the range 1–5; +values outside this range cause `ArgumentException`. + +#### Dependencies + +- No dependencies on other ReviewMark units or subsystems. `Context.Create()` only + accesses the file system to open the optional log file. + +#### Callers + +- **`Program.Main()`** — creates the `Context` instance via `Context.Create(string[] args)` + and disposes it at the end of the `using` block +- **All processing subsystems** (Configuration, Indexing, SelfTest) — receive the `Context` + instance as a parameter for output and configuration access diff --git a/docs/design/review-mark/configuration.md b/docs/design/review-mark/configuration.md index c8fac50..d1ce196 100644 --- a/docs/design/review-mark/configuration.md +++ b/docs/design/review-mark/configuration.md @@ -1,68 +1,76 @@ -## Configuration Subsystem - -### Overview +## Configuration The Configuration subsystem is responsible for loading, validating, and processing the -ReviewMark YAML configuration file (`.reviewmark.yaml`). It also provides the -file-pattern-matching capability used to resolve glob patterns into concrete file lists. +ReviewMark YAML configuration file (`.reviewmark.yaml`). It provides file-pattern matching +and drives the generation of Review Plan and Review Report compliance documents. -### Responsibilities +### Overview -- Deserialize `.reviewmark.yaml` into a strongly-typed configuration model -- Lint the loaded configuration and report any structural errors or warnings -- Resolve `needs-review` and per-review-set `paths` glob patterns into sorted file lists -- Compute SHA-256 fingerprints across resolved file sets -- Generate Review Plan and Review Report markdown documents -- Elaborate a review-set entry and produce a formatted Markdown description +The Configuration subsystem solves the problem of interpreting the user-authored +`.reviewmark.yaml` file and turning it into actionable data for the rest of the tool. Its +boundaries are: it reads the definition YAML from disk and resolves glob patterns via +`GlobMatcher`; it does not load evidence indexes (that is the Indexing subsystem's +responsibility) but accepts a `ReviewIndex` as a parameter for report generation. -### Units +It contains two units: | Unit | Source File | Purpose | -| --- | --- | --- | +| ---- | ----------- | ------- | | ReviewMarkConfiguration | `Configuration/ReviewMarkConfiguration.cs` | YAML parser and review-set processor | | GlobMatcher | `Configuration/GlobMatcher.cs` | File pattern matching using glob syntax | -### Interfaces / API - -`ReviewMarkConfiguration.Load(string path)` is the primary entry point. It reads and -deserializes the YAML file at `path`, lints the result, and returns a -`ReviewMarkLoadResult` with two members: - -| Member | Type | Description | -| ------ | ---- | ----------- | -| `Configuration` | `ReviewMarkConfiguration?` | Parsed configuration, or `null` if loading failed | -| `Issues` | `IReadOnlyList` | Lint errors and warnings found during loading | - -When `Configuration` is non-null, the following properties are available on the `ReviewMarkConfiguration` object: - -#### Properties - -| Property | Type | Description | -| -------- | ---- | ----------- | -| `EvidenceSource` | `EvidenceSource` | Evidence-source configuration (type, location, optional credentials) | -| `Reviews` | `IReadOnlyList` | List of review-set definitions (Id, Title, Paths, fingerprinting methods) | - -When `Configuration` is non-null, callers may invoke the following methods: - -- **`GetNeedsReviewFiles(string dir)`** → `IReadOnlyList` — Resolves `needs-review` glob patterns -- **`Reviews[i].GetFingerprint(string dir)`** → `string` — Computes a content-based - SHA-256 fingerprint across the files resolved by the review-set's glob patterns. - The fingerprint is rename-invariant (based on file content, not path). Called on - individual `ReviewSet` instances from the `Reviews` collection. -- **`ElaborateReviewSet(string id, string dir, int markdownDepth = 1)`** → `ElaborateResult` — - Builds an elaboration for one review-set -- **`PublishReviewPlan(string dir, int depth = 1)`** → `ReviewPlanResult` — Generates the Review Plan Markdown -- **`PublishReviewReport(ReviewIndex, string dir, int depth = 1)`** → `ReviewReportResult` — - Produces Review Report - -### Error Handling - -- If the YAML file cannot be opened or is syntactically invalid, `Load()` returns a - null `Configuration` with a descriptive entry in `Issues`. -- Structural lint errors (duplicate review IDs, unknown evidence-source type, missing - required fields) are surfaced as `Issues` entries; `Configuration` may still be - non-null for non-fatal errors. -- `ElaborateReviewSet` throws `ArgumentException` when the supplied `id` does not - match any review-set in the configuration. -- File-system failures during glob pattern expansion (e.g., the working directory does - not exist) propagate as `IOException` or `UnauthorizedAccessException` to the caller. +See the *ReviewMarkConfiguration Design* and *GlobMatcher Design* for full unit details. + +### Interfaces + +**`ReviewMarkConfiguration.Load(string filePath)`** → `ReviewMarkLoadResult` + +- *Type*: In-process .NET static factory method +- *Role*: Provider — called by `Program.RunLintLogic()` and `Program.RunDefinitionLogic()` +- *Contract*: Reads and deserializes `.reviewmark.yaml`, lints the result, and returns a + `ReviewMarkLoadResult` with `Configuration` (or `null` on error) and `Issues` +- *Constraints*: Returns a non-null `Configuration` only when no error-level issues are + found. Lint errors and warnings are accumulated as `LintIssue` records. + +When `Configuration` is non-null, the following instance properties and methods are available: + +- **`EvidenceSource`** → `EvidenceSource` — parsed evidence-source block (`Type`, `Location`, + optional credential env-var names) +- **`Reviews`** → `IReadOnlyList` — ordered list of review-set definitions +- **`GetNeedsReviewFiles(string dir)`** → `IReadOnlyList` +- **`ElaborateReviewSet(string id, string dir, int markdownDepth = 1)`** → `ElaborateResult` +- **`PublishReviewPlan(string dir, int depth = 1)`** → `ReviewPlanResult` +- **`PublishReviewReport(ReviewIndex index, string dir, int depth = 1)`** → `ReviewReportResult` + +**`GlobMatcher.GetMatchingFiles(string baseDirectory, IReadOnlyList patterns)`** +→ `IReadOnlyList` + +- *Type*: In-process .NET static method +- *Role*: Provider — called by `ReviewMarkConfiguration` and `ReviewIndex.Scan()` +- *Contract*: Returns a sorted, deduplicated list of relative file paths matching the + ordered include/exclude glob patterns; `!`-prefixed patterns are exclusions +- *Constraints*: Throws `ArgumentNullException` for null inputs; throws `ArgumentException` + for empty base directory; file-system exceptions propagate to the caller + +### Design + +`ReviewMarkConfiguration` is the central unit. It coordinates `GlobMatcher` for file +enumeration and owns SHA-256 fingerprinting, document generation, and elaboration logic. + +1. `Load()` reads the YAML file, deserializes it into internal model types via YamlDotNet, + and delegates validation to `ValidateEvidenceSource()` and `ValidateReviews()`, which + accumulate issues into a shared list. A `null` configuration is returned only when + error-level issues are found. +2. For `PublishReviewPlan()` and `PublishReviewReport()`, `ReviewMarkConfiguration` calls + `GlobMatcher.GetMatchingFiles()` to resolve each review-set's `paths` patterns and the + top-level `needs-review` patterns into sorted file lists. +3. Fingerprinting is content-based and order-independent: per-file SHA-256 hashes are + sorted lexicographically before concatenation and re-hashing, ensuring the fingerprint + is insensitive to file enumeration order but sensitive to content changes. +4. `PublishReviewReport()` accepts `ReviewIndex` as a parameter and calls `GetEvidence()` + for each review-set to determine Current, Stale, Missing, or Failed status. + +`GlobMatcher` is stateless and has no knowledge of `ReviewMarkConfiguration`; the +dependency is strictly one-directional. Integration-level Configuration subsystem tests +may pass `ReviewIndex` objects from the Indexing subsystem to `PublishReviewReport()`; +this cross-subsystem dependency is limited to the test layer. diff --git a/docs/design/review-mark/configuration/glob-matcher.md b/docs/design/review-mark/configuration/glob-matcher.md index 8c9dc56..703eb0b 100644 --- a/docs/design/review-mark/configuration/glob-matcher.md +++ b/docs/design/review-mark/configuration/glob-matcher.md @@ -2,42 +2,58 @@ #### Purpose -The `GlobMatcher` software unit resolves an ordered list of glob patterns into a -concrete, sorted list of file paths relative to a base directory. It provides the +`GlobMatcher` is a static utility class that resolves an ordered list of glob patterns +into a concrete, sorted list of file paths relative to a base directory. It provides the file enumeration primitive used by the Configuration subsystem to expand the -`needs-review` and `review-set` file lists defined in `.reviewmark.yaml`. +`needs-review` and review-set file lists defined in `.reviewmark.yaml`, supporting both +inclusion patterns and `!`-prefixed exclusion patterns with recursive `**` matching. -#### Algorithm +#### Data Model -`GlobMatcher.GetMatchingFiles(baseDirectory, patterns)` processes patterns in the -order they are declared. Patterns prefixed with `!` are exclusion patterns; all -others are inclusion patterns. Each inclusion pattern adds matching paths to the -result set; each exclusion pattern removes matching paths from the result set. -Because patterns are applied in declaration order, a later pattern can re-include -files excluded by an earlier one, or exclude files included by an earlier one. The -`**` wildcard matches any number of path segments, enabling recursive matching. -After all patterns are processed, the result set is sorted and returned. +N/A — static utility class with no instance state. -#### Return Value +#### Key Methods -The method returns a sorted list of relative file paths. Path separators are -normalized to forward slashes regardless of the host operating system, ensuring -consistent fingerprint computation across platforms. +**`GlobMatcher.GetMatchingFiles(string baseDirectory, IReadOnlyList patterns)`** +→ `IReadOnlyList` -#### Usage +- *Parameters*: + - `string baseDirectory` — root directory to search within; must not be null, empty, + or whitespace + - `IReadOnlyList patterns` — ordered list of glob patterns; patterns prefixed + with `!` are exclusions, all others are inclusions; must not be null +- *Returns*: Sorted, deduplicated `IReadOnlyList` of relative file paths with + forward-slash separators +- *Preconditions*: `baseDirectory` is not null, empty, or whitespace; `patterns` is not null +- *Postconditions*: Returned list has no duplicates, is sorted by `StringComparer.Ordinal`, + and all separators are normalized to forward slashes -`GlobMatcher.GetMatchingFiles()` is called by `ReviewMarkConfiguration` to resolve: +Processes patterns in declaration order. Each inclusion pattern adds matching paths to a +`HashSet` accumulator; each exclusion pattern removes matching paths. Because +patterns are applied in order, a later pattern can re-include files excluded by an earlier +one, or vice versa. -- The `needs-review` file list, which represents all files subject to review -- Each `review-set` file list, which represents the files covered by a specific review record +Per-pattern `Matcher` instances are used (not a single combined matcher) to preserve +declaration-order semantics. A `HashSet` accumulator deduplicates files matched +by more than one include pattern. After all patterns are processed, paths are normalized +to forward slashes and sorted using `StringComparer.Ordinal`. #### Error Handling -`GlobMatcher.GetMatchingFiles()` throws the following exceptions for invalid inputs: - -- `ArgumentNullException` — when `baseDirectory` or `patterns` is `null` +- `ArgumentNullException` — when `baseDirectory` or `patterns` is null - `ArgumentException` — when `baseDirectory` is empty or whitespace +- `IOException` / `UnauthorizedAccessException` — file-system exceptions during enumeration + are not caught and propagate to the caller + +#### Dependencies + +- **`Microsoft.Extensions.FileSystemGlobbing`** (OTS) — provides the `Matcher` class used + for glob pattern matching and file enumeration via `AddInclude()` and + `GetResultsInFullPath()` + +#### Callers -File-system exceptions (`IOException`, `UnauthorizedAccessException`) are not caught -and propagate to the caller when the base directory is inaccessible or the filesystem -returns an error during enumeration. +- **`ReviewMarkConfiguration`** (Configuration subsystem) — calls `GetMatchingFiles()` to + resolve `needs-review` and review-set glob patterns into file lists +- **`ReviewIndex.Scan()`** (Indexing subsystem) — calls `GetMatchingFiles()` to resolve + PDF evidence glob patterns into file paths diff --git a/docs/design/review-mark/configuration/review-mark-configuration.md b/docs/design/review-mark/configuration/review-mark-configuration.md index 8f39bbf..391cc62 100644 --- a/docs/design/review-mark/configuration/review-mark-configuration.md +++ b/docs/design/review-mark/configuration/review-mark-configuration.md @@ -2,197 +2,144 @@ #### Purpose -The `ReviewMarkConfiguration` software unit is responsible for parsing the -`.reviewmark.yaml` configuration file and performing all review-set processing. -It coordinates file enumeration, fingerprint computation, evidence lookup, and -the generation of the Review Plan and Review Report compliance documents. +`ReviewMarkConfiguration` is responsible for parsing the `.reviewmark.yaml` configuration +file and performing all review-set processing. It coordinates file enumeration, fingerprint +computation, evidence lookup, and the generation of Review Plan and Review Report +compliance documents. -#### Configuration Model +#### Data Model -The `.reviewmark.yaml` file is deserialized into the following model: +**Top-level properties on a loaded `ReviewMarkConfiguration` instance:** + +| Property | Type | Description | +| -------- | ---- | ----------- | +| `EvidenceSource` | `EvidenceSource` | Parsed evidence-source block (`Type`, `Location`, optional credential env-var names) | +| `Reviews` | `IReadOnlyList` | Ordered list of review-set definitions | + +**YAML deserialization types (internal, not part of public API):** | Class | Description | | ----- | ----------- | -| `ReviewMarkYaml` | Root configuration object containing the evidence source and review list | +| `ReviewMarkYaml` | Root configuration object; contains `NeedsReview` patterns, `EvidenceSource`, and `Reviews` list | | `EvidenceSourceYaml` | Describes how to locate the evidence index (`type`, `location`, optional `credentials`) | -| `ReviewSetYaml` | Describes a single review-set (`id`, `title`, file patterns) | - -##### Evidence Source Types +| `ReviewSetYaml` | Describes a single review-set (`id`, `title`, `paths`) | +| `EvidenceCredentialsYaml` | Optional credentials block with `username-env` and `password-env` fields | -The `type` field of `EvidenceSourceYaml` controls how the evidence index is located: +**Evidence source types:** | Type | Description | | ---- | ----------- | -| `none` | No evidence index. The `location` field is optional and ignored. All review-sets are reported as Missing. | -| `fileshare` | The evidence index is read from the file path specified in `location`. | -| `url` | The evidence index is downloaded from the HTTP or HTTPS URL specified in `location`. | - -#### ReviewMarkConfiguration.Load() - -`ReviewMarkConfiguration.Load(filePath)` is the unified loading mechanism that performs -both configuration parsing and linting in a single pass. It returns a `ReviewMarkLoadResult` -containing: - -- `Configuration`: the loaded `ReviewMarkConfiguration`, or `null` if any error-level issues - were detected. -- `Issues`: a read-only list of `LintIssue` records, each with a `Location`, `Severity` - (`LintSeverity.Error` or `LintSeverity.Warning`), and `Description`. +| `none` | No evidence index; `location` is optional and ignored; all review-sets are Missing | +| `fileshare` | Evidence index read from the file path in `location` | +| `url` | Evidence index downloaded from the HTTP/HTTPS URL in `location` | -Errors result in a `null` configuration so callers can distinguish between a completely -invalid file and a file with only warnings. `LintIssue.ToString()` formats each issue as -`{location}: {severity}: {description}`, matching standard linting tool output conventions. +**Result and internal API types:** -The method delegates validation to `ValidateEvidenceSource` and `ValidateReviews`, which -accumulate issues into the shared `issues` list before `Load` decides whether to return a -valid configuration or `null`. - -#### Fingerprinting Algorithm - -The fingerprint for a review-set uniquely identifies the exact content of its file-set. -The algorithm is: +| Type | Description | +| ---- | ----------- | +| `EvidenceSource` | Immutable record: `Type`, `Location`, `UsernameEnv`, `PasswordEnv` | +| `ReviewSet` | Class with `Id`, `Title`, `Paths`, `GetFingerprint(dir)`, `GetFiles(dir)` | +| `LintSeverity` | Enum: `Warning`, `Error` | +| `LintIssue` | Record: `Location`, `Severity`, `Description`; `ToString()` formats as `{location}: {severity}: {description}` | +| `ReviewMarkLoadResult` | Record: `Configuration` (null if error-level issues found), `Issues` | +| `ReviewPlanResult` | Record: `Markdown`, `HasIssues` | +| `ReviewReportResult` | Record: `Markdown`, `HasIssues` | +| `ElaborateResult` | Record: `Markdown` | + +#### Key Methods + +**`ReviewMarkConfiguration.Load(string filePath)`** → `ReviewMarkLoadResult` + +- *Parameters*: `string filePath` — path to the `.reviewmark.yaml` file +- *Returns*: `ReviewMarkLoadResult` — contains `Configuration` (null on error) and `Issues` +- *Preconditions*: `filePath` is a valid file path +- *Postconditions*: All detectable lint issues are in `Issues`; `Configuration` is non-null + only when no error-level issues were found + +Reads the YAML file, then calls `ReviewMarkConfigurationHelpers.DeserializeRaw()` which +deserializes via YamlDotNet with `NullNamingConvention` and `IgnoreUnmatchedProperties`, +relying on `[YamlMember(Alias = "...")]` attributes for all key mappings. Validation is +delegated to `ValidateEvidenceSource()` and `ValidateReviews()`, which accumulate issues +into a shared list. Both parsing and linting are performed in a single file read. + +**`ReviewMarkConfiguration.Parse(string yaml)`** → `ReviewMarkConfiguration` + +- *Parameters*: `string yaml` — YAML content to parse +- *Returns*: A populated `ReviewMarkConfiguration` instance +- *Preconditions*: `yaml` is non-null +- *Postconditions*: Returns a fully populated configuration; throws on any parse or validation + error rather than accumulating issues + +Delegates to `ReviewMarkConfigurationHelpers.DeserializeRaw(yaml, filePath: null)` followed +by `BuildConfiguration()`. Because no file path is provided, YAML errors surface as +`ArgumentException` (not `InvalidOperationException`), preserving the unit-test contract. +Missing required fields (no evidence-source block, empty reviews list) also result in +`ArgumentException`. Used by unit tests to construct a `ReviewMarkConfiguration` directly +from YAML strings without touching the file system. + +**`ReviewMarkConfiguration.PublishReviewPlan(string dir, int depth = 1)`** → `ReviewPlanResult` + +Resolves `needs-review` patterns via `GlobMatcher.GetMatchingFiles()` and, for each file, +identifies which review-sets provide coverage. Returns the Markdown document and a boolean +indicating whether any files lack coverage. The `depth` parameter controls heading level. + +**`ReviewMarkConfiguration.PublishReviewReport(ReviewIndex index, string dir, int depth = 1)`** +→ `ReviewReportResult` + +For each review-set: resolves its file list via `GlobMatcher`, computes the SHA-256 +fingerprint, calls `index.GetEvidence(id, fingerprint)` to determine status (Current, +Stale, Missing, or Failed), and generates the report table. Returns the Markdown document +and a boolean indicating whether any review-set is non-current. + +**`ReviewMarkConfiguration.ElaborateReviewSet(string id, string dir, int markdownDepth = 1)`** +→ `ElaborateResult` + +Looks up the review-set with the given `id`, resolves its file list and fingerprint, and +returns a Markdown document with a heading at `markdownDepth`, a metadata table (ID, Title, +Fingerprint), and a file list subheading. Throws `ArgumentException` for unknown IDs; +throws `ArgumentOutOfRangeException` when `markdownDepth > 5`. + +**Fingerprinting algorithm:** 1. For each file in the review-set, read its contents and compute a SHA-256 hash. -2. Convert each hash to a lowercase hex string, then collect all per-file hashes and sort them lexicographically. +2. Convert each hash to a lowercase hex string; collect all per-file hashes and sort them + lexicographically. 3. Concatenate the sorted hashes and compute a SHA-256 hash of the result. -4. Return the final hash as a hex string — this is the review-set fingerprint. - -Sorting the per-file hashes before combining them ensures that the fingerprint is -sensitive to content changes but not to the order in which files happen to be -enumerated by the operating system. - -#### Review Plan Generation - -The Review Plan is generated by `ReviewMarkConfiguration.PublishReviewPlan()`. It produces -a Markdown document that lists every file in the `needs-review` file-set and, for -each file, identifies which review-sets provide coverage. - -- The `--plan-depth` argument controls the heading level used for sections - -#### Review Report Generation - -The Review Report is generated by `ReviewMarkConfiguration.PublishReviewReport()`. It -produces a Markdown document that lists every review-set with its current status. - -For each review-set the report includes: - -- The review-set `id` and `title` -- The current fingerprint of the file-set -- The review status: `Current`, `Stale`, `Missing`, or `Failed` - -Status is determined by looking up the current fingerprint in the loaded evidence -index to establish whether a passing, failing, stale, or missing review result exists. - -- The `--report-depth` argument controls the heading level used for sections - -#### ElaborateReviewSet - -`ReviewMarkConfiguration.ElaborateReviewSet(string id, string workingDirectory, int markdownDepth = 1)` -returns an `ElaborateResult` containing a Markdown document that elaborates on the named review-set. - -The generated Markdown document contains: - -- A heading with the review-set ID (at the specified `markdownDepth`) -- A metadata table with the following rows: `ID`, `Title`, and `Fingerprint` -- A `Files` subheading (at `markdownDepth + 1`) with all matched files listed as inline code - -The `markdownDepth` parameter controls the heading level (1–5). If `markdownDepth` is greater -than 5, the method throws `ArgumentOutOfRangeException`. - -The method throws `ArgumentNullException` for null input and `ArgumentException` for -whitespace/empty input (both via `ArgumentException.ThrowIfNullOrWhiteSpace`), and -`ArgumentException` when the ID does not match any review-set in the configuration. - -#### ValidateEvidenceSource - -`ReviewMarkConfigurationHelpers.ValidateEvidenceSource(string filePath, EvidenceSourceYaml? evidenceSource, -ICollection issues)` -validates the `evidence-source` block and appends any detected issues to `issues`. It is a -`internal static` method on the file-local `ReviewMarkConfigurationHelpers` type, called by -`Load()`. Validation is exercised indirectly through `Load()` tests. - -Checks performed: - -- If `evidenceSource` is `null`, one `Error` is added - ("missing required 'evidence-source' block") and the method returns early. -- If `type` is missing or whitespace, one `Error` is added. -- If `type` is present but not one of `none`, `fileshare`, or `url`, one `Error` is added. -- If `type` is not `none` and `location` is missing or whitespace, one `Error` is added. - -#### ValidateReviews - -`ReviewMarkConfigurationHelpers.ValidateReviews(string filePath, IList reviews, ICollection issues)` -validates every entry in the `reviews` list and appends any detected issues to `issues`. It is a -`internal static` method on the file-local `ReviewMarkConfigurationHelpers` type, called by -`Load()`. Validation is exercised indirectly through `Load()` tests. - -The method iterates over `reviews` by index and for each entry checks: - -- Missing `id` — adds an `Error` referencing the zero-based index. -- Duplicate `id` — adds an `Error` naming the duplicate ID. -- Missing `title` — adds an `Error` referencing the zero-based index. -- Missing or empty `paths` (no non-whitespace entries) — adds an `Error` referencing the zero-based index. - -#### Linting - -`ReviewMarkConfiguration.Load(filePath)` accumulates all detectable issues in a single pass -without stopping at the first error. It delegates to `ValidateEvidenceSource` and -`ValidateReviews`, which together cover: - -- Missing or invalid `evidence-source` block and fields -- All review-set `id` values are unique -- Each review-set has required `id`, `title`, and `paths` fields - -#### Internal API Types - -The following internal types are used by `ReviewMarkConfiguration` and related classes: - -##### EvidenceSource - -`EvidenceSource(string Type, string? Location, string? UsernameEnv, string? PasswordEnv)` — an -immutable record that describes how to locate the evidence index. `Type` is one of `none`, -`fileshare`, or `url`. `Location` is the file path or URL, and is optional for `none` sources. -`UsernameEnv` and `PasswordEnv` are the names of environment variables holding HTTP Basic-auth -credentials, used only by `url` sources. - -##### ReviewSet - -`ReviewSet` is a class with the following members: - -- `Id` — the review-set identifier -- `Title` — the human-readable title -- `Paths` — the ordered list of glob patterns -- `GetFingerprint(directory)` — computes the SHA-256 fingerprint for the review-set file-set -- `GetFiles(directory)` — returns the list of files matched by the review-set patterns - -##### LintSeverity - -`LintSeverity` is an enum with two values: `Warning` and `Error`. +4. Return the final hash as a hex string. -##### LintIssue +Sorting per-file hashes before combining ensures the fingerprint is insensitive to +file enumeration order but sensitive to content changes. -`LintIssue(string Location, LintSeverity Severity, string Description)` — a record representing -a single linting diagnostic. `ToString()` formats the issue as `{location}: {severity}: {description}`, -matching standard linting tool output conventions. +**`ValidateEvidenceSource()`** (internal): validates the `evidence-source` block; adds +errors for null block, missing or unknown `type`, and missing `location` for non-`none` +types. -##### ReviewMarkLoadResult +**`ValidateReviews()`** (internal): iterates reviews by index; adds errors for missing +`id`, duplicate `id`, missing `title`, and missing or empty `paths`. -`ReviewMarkLoadResult(ReviewMarkConfiguration? Configuration, IReadOnlyList Issues)` — a -record returned by `ReviewMarkConfiguration.Load()`. `Configuration` is `null` if any error-level -issues were detected. `Issues` contains all detected lint diagnostics. +#### Error Handling -##### ReviewPlanResult +| Exception | Source | Handling | +| --------- | ------ | -------- | +| `InvalidOperationException` | File open failure or unexpected I/O in `Load()` | Propagated to `Program.RunLintLogic()` or `Program.RunDefinitionLogic()` | +| `ArgumentException` | Unknown review-set ID in `ElaborateReviewSet()` | Propagated to `Program.RunDefinitionLogic()`, which catches it and calls `context.WriteError()` | +| `ArgumentOutOfRangeException` | `markdownDepth > 5` in `ElaborateReviewSet()` | Propagated to the caller | +| `IOException` / `UnauthorizedAccessException` | File-system failure during glob pattern expansion | Propagated to the caller | -`ReviewPlanResult(string Markdown, bool HasIssues)` — a record returned by -`ReviewMarkConfiguration.PublishReviewPlan()`. `Markdown` is the generated plan document. -`HasIssues` is `true` if any files in the needs-review set are not covered by any review-set. +Lint errors (duplicate IDs, missing fields, invalid evidence-source type) are surfaced as +`LintIssue` entries in `ReviewMarkLoadResult.Issues` and do not cause exceptions. -##### ReviewReportResult +#### Dependencies -`ReviewReportResult(string Markdown, bool HasIssues)` — a record returned by -`ReviewMarkConfiguration.PublishReviewReport()`. `Markdown` is the generated report document. -`HasIssues` is `true` if any review-set has a status other than `Current`. +- **`GlobMatcher`** (Configuration subsystem) — called to resolve `needs-review` and + review-set glob patterns into sorted file lists +- **`ReviewIndex`** (Indexing subsystem) — accepted as a parameter in + `PublishReviewReport()`; no static import dependency (passed in by the caller) +- **`YamlDotNet`** (OTS) — used by `Load()` to deserialize the `.reviewmark.yaml` file + into the internal model types -##### ElaborateResult +#### Callers -`ElaborateResult(string Markdown)` — a record returned by -`ReviewMarkConfiguration.ElaborateReviewSet()`. `Markdown` is the generated elaboration document. +- **`Program.RunLintLogic()`** — calls `Load()` then reports issues via `ReportIssues()` +- **`Program.RunDefinitionLogic()`** — calls `Load()`, `PublishReviewPlan()`, + `PublishReviewReport()`, and `ElaborateReviewSet()` diff --git a/docs/design/review-mark/indexing.md b/docs/design/review-mark/indexing.md index 305ac8d..bc02afb 100644 --- a/docs/design/review-mark/indexing.md +++ b/docs/design/review-mark/indexing.md @@ -1,110 +1,80 @@ -## Indexing Subsystem - -### Overview +## Indexing The Indexing subsystem is responsible for loading review evidence from an external index and for safe file-path manipulation. It provides the lookup engine that determines whether each review-set is Current, Stale, Missing, or Failed. -### Responsibilities +### Overview + +The Indexing subsystem solves the problem of abstracting the evidence store so that callers +do not need to know whether evidence was loaded from a file share, downloaded over HTTP, +built from PDF scans, or is simply absent. Its boundary is: it reads evidence from external +sources (files, URLs, PDFs) and exposes a uniform query interface; it does not interpret +review-set definitions but accepts glob patterns from `GlobMatcher` for PDF scanning. + +It contains two units: + +| Unit | Source File | Purpose | +| ---- | ----------- | ------- | +| ReviewIndex | `Indexing/ReviewIndex.cs` | Review evidence loader and query engine | +| PathHelpers | `Indexing/PathHelpers.cs` | Safe path-combination utility | + +See the *ReviewIndex Design* and *PathHelpers Design* for full unit details. + +### Interfaces + +**`ReviewIndex` static factory methods:** + +- **`ReviewIndex.Empty()`** → `ReviewIndex` — empty index with no entries +- **`ReviewIndex.Load(EvidenceSource)`** → `ReviewIndex` — loads from configured source +- **`ReviewIndex.Load(EvidenceSource, HttpClient)`** → `ReviewIndex` — testable overload +- **`ReviewIndex.Scan(string dir, IReadOnlyList paths, Action? onWarning)`** + → `ReviewIndex` — builds an index by scanning PDF files + +**`ReviewIndex` instance methods:** -- Load the evidence index from a `none`, `fileshare`, or `url` source -- Scan a set of PDF files, extract structured metadata from the Keywords field, and - produce an `index.json` evidence index -- Save the evidence index to a JSON file for later loading -- Provide safe path-combination utilities that prevent directory-traversal attacks +- **`Save(string filePath)`** — writes the index to a JSON file +- **`Save(Stream stream)`** — writes the index to a stream (testable overload) +- **`HasId(string id)`** → `bool` — true if any evidence exists for the given ID +- **`GetEvidence(string id, string fingerprint)`** → `ReviewEvidence?` — matching record or null +- **`GetAllForId(string id)`** → `IReadOnlyList` — all records for an ID -### Units +**`PathHelpers`:** -| Unit | Source File | Purpose | -|---------------|--------------------------------|------------------------------------------------------| -| ReviewIndex | `Indexing/ReviewIndex.cs` | Review evidence loader and query engine | -| PathHelpers | `Indexing/PathHelpers.cs` | File path utilities (safe path combination) | +- **`SafePathCombine(string basePath, string relativePath)`** → `string` — combines paths, + rejecting traversal sequences -### Cross-Unit Interaction and Data Flow +The subsystem exposes no public types beyond `ReviewIndex`, `ReviewEvidence`, and +`PathHelpers`; all members are `internal` to the assembly. -`ReviewIndex` is the primary unit of the subsystem. It depends on `GlobMatcher` -(from the Configuration subsystem) to resolve glob patterns into sorted file lists -during PDF scanning, and on `PathHelpers` (in this subsystem) for safe path -combination when constructing output file paths. +### Design -The data flow through the subsystem follows two distinct paths: +`ReviewIndex` is the primary unit. It owns all evidence-store interaction and exposes a +uniform interface regardless of source type. `PathHelpers` is a pure stateless utility +called by `ReviewIndex.Scan()`. **Load path** (evidence already indexed): -1. `Program` calls `ReviewIndex.Load(EvidenceSource)` with the configured source. -2. `ReviewIndex` dispatches to the appropriate loader: empty index for `none`, - local file read for `fileshare`, or HTTP download for `url`. -3. The loaded JSON is deserialized into internal `ReviewEvidence` records and - stored in a two-level dictionary keyed by `(id, fingerprint)`. -4. The populated `ReviewIndex` is returned to `Program` for use in report - generation. +1. `Program` calls `ReviewIndex.Load(EvidenceSource)`. +2. `ReviewIndex` dispatches to the appropriate loader: empty index for `none`, local file + read for `fileshare`, or HTTP download for `url` (with optional Basic-auth credentials + from environment variables named by `UsernameEnv` and `PasswordEnv`). +3. The loaded JSON is deserialized into `ReviewEvidence` records and stored in a two-level + `Dictionary>` keyed by `(id, fingerprint)`. +4. The populated `ReviewIndex` is returned for use in report generation. **Scan path** (building the index from PDF evidence files): 1. `Program` calls `ReviewIndex.Scan(directory, paths, onWarning)`. -2. `GlobMatcher.GetMatchingFiles` resolves the glob patterns into a sorted list - of PDF file paths. -3. For each matched file, `ReviewIndex` opens the PDF with PDFsharp and reads - the `Keywords` document property. -4. The keywords string is parsed into key-value pairs; entries with all required - fields (`id`, `fingerprint`, `date`, `result`) are added to the index. -5. PDFs that cannot be opened or are missing required metadata trigger the - `onWarning` callback with a descriptive message. -6. The completed `ReviewIndex` is returned, and `Program` calls `Save()` to - persist it as `index.json`. - -### API - -`ReviewIndex` exposes the following public API (all members are `internal` to the -assembly): - -#### Static Factory Methods - -- **`Empty()`** → `ReviewIndex` — Returns a new empty index with no entries -- **`Load(EvidenceSource)`** → `ReviewIndex` — Loads the index from the configured source -- **`Load(EvidenceSource, HttpClient)`** → `ReviewIndex` — Testable overload with injected HttpClient -- **`Scan(string dir, IReadOnlyList paths, Action? onWarning)`** → `ReviewIndex` — - Builds an index by scanning PDF files - -#### Instance Methods - -- **`Save(string filePath)`** — Saves the index to a JSON file -- **`Save(Stream stream)`** — Saves the index to a stream (testable overload) -- **`HasId(string id)`** → `bool` — Returns true if any evidence exists for the given ID -- **`GetEvidence(string id, string fingerprint)`** → `ReviewEvidence?` — Returns matching evidence or null -- **`GetAllForId(string id)`** → `IReadOnlyList` — Returns all evidence entries for an ID - -`PathHelpers` exposes: - -- **`SafePathCombine(string base, string relative)`** → `string` — Combines paths, rejecting traversal sequences - -### Normal Operation - -During a typical review plan or report generation run: - -1. `ReviewIndex.Load` is called with the `EvidenceSource` from the configuration. - - For `none` sources, an empty `ReviewIndex` is returned immediately with no - file system or network access. - - For `fileshare` sources, the JSON file at `EvidenceSource.Location` is read - and deserialized. - - For `url` sources, an HTTP GET request is issued to `EvidenceSource.Location` - and the response body is deserialized as JSON. -2. The loaded index is passed to `ReviewMarkConfiguration.PublishReviewReport()`, - which calls `GetEvidence` for each review-set to determine its status. -3. When the `--index` flag is used, `ReviewIndex.Scan` is called first to rebuild - the index from PDF files, and `Save` is called to write `index.json`. - -### Error Handling - -- If the evidence source type is unrecognized, `Load` throws - `InvalidOperationException` with a descriptive message. -- If the `fileshare` JSON file cannot be read or contains invalid JSON, `Load` - throws `InvalidOperationException` wrapping the underlying exception. -- If the `url` HTTP request returns a non-success status code or the response - body is not valid JSON, `Load` throws `InvalidOperationException`. -- If `filePath` is null or empty in `Save(string)`, `ArgumentException` is thrown. -- PDFs that cannot be opened during `Scan` produce a warning via `onWarning` - and are skipped; the scan continues with remaining files. -- `SafePathCombine` throws `ArgumentException` for any path segment containing - traversal sequences (`..`) or absolute paths. +2. `GlobMatcher.GetMatchingFiles()` resolves the glob patterns into sorted PDF file paths. +3. For each matched file, `ReviewIndex` opens the PDF with PDFsharp and reads the + `Keywords` document property; the keywords string is parsed into key-value pairs. +4. Entries with all required fields (`id`, `fingerprint`, `date`, `result`) are added to + the index; PDFs that cannot be opened or are missing required metadata trigger the + `onWarning` callback. +5. The completed `ReviewIndex` is returned, and `Program` calls `Save()` to persist + `index.json`. + +The two-level dictionary storage enables O(1) evidence lookup. The testable +`Load(EvidenceSource, HttpClient)` overload and the `Save(Stream)` overload allow unit +tests to exercise loading and saving without real network or file-system access. diff --git a/docs/design/review-mark/indexing/path-helpers.md b/docs/design/review-mark/indexing/path-helpers.md index 39da91b..b6d528f 100644 --- a/docs/design/review-mark/indexing/path-helpers.md +++ b/docs/design/review-mark/indexing/path-helpers.md @@ -1,61 +1,64 @@ ### PathHelpers -#### Overview +#### Purpose -`PathHelpers` is a static utility class that provides a safe path-combination method. It -protects callers against path-traversal attacks by verifying the resolved combined path stays -within the base directory. Note that `Path.GetFullPath` normalizes `.`/`..` segments but does -not resolve symlinks or reparse points, so this check guards against string-level traversal -only. +`PathHelpers` is a static utility class that provides a safe path-combination primitive. +It guards against path-traversal attacks by verifying the resolved combined path remains +within the base directory, protecting the tool when it constructs file paths from +`file` fields read from externally supplied evidence index records. -#### Class Structure +#### Data Model -##### SafePathCombine Method +N/A — static utility class with no instance state. -```csharp -internal static string SafePathCombine(string basePath, string relativePath) -``` +#### Key Methods -Combines `basePath` and `relativePath` safely, ensuring the resulting path remains within -the base directory. +**`PathHelpers.SafePathCombine(string basePath, string relativePath)`** → `string` -**Validation steps:** +- *Parameters*: + - `string basePath` — the root directory that the result must reside within; must not + be null + - `string relativePath` — the relative path to combine with `basePath`; must not be null +- *Returns*: The combined absolute path as a string +- *Preconditions*: Both parameters are non-null and the combined path resolves inside + `basePath` +- *Postconditions*: Returned path is within `basePath`; throws otherwise + +Algorithm: 1. Reject null inputs via `ArgumentNullException.ThrowIfNull`. -2. Combine the paths with `Path.Combine` to produce the candidate path (preserving the - caller's relative/absolute style). -3. Resolve both `basePath` and the candidate to absolute form with `Path.GetFullPath`. -4. Compute `Path.GetRelativePath(absoluteBase, absoluteCombined)` and reject the input if - the result is exactly `".."`, starts with `".."` followed by `Path.DirectorySeparatorChar` - or `Path.AltDirectorySeparatorChar`, or is itself rooted (absolute), which would indicate - the combined path escapes the base directory. - -#### Design Decisions - -- **`Path.GetRelativePath` for containment check**: Using `GetRelativePath` to verify - containment handles root paths (e.g. `/`, `C:\`), platform case-sensitivity, and - directory-separator normalization natively. The containment test should treat `..` as an - escaping segment only when it is the entire relative result or is followed by a directory - separator, avoiding false positives for valid in-base names such as `..data`. -- **Post-combine canonical-path check**: Resolving paths after combining handles all traversal - patterns — `../`, embedded `/../`, absolute-path overrides, and platform edge cases — - without fragile pre-combine string inspection of `relativePath`. -- **ArgumentException on invalid input**: Callers receive a specific `ArgumentException` - identifying `relativePath` as the problematic parameter, making debugging straightforward. -- **No logging or error accumulation**: `SafePathCombine` is a pure utility method that throws - on invalid input; it does not interact with the `Context` or any output mechanism. -- **Platform-passthrough exceptions**: `SafePathCombine` does not suppress platform exceptions - arising from the path arguments. Callers should be aware that platform-specific conditions - may surface through `Path.GetFullPath` and `Path.Combine`: - - `NotSupportedException` — thrown when a path contains an unsupported format (e.g. a colon - in a non-drive-root position on Windows). - - `PathTooLongException` — thrown when the combined path exceeds the platform path-length - limit. These are passed through to the caller without wrapping. - -#### Security Rationale - -Evidence index files may be loaded from external sources (file shares or URLs). -The `file` field in each index record is supplied by the evidence store and must -be treated as untrusted input. Without path validation, a maliciously crafted -index could direct the tool to read or reference files outside the intended -evidence directory. `SafePathCombine` eliminates this attack surface. +2. Combine the paths with `Path.Combine` to produce a candidate path. +3. Resolve both `basePath` and the candidate to absolute form via `Path.GetFullPath`. +4. Compute `Path.GetRelativePath(absoluteBase, absoluteCombined)`. +5. Reject the input if the relative result is `".."`, starts with `".."` followed by a + directory-separator character, or is rooted (absolute) — any of these conditions + indicates the combined path escapes `basePath`. + +Using `Path.GetRelativePath()` after resolving to absolute form handles all traversal +patterns (`../`, embedded `/../`, absolute-path overrides) without fragile pre-combine +string inspection. The check treats `..` as an escaping segment only when it is the +complete relative result or is followed by a separator, avoiding false positives for +valid names such as `..data`. + +Note: `Path.GetFullPath` normalizes `.`/`..` segments but does not resolve symlinks or +reparse points; this containment check guards against string-level traversal only. + +#### Error Handling + +| Exception | Condition | +| --------- | --------- | +| `ArgumentNullException` | `basePath` or `relativePath` is null | +| `ArgumentException` | Resolved path escapes `basePath` | +| `NotSupportedException` | Path contains an unsupported format (e.g. colon in a non-root position on Windows); propagated without wrapping | +| `PathTooLongException` | Combined path exceeds the platform path-length limit; propagated without wrapping | + +#### Dependencies + +- No dependencies on other ReviewMark units, subsystems, or OTS libraries — uses only + `System.IO.Path` from the .NET runtime. + +#### Callers + +- **`ReviewIndex.Scan()`** (Indexing subsystem) — calls `SafePathCombine()` to validate + each PDF file path constructed from index `file` fields before opening the file, + preventing directory-traversal attacks from maliciously crafted evidence index records diff --git a/docs/design/review-mark/indexing/review-index.md b/docs/design/review-mark/indexing/review-index.md index e517a51..105ce92 100644 --- a/docs/design/review-mark/indexing/review-index.md +++ b/docs/design/review-mark/indexing/review-index.md @@ -2,123 +2,119 @@ #### Purpose -The `ReviewIndex` software unit manages the loading, querying, and creation of the review -evidence index. It abstracts the evidence store behind a uniform interface so that -the rest of the tool does not need to know whether evidence is stored on a fileshare, -served over HTTP, or absent entirely. +`ReviewIndex` manages the loading, querying, and building of the review evidence index. +It abstracts the evidence store behind a uniform in-memory interface so that callers do +not need to know whether evidence was loaded from a file share, downloaded over HTTP, +built from PDF scans, or is simply absent. -#### ReviewEvidence Record +#### Data Model -`ReviewEvidence` is an immutable record that holds the in-memory representation of a -single review record once the index has been loaded or scanned. +**`ReviewIndex` instance state:** + +| Field | Type | Description | +| ----- | ---- | ----------- | +| Internal store | `Dictionary>` | Evidence records indexed by `(Id, Fingerprint)` for O(1) lookup | + +The dictionary is populated during construction (via `Load()` or `Scan()`) and is +read-only thereafter. + +**`ReviewEvidence` record:** | Property | Type | Description | | -------- | ---- | ----------- | -| `Id` | string | The review-set identifier | -| `Fingerprint` | string | The SHA-256 fingerprint of the reviewed files | -| `Date` | string | The date of the review (e.g. `2026-02-14`) | -| `Result` | string | The review outcome (`pass` or `fail`) | -| `File` | string | The relative path to the review evidence PDF | +| `Id` | `string` | The review-set identifier | +| `Fingerprint` | `string` | SHA-256 fingerprint of the reviewed files | +| `Date` | `string` | Date of the review (e.g. `2026-02-14`) | +| `Result` | `string` | Review outcome (`pass` or `fail`) | +| `File` | `string` | Relative path to the review evidence PDF | -The `ReviewIndex` holds these records in a two-level -`Dictionary>` keyed first by `Id` and -then by `Fingerprint`, which enables O(1) lookup by both fields simultaneously. +**Evidence index JSON format** (`index.json`): top-level object with a `reviews` array; +each entry has fields `id`, `fingerprint`, `date`, `result`, and `file`. -#### Evidence Index Format +#### Key Methods -The evidence index is a JSON file (`index.json`) containing an array of review records. -Each record has the following fields: +**`ReviewIndex.Load(EvidenceSource)`** → `ReviewIndex` -| Field | Type | Description | -| ----- | ---- | ----------- | -| `id` | string | Unique identifier for the review record (matches the review-set `id` in `.reviewmark.yaml`) | -| `fingerprint` | string | SHA-256 fingerprint of the file-set at time of review | -| `date` | string | Date the review was conducted | -| `result` | string | Review outcome (`pass` or `fail`) | -| `file` | string | Relative path to the PDF evidence file | - -#### ReviewIndex.Load(EvidenceSource) - -`ReviewIndex.Load(EvidenceSource)` selects a loading strategy based on the evidence -source type (see below). For `url` sources, the tool constructs an `HttpClient` -internally and applies a pre-emptive `Authorization: Basic ` header when both -credential environment-variable names (`UsernameEnv` and `PasswordEnv` from the -`EvidenceSource`) are set and the corresponding environment variables are non-empty. -The encoded credential is `Base64(UTF-8(":"))`. -This overload is **not** exposed for test injection; see -`Load(EvidenceSource, HttpClient)` for the testable overload. - -- **`none`** — Returns an empty index (equivalent to `ReviewIndex.Empty()`) -- **`fileshare`** — Reads `index.json` from the specified file path -- **`url`** — Downloads `index.json` from the specified HTTP or HTTPS URL, with optional - Basic-auth credentials read from environment variables - -##### Error Behavior - -- **`fileshare` — file missing or unreadable**: If the file at the specified path does not - exist or cannot be read, an `InvalidOperationException` is thrown with a message - identifying the path and the underlying I/O failure. -- **`fileshare` — malformed JSON**: If the file exists but cannot be deserialized as a - valid evidence index, an `InvalidOperationException` is thrown with a message describing - the parse failure. -- **`url` — HTTP request fails**: If the HTTP or HTTPS request fails (e.g., network - error, non-success status code), an `InvalidOperationException` is thrown with a message - identifying the URL and the HTTP status or network error. -- **`url` — malformed response**: If the response body is not valid evidence-index JSON, - an `InvalidOperationException` is thrown with a message describing the parse failure. - -#### ReviewIndex.Load(EvidenceSource, HttpClient) - -`ReviewIndex.Load(EvidenceSource, HttpClient)` is an internally-visible overload that -accepts a caller-supplied `HttpClient`. It is exposed to allow unit tests to inject a -fake `HttpMessageHandler` when testing `url`-type evidence sources, avoiding real -network calls. The behavior is identical to the single-argument overload except that -the caller provides the `HttpClient` instead of having one created internally. - -#### ReviewIndex.Scan() - -`ReviewIndex.Scan(directory, paths, onWarning)` scans a directory for PDF files matching -the given glob patterns. For each PDF file found, it reads embedded metadata to -extract the review record fields and returns a populated in-memory `ReviewIndex`. -The `onWarning` parameter is an optional `Action?` callback invoked with a -warning message when a PDF is skipped due to missing or incomplete metadata fields. -When a PDF file cannot be opened or read (e.g., the file is corrupt or access is -denied), `onWarning` is invoked with a descriptive message and scanning continues -with the next file; no exception is propagated to the caller. -The caller (e.g., `Program`) is responsible for choosing an output path and calling -`Save(...)` on the returned index to produce `index.json` as part of the `--index` -workflow. - -#### ReviewIndex.Empty() - -`ReviewIndex.Empty()` returns an index with no records. It is used when the evidence -source type is `none`, resulting in all review-sets being reported as Missing. - -#### ReviewIndex.Save() - -`ReviewIndex` provides two overloads for persisting the index to `index.json` format: - -- `Save(string filePath)` — writes the serialized index to the specified file path -- `Save(Stream stream)` — writes the serialized index to the provided stream - -Both overloads serialize all `ReviewEvidence` records in the index to JSON format. -The `Save(string filePath)` overload is used by the `--index` workflow in `Program` -to write the output file after scanning. - -#### ReviewIndex.GetEvidence() - -`ReviewIndex.GetEvidence(string id, string fingerprint)` returns the `ReviewEvidence` -record whose `Id` matches `id` and whose `Fingerprint` matches `fingerprint`, or `null` -if no such record exists. - -#### ReviewIndex.HasId() - -`ReviewIndex.HasId(string id)` returns `true` if the index contains at least one record -with the given `id`, regardless of fingerprint. Returns `false` if no record exists for -the id. - -#### ReviewIndex.GetAllForId() - -`ReviewIndex.GetAllForId(string id)` returns all `ReviewEvidence` records that have the -given `id`, as a read-only indexed collection (`IReadOnlyList`). Returns an -empty collection if no records exist for the id. +- *Parameters*: `EvidenceSource` — configured evidence source (type, location, credentials) +- *Returns*: `ReviewIndex` populated from the specified source +- *Preconditions*: Evidence source type is one of `none`, `fileshare`, or `url` +- *Postconditions*: All records from the source are in the internal dictionary + +Dispatches by source type: `none` → `Empty()`; `fileshare` → reads and deserializes the +JSON file at `EvidenceSource.Location`; `url` → issues an HTTP GET to +`EvidenceSource.Location`. For `url` sources, if both `UsernameEnv` and `PasswordEnv` +environment variables are set and non-empty, a pre-emptive `Authorization: Basic +` header is added, encoding `Base64(UTF-8(":"))`. + +**`ReviewIndex.Load(EvidenceSource, HttpClient)`** → `ReviewIndex` (testable overload) + +Accepts a caller-supplied `HttpClient` to allow unit tests to inject a fake +`HttpMessageHandler` without real network calls. Behavior is identical to the +single-argument overload. + +**`ReviewIndex.Scan(string dir, IReadOnlyList paths, Action? onWarning)`** +→ `ReviewIndex` + +- *Parameters*: `dir` — scan root; `paths` — glob patterns for PDF files; `onWarning` — + optional callback invoked with a descriptive message when a PDF is skipped +- *Returns*: `ReviewIndex` built from scanned PDF metadata +- *Preconditions*: `dir` is a valid directory path; `paths` is not null +- *Postconditions*: All PDFs with valid metadata are in the returned index + +Calls `GlobMatcher.GetMatchingFiles(dir, paths)` to enumerate PDFs. For each matched +file, calls `PdfReader.Open(fullPath, PdfDocumentOpenMode.Import)` via PDFsharp and reads +`doc.Info.Keywords`. The keywords string is parsed into key-value pairs; entries with all +required fields are added to the index. PDFs that cannot be opened or are missing required +fields trigger `onWarning` and are skipped; the scan continues. + +**`ReviewIndex.Empty()`** → `ReviewIndex` + +Returns an index with no records. Used when evidence source type is `none`. + +**`ReviewIndex.Save(string filePath)`** / **`ReviewIndex.Save(Stream stream)`** + +Serializes all `ReviewEvidence` records to JSON format and writes to the specified file +or stream. Used by `Program.RunIndexLogic()` to write `index.json` after scanning. + +**`ReviewIndex.GetEvidence(string id, string fingerprint)`** → `ReviewEvidence?` + +Returns the `ReviewEvidence` record matching both `id` and `fingerprint`, or null if none +exists. O(1) lookup via the two-level dictionary. + +**`ReviewIndex.HasId(string id)`** → `bool` + +Returns true if the index contains at least one record with the given `id`, regardless of +fingerprint. + +**`ReviewIndex.GetAllForId(string id)`** → `IReadOnlyList` + +Returns all `ReviewEvidence` records with the given `id`. Returns an empty list if none exist. + +#### Error Handling + +| Exception | Source | Handling | +| --------- | ------ | -------- | +| `InvalidOperationException` | `Load()` — unrecognized source type, file-read failure, HTTP error, or malformed JSON | Propagated to `Program.RunDefinitionLogic()` or `Program.RunIndexLogic()` | +| `ArgumentException` | `Save(string)` — null or empty `filePath` | Propagated to the caller | +| `InvalidOperationException` | `Save(string)` — file write failure | Propagated to the caller | + +During `Scan()`, PDFs that cannot be opened or are missing required metadata trigger +`onWarning` with a descriptive message; no exception is propagated. + +#### Dependencies + +- **`GlobMatcher`** (Configuration subsystem) — called by `Scan()` to resolve PDF + evidence glob patterns into file paths +- **`PathHelpers`** (Indexing subsystem) — called by `Scan()` for safe path combination + when constructing the absolute path of each matched PDF +- **`PDFsharp`** (OTS) — used by `Scan()` to open PDF files and read the `Keywords` + metadata field + +#### Callers + +- **`Program.RunDefinitionLogic()`** — calls `Load(EvidenceSource)` to load the evidence + index for report generation +- **`Program.RunIndexLogic()`** — calls `Scan()` to build the index from PDF files and + `Save(string)` to persist `index.json` +- **`ReviewMarkConfiguration.PublishReviewReport()`** — calls `GetEvidence()` and `HasId()` + for each review-set to determine review status diff --git a/docs/design/review-mark/program.md b/docs/design/review-mark/program.md index 9a1f4a0..3dd3d78 100644 --- a/docs/design/review-mark/program.md +++ b/docs/design/review-mark/program.md @@ -2,135 +2,122 @@ ### Purpose -The `Program` software unit is the main entry point of the ReviewMark tool. It is -responsible for constructing the execution context, dispatching to the appropriate -processing logic based on parsed flags, and returning a meaningful exit code to the -calling process. +`Program` is the process entry point and execution dispatcher for ReviewMark. It owns +`Main()`, constructs the `Context` instance, dispatches to the appropriate processing +logic based on parsed CLI flags, and returns a meaningful exit code. There is no +review-processing logic in `Program` itself; all domain work is delegated to the +Configuration, Indexing, and SelfTest subsystems through the `Context` carrier. -### Version Property +### Data Model -`Program.Version` returns the tool version string. The version is embedded at build -time from the assembly metadata and follows semantic versioning conventions. +**`Version`**: `string` (static property) — Tool version string embedded at build time; +sourced first from `AssemblyInformationalVersionAttribute` (may include a git hash suffix), +then from `AssemblyVersionAttribute`, and finally defaults to `"0.0.0"` when neither +attribute is present. Stateless and thread-safe. -### Main() Method +`Program` holds no other state; `Main()`, `Run()`, and all helper methods are static. -`Program.Main(string[] args)` is the process entry point. It: +### Key Methods -1. Constructs a `Context` instance via `Context.Create(args)` inside a `using` block -2. Calls `Program.Run(Context)` to perform the requested operation -3. Returns `Context.ExitCode` as the process exit code +**`Program.Main(string[] args)`** -**Exception handling — three tiers:** +- *Parameters*: `string[] args` — raw command-line arguments from the OS +- *Returns*: `int` — exit code; 0 for success, 1 for failure +- *Preconditions*: Called by the OS process launcher as the entry point +- *Postconditions*: All resources are released; exit code reflects success or failure -| Exception type | Action | -| -------------- | ------ | -| `ArgumentException` | Write `"Error: {message}"` to `Console.Error`; return exit code 1 | -| `InvalidOperationException` | Write `"Error: {message}"` to `Console.Error`; return exit code 1 | +Creates a `Context` instance via `Context.Create(args)` inside a `using` block, calls +`Program.Run(context)`, and returns `context.ExitCode`. Uses a three-tier exception +handler: + +| Exception | Action | +| --------- | ------ | +| `ArgumentException` | Write `"Error: {message}"` to `Console.Error`; return 1 | +| `InvalidOperationException` | Write `"Error: {message}"` to `Console.Error`; return 1 | | Any other exception | Write `"Unexpected error: {message}"` to `Console.Error`; rethrow | -`ArgumentException` is thrown by `Context.Create` when an unknown or malformed -argument is supplied. `InvalidOperationException` is thrown by `Context.Create` -when the log file cannot be opened, or by `RunDefinitionLogic` when a plan or -report file cannot be written. Other exceptions propagate as unhandled, which -terminates the process with a runtime-generated error exit code. - -### Run() Dispatch Logic - -`Program.Run(Context)` evaluates the parsed flags in the following priority order, -executing the first matching action and returning: - -1. If `--version` — print version and return -2. Print application banner (skipped for `--lint`) -3. If `--help` — print help and return -4. If `--validate` — run self-validation and return -5. If `--lint` — run configuration lint and return -6. Otherwise — run main tool logic (index scanning and/or Review Plan/Report/Elaborate) - -The application banner (step 2) is always printed unless `--version` or `--lint` is -specified. Only one top-level action is performed per invocation. Actions later in the -priority order are not reached if an earlier flag is set. - -### PrintBanner() - -`Program.PrintBanner(Context)` writes the application name, version, and copyright -notice to the console via `Context.WriteLine()`. The banner is printed for every -invocation except `--version` and `--lint`. - -### PrintHelp() - -`Program.PrintHelp(Context)` writes usage information to the console via -`Context.WriteLine()`. The help text lists all supported flags and arguments with brief -descriptions. - -### RunLintLogic() - -`Program.RunLintLogic(Context)` validates the definition file and reports issues: - -1. Resolves the definition file path (from `--definition` or the default - `.reviewmark.yaml` relative to the working directory). -2. Loads and lints the file via `ReviewMarkConfiguration.Load()`, collecting all - detectable issues in one pass. -3. Writes each issue to the context via `ReportIssues()` — errors go to - `Context.WriteError()`, warnings to `Context.WriteLine()`. The call to - `Context.WriteError()` is also the mechanism by which the exit code is - implicitly set to 1: `ReportIssues()` calls `Context.WriteError()` for each - error-severity issue, and `Context.WriteError()` sets the internal error flag - that drives `Context.ExitCode`. - -No banner and no summary message are printed. Successful lint produces no output -(silence means the definition file is valid). This keeps the output clean for -integration with linting scripts and CI pipelines. - -### RunToolLogic() - -`Program.RunToolLogic(Context)` is called when none of the early-exit flags -(`--version`, `--help`, `--validate`, `--lint`) are set. It: - -1. Determines the working directory from `context.WorkingDirectory` or - `Directory.GetCurrentDirectory()`. -2. If `context.IndexPaths` is non-empty, calls `RunIndexLogic()` to scan PDF - evidence files and write an `index.json` file. -3. If any definition-based action is requested (`--plan`, `--report`, - `--definition`, or `--elaborate`), calls `RunDefinitionLogic()`. -4. If neither index nor definition actions are requested, prints a usage hint - via `context.WriteLine()`. - -### RunIndexLogic() - -`Program.RunIndexLogic(Context, string directory)` scans PDF files using -`ReviewIndex.Scan(directory, context.IndexPaths)` and writes the resulting -index to `index.json` in the working directory via `ReviewIndex.Save()`. -Warnings from the scan (e.g., PDFs missing required metadata) are forwarded -to `context.WriteLine()`. Progress messages `"Scanning PDF evidence files..."` -and `"Index written to {indexFile}"` are emitted via `context.WriteLine()` -before and after the scan respectively. - -If `ReviewIndex.Scan()` throws an unexpected exception, it propagates unhandled to -`Main()`, which writes `"Unexpected error: {message}"` to `Console.Error` and rethrows. - -### RunDefinitionLogic() - -`Program.RunDefinitionLogic(Context, string directory, string definitionFile)` -handles the definition-based workflow: - -1. Loads the configuration file via `ReviewMarkConfiguration.Load()`. -2. Reports all lint issues via `loadResult.ReportIssues(context)`. -3. If `Configuration` is null after loading, returns immediately. -4. If `--plan` is set, generates the Review Plan Markdown and writes it to - the specified file; wraps I/O failures as `InvalidOperationException`. -5. If `--report` is set, loads the evidence index via `ReviewIndex.Load()`, - generates the Review Report Markdown, and writes it to the specified file. -6. If `--elaborate` is set, calls `config.ElaborateReviewSet()` and writes the - result to the console via `context.WriteLine()`; catches `ArgumentException` - for unknown IDs and calls `context.WriteError($"Error: {ex.Message}")` with the formatted message, - which sets the exit code to 1. - -### HandleIssues() - -`Program.HandleIssues(Context, bool hasIssues, string message)` translates a -boolean issue flag into a context message: - -- If `hasIssues` is false, it does nothing. -- If `context.Enforce` is true, calls `context.WriteError(message)` (sets - exit code to 1). -- Otherwise, calls `context.WriteLine($"Warning: {message}")` (non-fatal). +**`Program.Run(Context context)`** + +- *Parameters*: `Context context` — fully initialized execution context +- *Returns*: void (outcome communicated through `context.ExitCode`) +- *Preconditions*: `context` is not null +- *Postconditions*: Exactly one top-level action has been performed + +Evaluates parsed flags in a fixed priority order and executes the first matching action: + +1. If `context.Version` — print version string via `context.WriteLine(Version)` and return +2. Print application banner (suppressed when `context.Lint` is true) +3. If `context.Help` — print help text and return +4. If `context.Validate` — call `Validation.Run(context)` and return +5. If `context.Lint` — call `RunLintLogic(context)` and return +6. Otherwise — call `RunToolLogic(context)` + +**`Program.RunLintLogic(Context context)`** + +Resolves the definition file path (`--definition` or default `.reviewmark.yaml` under the +working directory), calls `ReviewMarkConfiguration.Load()`, and reports all issues via +`loadResult.ReportIssues(context)`. No banner and no summary are printed; silence means +the definition file is valid. + +**`Program.RunToolLogic(Context context)`** + +Determines the working directory from `context.WorkingDirectory` or +`Directory.GetCurrentDirectory()`. If `context.IndexPaths` is non-empty, calls +`RunIndexLogic()`. If any definition-based action is requested (`--plan`, `--report`, +`--definition`, or `--elaborate`), calls `RunDefinitionLogic()`. If neither is requested, +prints a usage hint. + +**`Program.RunIndexLogic(Context context, string directory)`** + +Calls `ReviewIndex.Scan(directory, context.IndexPaths, onWarning: context.WriteLine)`, +then writes the index to `index.json` via `ReviewIndex.Save()`. Emits progress messages +before and after the scan. Per-file scan failures are forwarded as warnings via +`context.WriteLine()`. Write failures propagate as `InvalidOperationException`. + +**`Program.RunDefinitionLogic(Context context, string directory, string definitionFile)`** + +Loads the configuration via `ReviewMarkConfiguration.Load()`, reports lint issues, and +stops if `Configuration` is null. Then: + +- If `--plan`: generates the Review Plan via `PublishReviewPlan(directory, context.PlanDepth)`, + writes it to `context.PlanFile`, and calls `HandleIssues()`. +- If `--report`: loads the evidence index via `ReviewIndex.Load(config.EvidenceSource)`, + generates the Review Report via `PublishReviewReport(index, directory, context.ReportDepth)`, + writes it to `context.ReportFile`, and calls `HandleIssues()`. +- If `--elaborate`: calls `config.ElaborateReviewSet(context.ElaborateId, directory)` and + writes the result to the console; catches `ArgumentException` for unknown IDs and calls + `context.WriteError($"Error: {ex.Message}")`. + +**`Program.HandleIssues(Context context, bool hasIssues, string message)`** + +If `hasIssues` is false, does nothing. If `context.Enforce` is true, calls +`context.WriteError(message)` (sets exit code to 1). Otherwise calls +`context.WriteLine($"Warning: {message}")` (non-fatal). + +### Error Handling + +| Exception | Source | Handling | +| --------- | ------ | -------- | +| `ArgumentException` | `Context.Create()` — unrecognized or malformed argument | Caught in `Main()`; message to `Console.Error`; return 1 | +| `InvalidOperationException` | `Context.Create()` (log file) or `RunDefinitionLogic()` (file I/O) | Caught in `Main()`; message to `Console.Error`; return 1 | +| `ArgumentException` | `ElaborateReviewSet()` — unknown review-set ID | Caught in `RunDefinitionLogic()`; routed through `context.WriteError()` | +| Any other exception | Unexpected failure | Message to `Console.Error` as `"Unexpected error: {message}"`; rethrown | + +### Dependencies + +- **`Context`** (Cli subsystem) — `Context.Create(string[] args)` constructs the execution + context; `context.WriteLine()`, `context.WriteError()`, and `context.ExitCode` are used + throughout +- **`Validation`** (SelfTest subsystem) — `Validation.Run(context)` invoked for `--validate` +- **`ReviewMarkConfiguration`** (Configuration subsystem) — `Load()`, `PublishReviewPlan()`, + `PublishReviewReport()`, `ElaborateReviewSet()` invoked for lint and definition-based workflows +- **`ReviewIndex`** (Indexing subsystem) — `Scan()`, `Load()`, `Save()` invoked for index + scanning and report generation +- **`PathHelpers`** (Indexing subsystem) — `SafePathCombine()` used to resolve default + definition file and `index.json` paths under the working directory + +### Callers + +N/A — entry point, called by the host environment. `Program.Main()` is the process entry +point and has no callers within the assembly. `Program.Run()` is called only by `Main()` +and by the self-validation test suite in `Validation.cs`. diff --git a/docs/design/review-mark/self-test.md b/docs/design/review-mark/self-test.md index ea3e428..c969dd8 100644 --- a/docs/design/review-mark/self-test.md +++ b/docs/design/review-mark/self-test.md @@ -1,44 +1,51 @@ -## SelfTest Subsystem +## SelfTest -### Overview - -The SelfTest subsystem provides a self-validation framework that allows ReviewMark to -qualify itself as a tool for use in regulated environments. It executes a built-in suite -of integration tests against a temporary working directory and reports the results. - -### Responsibilities - -- Orchestrate the execution of the built-in validation test suite -- Write test results to a TRX or JUnit XML file for ingestion by CI pipelines -- Output a human-readable summary table to the console -- Set the process exit code to reflect overall pass/fail status - -### Units - -| Unit | Source File | Purpose | -|------------|---------------------------|--------------------------------------------------| -| Validation | `SelfTest/Validation.cs` | Self-validation test runner | +The SelfTest subsystem provides a built-in self-validation framework that allows ReviewMark +to qualify itself as a tool for use in regulated environments. -### Entry Point - -`Validation.Run(Context context)` is the single public entry point for this -subsystem. It is called by `Program.Run()` when the `--validate` flag is set. -`Validation.Run` depends on the `Configuration` and `Indexing` subsystems -(to construct a valid runtime environment for each test case) and on the `Cli` -subsystem (to report results through the context). - -The method: - -1. Runs each built-in test case against a temporary working directory. -2. Writes a TRX or JUnit XML results file if `--results` was specified. -3. Writes a human-readable summary table (pass count, fail count, total) to - the console via `context.WriteLine()`. -4. Sets the context exit code to 1 if any test case fails. - -### Error Handling +### Overview -If test infrastructure setup fails (for example, the temporary directory cannot -be created, or a required file cannot be written), the exception propagates -out of `Validation.Run()` to `Program.Main()`, where it is caught by the -third-tier handler, written to `Console.Error`, and rethrown as an unhandled -exception. +The SelfTest subsystem solves the problem of providing repeatable, structured evidence that +the ReviewMark tool operates correctly in a target deployment environment. Its boundary is +narrow: it executes a fixed suite of integration tests against a temporary working directory +and produces a structured results file. It consumes the Configuration and Indexing +subsystems internally to construct valid runtime environments for each test case. + +The subsystem contains a single unit: **Validation** (`SelfTest/Validation.cs`) — the +self-validation test runner. See the *Validation Design* for full unit details. + +### Interfaces + +**`Validation.Run(Context context)`** + +- *Type*: In-process .NET static method +- *Role*: Provider — called by `Program.Run()` when `--validate` is set +- *Contract*: Executes the full self-validation suite; writes a pass/fail summary to the + console via `context.WriteLine()`; writes a TRX or JUnit XML results file when + `context.ResultsFile` is set; calls `context.WriteError()` if any test fails, which sets + `context.ExitCode` to 1 +- *Constraints*: Throws for infrastructure failures (e.g., temporary directory creation); + result-file write failures are caught and logged via `WriteError()` without stopping the + remaining tests + +### Design + +`Validation` is the sole unit. It runs each built-in test case sequentially against an +isolated temporary working directory, ensuring test cases do not interfere with each other +or with the caller's environment. + +1. `Validation.Run()` validates that `context` is not null, then executes each test case + in sequence, writing per-test results inline. +2. Each test is timed; `DemaConsulting.TestResults` accumulates `TestResult` records with + `TestName`, `Outcome`, `StartTime`, `EndTime`, and `Duration`. +3. After all tests complete, a human-readable summary table (pass count, fail count, total) + is written to the console and, if `context.ResultsFile` is set, results are serialized + to TRX (`.trx`) or JUnit XML (`.xml`) format. +4. If any test fails, `context.WriteError()` is called, setting `context.ExitCode` to 1. + +The self-validation suite covers: version display, help display, plan generation, report +generation, index scanning, enforce mode, working directory override, elaborate mode, lint +mode, and the `--depth` flag. + +Infrastructure failures propagate as unhandled exceptions to `Program.Main()`, where the +third-tier handler catches and rethrows them. diff --git a/docs/design/review-mark/self-test/validation.md b/docs/design/review-mark/self-test/validation.md index c5ee633..c604207 100644 --- a/docs/design/review-mark/self-test/validation.md +++ b/docs/design/review-mark/self-test/validation.md @@ -2,54 +2,66 @@ #### Purpose -The `Validation` software unit implements the self-validation framework for -ReviewMark. Self-validation allows the tool to verify its own correct operation -in a target environment, which is a requirement for regulated deployment contexts -where the tool itself is part of a qualified software chain. +`Validation` implements the self-validation framework for ReviewMark. It executes a +built-in suite of integration tests against a temporary working directory and writes +structured results to a TRX or JUnit XML file. Self-validation allows the tool to +verify its own correct operation in a target environment, qualifying it for use in +regulated deployment contexts where the tool is part of a qualified software chain. -#### Validation.Run() +#### Data Model -`Validation.Run(Context)` orchestrates all self-validation tests. It: +N/A — static utility class with no instance state. -1. Validates that `context` is not null -2. Prints a validation header to the console via `Context.WriteLine()` -3. Executes each test case in sequence, writing per-test results inline -4. Writes a summary table to the console -5. Writes results to the configured output file (TRX or JUnit format) if `ResultsFile` is set -6. Calls `Context.WriteError()` when any test fails, which causes `Context.ExitCode` to return a non-zero value +#### Key Methods -#### Test Output Format +**`Validation.Run(Context context)`** -Results are written using the `DemaConsulting.TestResults` library, which supports -both TRX (Visual Studio Test Results) and JUnit XML output formats. The output format -is inferred from the file extension of `ResultsFile`. +Orchestrates all self-validation tests. No return value is produced; the outcome is +communicated through `Context.ExitCode`. -#### Test Coverage +Steps: -The self-validation suite covers the following scenarios: +1. Validates that `context` is not null. +2. Prints a validation header to the console via `Context.WriteLine()`. +3. Executes each test case in sequence, writing per-test results inline. +4. Writes a summary table to the console. +5. Writes structured results to the configured output file (TRX or JUnit format) if + `Context.ResultsFile` is set; the format is inferred from the file extension. +6. Calls `Context.WriteError()` for any test failure, causing `Context.ExitCode` to + return a non-zero value. -- **Version display**: Tool correctly reports its version -- **Help display**: Tool correctly displays help text -- **Plan generation**: Review Plan is generated correctly for a known configuration -- **Report generation**: Review Report is generated correctly for a known configuration -- **Index scanning**: Evidence index is created correctly by scanning a directory -- **Enforce mode**: Tool returns non-zero exit code when enforce mode detects uncovered review sets -- **Working directory override**: Relative paths are resolved correctly when the working directory is overridden -- **Elaborate mode**: File lists are expanded in generated documents when elaborate mode is active -- **Lint mode**: Configuration errors are detected correctly -- **Depth flag**: Tool respects the `--depth` flag, adjusting heading depth in generated documents +The test suite creates a `TestResults` object named `"ReviewMark Self-Validation"` and +covers the following 10 scenarios: -#### Console Output - -In addition to the structured results file, `Validation.Run()` writes a human-readable -summary to the console. The summary includes a table of all tests with their pass/fail -status, followed by detailed output for any failing tests to aid diagnosis. +- **Version display** — tool correctly reports its version +- **Help display** — tool correctly displays help text +- **Plan generation** — Review Plan is generated correctly for a known configuration +- **Report generation** — Review Report is generated correctly for a known configuration +- **Index scanning** — evidence index is created correctly by scanning a directory +- **Enforce mode** — tool returns non-zero exit code when enforce mode detects uncovered review sets +- **Working directory override** — relative paths are resolved correctly when the working directory is overridden +- **Elaborate mode** — file lists are expanded in generated documents when elaborate mode is active +- **Lint mode** — configuration errors are detected correctly +- **Depth flag** — tool respects the `--depth` flag, adjusting heading depth in generated documents #### Error Handling -- If `ResultsFile` has an unsupported file extension, `WriteError` is called and no results - file is written; the validation run continues, but the process is still considered failed - because the logged error causes a non-zero exit code. -- I/O exceptions when writing the results file are caught, logged via `WriteError`, and the - run continues, but the process is still considered failed because the logged error causes - a non-zero exit code. +- Unsupported `ResultsFile` extension: `WriteError` is called and no results file is + written; the run continues but exits with a non-zero code. +- I/O exceptions when writing the results file are caught, logged via `WriteError`, and + the run continues, but the process exits with a non-zero code. + +#### Dependencies + +- **`Context`** (Cli subsystem) — used for all output and to communicate failure via + `WriteError()`, which sets `Context.ExitCode` to a non-zero value +- **`ReviewMarkConfiguration`** (Configuration subsystem) — used internally to construct + valid runtime environments for individual test cases +- **`ReviewIndex`** (Indexing subsystem) — used internally to construct valid runtime + environments for test cases that exercise evidence loading and report generation +- **`DemaConsulting.TestResults`** (OTS) — used for TRX and JUnit XML serialization of + test results when `Context.ResultsFile` is set + +#### Callers + +- **`Program.Run()`** — calls `Validation.Run(Context)` when the `--validate` flag is set diff --git a/docs/design/title.txt b/docs/design/title.txt index d140ba3..8b63277 100644 --- a/docs/design/title.txt +++ b/docs/design/title.txt @@ -1,13 +1,13 @@ --- -title: ReviewMark Design -subtitle: Software Design Document for ReviewMark -author: DEMA Consulting -description: Software Design Document for ReviewMark +title: "ReviewMark Software Design Document" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +description: "Software Design Document for ReviewMark" lang: en-US keywords: - ReviewMark - Design - - Software Architecture + - Software Design Document - .NET - Command-Line Tool --- diff --git a/docs/reqstream/ots/buildmark.yaml b/docs/reqstream/ots/buildmark.yaml index 31d873c..2d3ec46 100644 --- a/docs/reqstream/ots/buildmark.yaml +++ b/docs/reqstream/ots/buildmark.yaml @@ -1,14 +1,11 @@ --- -# BuildMark OTS Software Requirements -# -# Requirements for the BuildMark build documentation tool functionality. - +# BuildMark OTS requirements sections: - title: OTS Software Requirements sections: - title: BuildMark Requirements requirements: - - id: ReviewMark-OTS-BuildMark + - id: BuildMark-Core-GenerateBuildNotes title: BuildMark shall generate build-notes documentation from GitHub Actions metadata. justification: | Release artifacts must include auditable build documentation identifying which diff --git a/docs/reqstream/ots/dema-consulting-test-results.yaml b/docs/reqstream/ots/dema-consulting-test-results.yaml new file mode 100644 index 0000000..199602e --- /dev/null +++ b/docs/reqstream/ots/dema-consulting-test-results.yaml @@ -0,0 +1,32 @@ +--- +# DemaConsulting.TestResults OTS requirements +sections: + - title: OTS Software Requirements + sections: + - title: DemaConsulting.TestResults Requirements + requirements: + - id: ReviewMark-OTS-TestResults-TrxSerialize + title: >- + DemaConsulting.TestResults shall serialize test run results to MSTest TRX format. + justification: | + ReviewMark's self-validation output must be consumable by Azure DevOps, GitHub + Actions, and ReqStream's --enforce pipeline. The TRX format is the standard input + for these tools. DemaConsulting.TestResults provides a ready-made TrxSerializer + that produces well-formed TRX output, avoiding a bespoke XML serializer in the + SelfTest subsystem. + tags: [ots] + tests: + - TrxSerializer_Serialize_CompletedTestRun_ContainsTestRunElement + - Validation_Run_WithTrxResultsFile_WritesFile + - id: ReviewMark-OTS-TestResults-JUnitSerialize + title: >- + DemaConsulting.TestResults shall serialize test run results to JUnit XML format. + justification: | + ReviewMark's self-validation must also support JUnit XML output for CI environments + that consume the JUnit format. DemaConsulting.TestResults provides a JUnitSerializer + alongside TrxSerializer, enabling both output formats from a single library without + additional dependencies. + tags: [ots] + tests: + - JUnitSerializer_Serialize_CompletedTestRun_ContainsTestSuitesElement + - Validation_Run_WithXmlResultsFile_WritesFile diff --git a/docs/reqstream/ots/fileassert.yaml b/docs/reqstream/ots/fileassert.yaml index f4eee86..2169424 100644 --- a/docs/reqstream/ots/fileassert.yaml +++ b/docs/reqstream/ots/fileassert.yaml @@ -1,15 +1,12 @@ --- -# FileAssert OTS Software Requirements -# -# Requirements for the FileAssert document assertion tool functionality. - +# FileAssert OTS requirements sections: - title: OTS Software Requirements sections: - title: FileAssert Requirements requirements: - id: ReviewMark-OTS-FileAssert - title: FileAssert shall be operationally available and confirmed functional through self-validation. + title: FileAssert shall confirm operational availability by successfully completing self-validation. justification: | The documentation build pipeline produces HTML and PDF artifacts from multiple tools. Without automated assertion, undetected truncation, empty files, or missing content diff --git a/docs/reqstream/ots/microsoft-extensions-file-system-globbing.yaml b/docs/reqstream/ots/microsoft-extensions-file-system-globbing.yaml new file mode 100644 index 0000000..d57fb87 --- /dev/null +++ b/docs/reqstream/ots/microsoft-extensions-file-system-globbing.yaml @@ -0,0 +1,38 @@ +--- +# Microsoft.Extensions.FileSystemGlobbing OTS requirements +sections: + - title: OTS Software Requirements + sections: + - title: Microsoft.Extensions.FileSystemGlobbing Requirements + requirements: + - id: ReviewMark-OTS-FileSystemGlobbing-WildcardMatching + title: >- + Microsoft.Extensions.FileSystemGlobbing shall match files using ** double-wildcard patterns. + justification: | + ReviewMark resolves the set of governed files from the ordered include patterns + in .reviewmark.yaml. Microsoft.Extensions.FileSystemGlobbing provides the + ** double-wildcard semantics and cross-platform path handling required by these + patterns, avoiding the need to reimplement glob semantics in application code. + tags: [ots] + tests: + - Matcher_GetResultsInFullPath_DoubleWildcard_MatchesFilesInSubdirectories + - Matcher_GetResultsInFullPath_SingleWildcard_MatchesFilesInDirectory + - GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles + - GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching + - GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles + - GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles + - GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList + - GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator + - id: ReviewMark-OTS-FileSystemGlobbing-ExclusionPrefix + title: >- + Microsoft.Extensions.FileSystemGlobbing shall exclude files matching a !-prefixed pattern. + justification: | + ReviewMark allows governing file sets to exclude specific paths using !-prefixed + patterns in .reviewmark.yaml. Microsoft.Extensions.FileSystemGlobbing's exclude + support enables these exclusion semantics without custom filtering logic. + tags: [ots] + tests: + - Matcher_GetResultsInFullPath_ExcludePattern_OmitsMatchingFiles + - GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles + - GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles + - GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles diff --git a/docs/reqstream/ots/pandoc.yaml b/docs/reqstream/ots/pandoc.yaml index f46f385..5b0887e 100644 --- a/docs/reqstream/ots/pandoc.yaml +++ b/docs/reqstream/ots/pandoc.yaml @@ -1,15 +1,14 @@ --- -# Pandoc OTS Software Requirements -# -# Requirements for the Pandoc document conversion tool functionality. - +# Pandoc OTS requirements sections: - title: OTS Software Requirements sections: - title: Pandoc Requirements requirements: - - id: ReviewMark-OTS-Pandoc - title: Pandoc shall convert Markdown documents to valid HTML. + - id: ReviewMark-Pandoc-ConvertMarkdown + title: >- + Pandoc shall convert Markdown documents to HTML containing a valid title + element and expected document content. justification: | DemaConsulting.PandocTool converts Markdown source documents to HTML as part of the documentation build pipeline. FileAssert validates that each generated HTML file diff --git a/docs/reqstream/ots/pdfsharp.yaml b/docs/reqstream/ots/pdfsharp.yaml new file mode 100644 index 0000000..a04224f --- /dev/null +++ b/docs/reqstream/ots/pdfsharp.yaml @@ -0,0 +1,24 @@ +--- +# PDFsharp OTS requirements +sections: + - title: OTS Software Requirements + sections: + - title: PDFsharp Requirements + requirements: + - id: ReviewMark-OTS-PDFsharp-ReadMetadata + title: >- + PDFsharp shall provide access to the Keywords metadata field of a PDF document opened in import mode. + justification: | + ReviewMark reads the review identifier and content fingerprint from the Keywords field + of each evidence PDF. PDFsharp's PdfDocumentOpenMode.Import provides lightweight + read-only access to the document information dictionary without loading full content + streams, allowing ReviewMark to scan large PDF collections efficiently. + tags: [ots] + tests: + - PdfReader_Open_ImportMode_ExposesKeywordsField + - PdfReader_Open_ImportMode_NoKeywords_ReturnsNullOrEmpty + - ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex + - ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning + - ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning + - ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning + - ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries diff --git a/docs/reqstream/ots/reqstream.yaml b/docs/reqstream/ots/reqstream.yaml index 48bb6e0..1e51cad 100644 --- a/docs/reqstream/ots/reqstream.yaml +++ b/docs/reqstream/ots/reqstream.yaml @@ -1,8 +1,5 @@ --- -# ReqStream OTS Software Requirements -# -# Requirements for the ReqStream requirements traceability tool functionality. - +# ReqStream OTS requirements sections: - title: OTS Software Requirements sections: diff --git a/docs/reqstream/ots/reviewmark.yaml b/docs/reqstream/ots/reviewmark.yaml index 829b20c..63c7c44 100644 --- a/docs/reqstream/ots/reviewmark.yaml +++ b/docs/reqstream/ots/reviewmark.yaml @@ -1,8 +1,5 @@ --- -# ReviewMark OTS Software Requirements -# -# Requirements for the ReviewMark file-review evidence management tool. - +# ReviewMark OTS requirements sections: - title: OTS Software Requirements sections: @@ -20,7 +17,7 @@ sections: immediately visible without manual inspection. tags: [ots] tests: - - ReviewMark_ValidateFlag_Invoked_RunsValidation + - ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport - id: ReviewMark-OTS-ReviewMark-Enforce title: >- @@ -36,7 +33,7 @@ sections: tests: - ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero - - id: ReviewMark-OTS-ReviewMark-Elaborate + - id: ReviewMark-OTS-ReviewMark-Plan title: >- ReviewMark shall generate a review plan document listing all review-sets and the files governed by each. @@ -51,12 +48,12 @@ sections: - id: ReviewMark-OTS-ReviewMark-Report title: >- - ReviewMark shall generate a review report document summarising the + ReviewMark shall generate a review report document summarizing the evidence status for each review-set. justification: | Audit evidence must show not only that reviews were planned but that they were completed and the outcomes recorded. ReviewMark produces a review report from the - evidence source, summarising the status of each review-set and providing a + evidence source, summarizing the status of each review-set and providing a consolidated view of review completeness for release sign-off. tags: [ots] tests: diff --git a/docs/reqstream/ots/sarifmark.yaml b/docs/reqstream/ots/sarifmark.yaml index 4216662..5b1349f 100644 --- a/docs/reqstream/ots/sarifmark.yaml +++ b/docs/reqstream/ots/sarifmark.yaml @@ -1,8 +1,5 @@ --- -# SarifMark OTS Software Requirements -# -# Requirements for the SarifMark SARIF report processing tool functionality. - +# SarifMark OTS requirements sections: - title: OTS Software Requirements sections: diff --git a/docs/reqstream/ots/sonarmark.yaml b/docs/reqstream/ots/sonarmark.yaml index 6fb3ba1..b19bd36 100644 --- a/docs/reqstream/ots/sonarmark.yaml +++ b/docs/reqstream/ots/sonarmark.yaml @@ -1,8 +1,5 @@ --- -# SonarMark OTS Software Requirements -# -# Requirements for the SonarMark quality reporting tool functionality. - +# SonarMark OTS requirements sections: - title: OTS Software Requirements sections: diff --git a/docs/reqstream/ots/versionmark.yaml b/docs/reqstream/ots/versionmark.yaml index 6642cf5..e56f79c 100644 --- a/docs/reqstream/ots/versionmark.yaml +++ b/docs/reqstream/ots/versionmark.yaml @@ -1,24 +1,30 @@ --- -# VersionMark OTS Software Requirements -# -# Requirements for the VersionMark version tracking tool functionality. - +# VersionMark OTS requirements sections: - title: OTS Software Requirements sections: - title: VersionMark Requirements requirements: - - id: ReviewMark-OTS-VersionMark - title: VersionMark shall publish captured tool-version information. + - id: ReviewMark-OTS-VersionMark-Capture + title: VersionMark shall capture tool version metadata. justification: | Compliance traceability and audit evidence require that the versions of all tools used in the build pipeline are documented in the release artifacts. DemaConsulting.VersionMark captures version metadata for each pipeline tool - and publishes a human-readable versions document included in the build notes. - This provides auditors and reviewers with a permanent record of which tool - versions were used to produce each release, satisfying traceability obligations - without manual data-entry. + and writes a JSON capture file. This provides auditors and reviewers with a + permanent record of which tool versions were used to produce each release, + satisfying traceability obligations without manual data-entry. tags: [ots] tests: - VersionMark_CapturesVersions + - id: ReviewMark-OTS-VersionMark-Report + title: VersionMark shall generate a markdown versions report. + justification: | + The build notes release artifact must include a human-readable summary of all + tool versions captured during the pipeline run. DemaConsulting.VersionMark + aggregates the JSON capture files produced by each pipeline stage and publishes + a single markdown versions document included in the build notes. This satisfies + the audit requirement for a consolidated, human-readable version record. + tags: [ots] + tests: - VersionMark_GeneratesMarkdownReport diff --git a/docs/reqstream/ots/weasyprint.yaml b/docs/reqstream/ots/weasyprint.yaml index 0a2bf8f..a58aabd 100644 --- a/docs/reqstream/ots/weasyprint.yaml +++ b/docs/reqstream/ots/weasyprint.yaml @@ -1,8 +1,5 @@ --- -# WeasyPrint OTS Software Requirements -# -# Requirements for the WeasyPrint PDF generation tool functionality. - +# WeasyPrint OTS requirements sections: - title: OTS Software Requirements sections: diff --git a/docs/reqstream/ots/xunit.yaml b/docs/reqstream/ots/xunit.yaml index 5f7593f..6d94766 100644 --- a/docs/reqstream/ots/xunit.yaml +++ b/docs/reqstream/ots/xunit.yaml @@ -1,8 +1,5 @@ --- -# xUnit OTS Software Requirements -# -# Requirements for the xUnit testing framework functionality. - +# xUnit OTS requirements sections: - title: OTS Software Requirements sections: diff --git a/docs/reqstream/ots/yamldotnet.yaml b/docs/reqstream/ots/yamldotnet.yaml new file mode 100644 index 0000000..585a081 --- /dev/null +++ b/docs/reqstream/ots/yamldotnet.yaml @@ -0,0 +1,44 @@ +--- +# YamlDotNet OTS requirements +sections: + - title: OTS Software Requirements + sections: + - title: YamlDotNet Requirements + requirements: + - id: ReviewMark-OTS-YamlDotNet-Deserialize + title: >- + YamlDotNet shall deserialize .reviewmark.yaml configuration files into typed C# objects. + justification: | + ReviewMark reads its configuration from a .reviewmark.yaml file. YamlDotNet provides + the deserialization path from raw YAML text to strongly-typed C# objects, removing the + need for a hand-written YAML parser. Correct deserialization is required for all + subsequent configuration-driven behavior. + tags: [ots] + tests: + - Deserializer_Deserialize_WellFormedYaml_MapsToTypedObject + - ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration + - ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly + - ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly + - ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly + - ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly + - id: ReviewMark-OTS-YamlDotNet-ErrorHandling + title: >- + YamlDotNet shall raise a YamlException on malformed YAML input. + justification: | + ReviewMark must report a descriptive error when the configuration file is malformed. + YamlDotNet's YamlException is caught at the Configuration subsystem boundary and + converted to a user-visible error message, preventing silent misconfiguration. + tags: [ots] + tests: + - Deserializer_Deserialize_MalformedYaml_ThrowsYamlException + - ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue + - id: ReviewMark-OTS-YamlDotNet-UnknownKeys + title: >- + YamlDotNet shall silently ignore unrecognized YAML keys without raising an error. + justification: | + ReviewMark uses IgnoreUnmatchedProperties to allow forward-compatible configuration + extensions. Unknown YAML keys introduced in future configuration versions must not + cause deserialization failures in older tool versions. + tags: [ots] + tests: + - Deserializer_Deserialize_UnknownKeys_DoesNotThrow diff --git a/docs/reqstream/review-mark.yaml b/docs/reqstream/review-mark.yaml index d2d4321..fe312d4 100644 --- a/docs/reqstream/review-mark.yaml +++ b/docs/reqstream/review-mark.yaml @@ -1,13 +1,7 @@ --- -# ReviewMark System-Level Requirements -# -# PURPOSE: -# - Define system-level requirements describing what end-users need the ReviewMark tool to provide -# - These requirements capture the externally visible behavior of the complete ReviewMark system -# - Unit-level requirements (per-class behavior) are in the individual *-requirements.yaml files - +# ReviewMark system-level requirements sections: - - title: System-Level Requirements + - title: ReviewMark Requirements requirements: - id: ReviewMark-System-ReviewPlan title: >- @@ -52,7 +46,22 @@ sections: - ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero children: - ReviewMark-Cmd-Enforce - - ReviewMark-Program-HandleIssues + - ReviewMark-Program-HandleIssues-Enforce + - ReviewMark-Program-HandleIssues-Warn + + - id: ReviewMark-System-Credentials + title: The tool shall support authenticated URL evidence sources via username-env and password-env credentials. + justification: | + Teams that host their evidence index on a secured HTTP(S) server must be able to + provide credentials so that ReviewMark can download the index without manual + intervention. Reading credentials from environment variables (username-env and + password-env) avoids storing secrets in the configuration file and integrates + cleanly with CI/CD secret management. + tests: + - ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput + - ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly + children: + - ReviewMark-Index-EvidenceSource - id: ReviewMark-System-IndexScan title: The tool shall scan PDF evidence files and write an index.json when the --index flag is provided. @@ -67,6 +76,22 @@ sections: - ReviewMark-Cmd-Index - ReviewMark-Indexing-SafePathCombine - ReviewMark-Indexing-ScanPdfEvidence + - ReviewMark-Indexing-Save + - ReviewMark-Program-Index + + - id: ReviewMark-System-IndexScan-Repeat + title: >- + The tool shall accept multiple --index flags in a single invocation to scan + evidence organized across multiple directories. + justification: | + Review evidence PDFs may be organized across several sub-directories. Supporting + repeated --index flags allows users to aggregate evidence from all relevant + locations into a single index.json without requiring separate tool invocations + or manual merging. + tests: + - ReviewMark_IndexFlag_WithRepeat_ScansAllPaths + children: + - ReviewMark-Cmd-Index - ReviewMark-Program-Index - id: ReviewMark-System-Validate @@ -80,6 +105,8 @@ sections: children: - ReviewMark-Cmd-Validate - ReviewMark-SelfTest-Qualification + - ReviewMark-SelfTest-ExitCodeOnFailure + - ReviewMark-SelfTest-ConsoleSummary - id: ReviewMark-System-Version title: The tool shall display the version string when the --version flag is provided. @@ -114,13 +141,14 @@ sections: - id: ReviewMark-System-Elaborate title: The tool shall print a Markdown elaboration of a review set when --elaborate is provided. justification: | - When preparing for a review, the reviewer needs the review set ID, its current - fingerprint, and the full sorted list of files to be reviewed. + When preparing for a review, the reviewer needs the review set ID, its title, + its current fingerprint, and the full sorted list of files to be reviewed. tests: - ReviewMark_ElaborateFlag_WithValidId_OutputsElaboration children: - ReviewMark-Cmd-Elaborate - ReviewMark-Configuration-Elaboration + - ReviewMark-Configuration-ElaborateUnknownId - id: ReviewMark-System-LintValidation title: The tool shall validate the definition file and report only issue messages when --lint is provided. @@ -130,6 +158,7 @@ sections: summary) keeps the output suitable for direct use in linting scripts and CI pipelines. tests: - ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput + - ReviewMark_LintFlag_WithInvalidConfig_ReportsOnlyIssueMessages children: - ReviewMark-Cmd-Lint - ReviewMark-Program-LintVerbosity @@ -168,23 +197,43 @@ sections: - id: ReviewMark-System-Depth title: >- - The tool shall apply the --depth flag as the default Markdown heading - depth for all generated documents, unless overridden by --plan-depth - or --report-depth. + The tool shall apply the --depth flag as the default Markdown heading depth + for all generated documents. justification: | Allows users to set the heading depth once and have it apply to the review plan, - review report, and self-validation report, unless a more specific flag is provided. - Default depth is 1 when not specified. - Note: These three flags (--depth, --plan-depth, --report-depth) are grouped in a single - requirement because they form a coherent depth-override mechanism; the per-command flags - are only meaningful in the context of the default, and all three are tested together in - each depth test scenario. + review report, and self-validation report. Default depth is 1 when not specified. tests: - ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth - ReviewMark_DepthFlag_WithValidate_SetsValidationHeadingDepth children: - ReviewMark-Cmd-Depth + + - id: ReviewMark-System-PlanDepth + title: >- + The tool shall apply the --plan-depth flag to override the Markdown heading depth + for the review plan document only. + justification: | + Allows the review plan to be embedded at any heading level within a larger Markdown + document, overriding the --depth default when specified. Default depth is 1 when + neither --plan-depth nor --depth is specified. + tests: + - ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth + - ReviewMark_PlanDepthFlag_Invoked_OverridesPlanHeadingOnly + children: - ReviewMark-Cmd-PlanDepth + + - id: ReviewMark-System-ReportDepth + title: >- + The tool shall apply the --report-depth flag to override the Markdown heading depth + for the review report document only. + justification: | + Allows the review report to be embedded at any heading level within a larger Markdown + document, overriding the --depth default when specified. Default depth is 1 when + neither --report-depth nor --depth is specified. + tests: + - ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth + - ReviewMark_ReportDepthFlag_Invoked_OverridesReportHeadingOnly + children: - ReviewMark-Cmd-ReportDepth - id: ReviewMark-System-InvalidArgs @@ -198,7 +247,8 @@ sections: - ReviewMark-Cmd-InvalidArgs - ReviewMark-Cmd-ErrorOutput - ReviewMark-Cmd-ExitCode - - ReviewMark-Program-EntryPoint + - ReviewMark-Program-ParseArguments + - ReviewMark-Program-ExitCode - id: ReviewMark-System-Results title: The tool shall write validation results to a standard test result file when --results is provided. @@ -210,7 +260,8 @@ sections: - ReviewMark_ValidateFlag_WithXmlResultsPath_GeneratesJUnitFile children: - ReviewMark-Cmd-Results - - ReviewMark-SelfTest-ResultsOutput + - ReviewMark-SelfTest-ResultsOutput-Trx + - ReviewMark-SelfTest-ResultsOutput-Junit - id: ReviewMark-System-Definition title: The tool shall support a --definition flag to specify the path to the definition YAML file. @@ -223,3 +274,36 @@ sections: - ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan children: - ReviewMark-Cmd-Definition + - ReviewMark-Configuration-MalformedYaml + + - id: ReviewMark-System-CrossPlatform + title: The tool shall run on Windows, Linux, and macOS with .NET 8, 9, and 10. + justification: | + DEMA Consulting tools must run on all major operating systems and current .NET + runtimes so that teams can use ReviewMark regardless of their development or CI/CD + platform. Supporting .NET 8 (LTS), 9, and 10 ensures long-term compatibility and + access to the latest runtime features. + tests: + - ReviewMark_VersionFlag_Invoked_OutputsVersion + children: + - ReviewMark-Platform-Windows + - ReviewMark-Platform-Linux + - ReviewMark-Platform-MacOS + - ReviewMark-Platform-Net8 + - ReviewMark-Platform-Net9 + - ReviewMark-Platform-Net10 + + - id: ReviewMark-System-ExecutionContext + title: The tool shall parse command-line arguments into a structured execution context and maintain execution state + for the duration of the operation. + justification: | + All downstream processing must read from a single, validated source of truth + derived from the command-line arguments, and all output channels and exit-code + state must be managed consistently for the full duration of an invocation. This + ensures predictable, auditable behavior in both interactive and CI/CD environments. + tests: + - ReviewMark_VersionFlag_Invoked_OutputsVersion + - ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError + children: + - ReviewMark-Cmd-Context + - ReviewMark-Cmd-ExecutionState diff --git a/docs/reqstream/review-mark/cli.yaml b/docs/reqstream/review-mark/cli.yaml new file mode 100644 index 0000000..54d2cdf --- /dev/null +++ b/docs/reqstream/review-mark/cli.yaml @@ -0,0 +1,229 @@ +--- +# Cli subsystem requirements +sections: + - title: ReviewMark Requirements + sections: + - title: Cli Requirements + requirements: + - id: ReviewMark-Cmd-Context + title: The CLI subsystem shall accept and parse command-line arguments into a structured execution context. + justification: | + Command-line arguments must be parsed into a consistent execution state so that all + downstream processing reads from a single, validated source of truth. This approach + is used consistently across DEMA Consulting DotNet Tools. + tests: + - Cli_Context_NoArgs_Parsed + children: [ReviewMark-Context-Parsing] + + - id: ReviewMark-Cmd-ExecutionState + title: >- + The CLI subsystem shall maintain execution state (output channels, exit code) + for the duration of the operation. + justification: | + A single context object owns stdout, the optional log file, and the process exit + code so that all output from any subsystem is routed consistently and the final + exit code reflects all errors encountered during the run. + tests: + - Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode + children: [ReviewMark-Context-Output] + + - id: ReviewMark-Cmd-Version + title: The tool shall support -v and --version flags to display version information. + justification: | + Users need to quickly identify the version of the tool they are using for + troubleshooting and compatibility verification. + tests: + - Cli_VersionFlag_FlagSupplied_OutputsVersionOnly + children: [ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Help + title: The tool shall support -?, -h, and --help flags to display usage information. + justification: | + Users need access to command-line usage documentation without requiring + external resources. + tests: + - Cli_HelpFlag_FlagSupplied_OutputsUsageInformation + children: [ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Silent + title: The tool shall support --silent flag to suppress console output. + justification: | + Enables automated scripts and CI/CD pipelines to run the tool without + cluttering output logs. + tests: + - Cli_SilentFlag_FlagSupplied_SuppressesOutput + children: [ReviewMark-Context-Output] + + - id: ReviewMark-Cmd-Validate + title: The tool shall support --validate flag to run self-validation tests. + justification: | + Provides a built-in mechanism to verify the tool is functioning correctly + in the deployment environment. + tests: + - Cli_ValidateFlag_FlagSupplied_RunsValidation + children: [ReviewMark-Program-Dispatch, ReviewMark-Validation-Run] + + - id: ReviewMark-Cmd-Results + title: The tool shall support --results flag to write validation results in TRX or JUnit format. + justification: | + Enables integration with CI/CD systems that expect standard test result formats. + tests: + - Cli_ResultsFlag_FlagSupplied_GeneratesTrxFile + children: [ReviewMark-Validation-ResultsFile] + + - id: ReviewMark-Cmd-Log + title: The tool shall support --log flag to write output to a log file. + justification: | + Provides persistent logging for debugging and audit trails. + tests: + - Cli_LogFlag_FlagSupplied_WritesOutputToFile + children: [ReviewMark-Context-Output, ReviewMark-Context-LogFileError] + + - id: ReviewMark-Cmd-Depth + title: The tool shall support --depth flag to set the default Markdown heading depth. + justification: | + Allows users to specify a default Markdown heading depth on the command line. + Default depth is 1 when not specified. + tests: + - Cli_DepthFlag_FlagSupplied_SetsDefaultHeadingDepth + - Cli_DepthFlag_BelowMinimum_ThrowsArgumentException + - Cli_DepthFlag_AboveMaximum_ThrowsArgumentException + children: [ReviewMark-Context-Parsing] + + - id: ReviewMark-Cmd-ErrorOutput + title: The tool shall write error messages to stderr. + justification: | + Error messages must be written to stderr so they remain visible to the user + without polluting stdout, which consumers may pipe or redirect for data capture. + tests: + - Cli_ErrorOutput_UnknownArg_WritesToStderr + children: [ReviewMark-Context-Output] + + - id: ReviewMark-Cmd-InvalidArgs + title: The tool shall reject unknown or malformed command-line arguments with a descriptive error. + justification: | + Providing clear feedback for invalid arguments helps users quickly correct + mistakes and prevents silent misconfiguration. + tests: + - Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode + children: [ReviewMark-Context-Parsing] + + - id: ReviewMark-Cmd-ExitCode + title: The tool shall return a non-zero exit code on failure. + justification: | + Callers (scripts, CI/CD pipelines) must be able to detect failure conditions + programmatically via the process exit code. + tests: + - Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode + children: [ReviewMark-Context-Output] + + - id: ReviewMark-Cmd-Definition + title: The tool shall support --definition flag to specify the definition YAML file. + justification: | + Users must be able to specify the path to the .reviewmark.yaml definition file, + which configures needs-review patterns, evidence source, and review set definitions. + tests: + - Cli_DefinitionFlag_FlagSupplied_LoadsSpecifiedFile + children: [ReviewMark-Config-Loading, ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Plan + title: The tool shall support --plan flag to write the review plan to a Markdown file. + justification: | + Enables automated generation of a review plan document that lists all review sets + and coverage status, suitable for inclusion in release documentation. + tests: + - Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan + children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-PlanDepth + title: The tool shall support --plan-depth flag to set the Markdown heading depth for the review plan. + justification: | + Allows the review plan to be embedded at any heading level within a larger + Markdown document, overriding --depth when specified. Default depth is 1 when + neither --plan-depth nor --depth is specified. + tests: + - Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth + - Cli_PlanDepthFlag_BelowMinimum_ThrowsArgumentException + - Cli_PlanDepthFlag_AboveMaximum_ThrowsArgumentException + children: [ReviewMark-Context-Parsing] + + - id: ReviewMark-Cmd-Report + title: The tool shall support --report flag to write the review report to a Markdown file. + justification: | + Enables automated generation of a review report document showing the current + status of each review set against the evidence index, suitable for release documentation. + tests: + - Cli_ReportFlag_FlagSupplied_GeneratesReviewReport + children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-ReportDepth + title: The tool shall support --report-depth flag to set the Markdown heading depth for the review report. + justification: | + Allows the review report to be embedded at any heading level within a larger + Markdown document, overriding --depth when specified. Default depth is 1 when + neither --report-depth nor --depth is specified. + tests: + - Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth + - Cli_ReportDepthFlag_BelowMinimum_ThrowsArgumentException + - Cli_ReportDepthFlag_AboveMaximum_ThrowsArgumentException + children: [ReviewMark-Context-Parsing] + + - id: ReviewMark-Cmd-Index + title: The tool shall support --index flag to scan PDF evidence files matching a glob path and write index.json. + justification: | + Provides a mechanism to regenerate the review evidence index from scanned PDF + files, reading embedded metadata from each PDF's Keywords field to populate + the index with review IDs, fingerprints, dates, results, and file names. + tests: + - Cli_IndexFlag_FlagSupplied_CreatesIndexJson + children: [ReviewMark-Index-PdfParsing, ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Enforce + title: The tool shall support --enforce flag to exit with a non-zero code when there are review issues. + justification: | + Enables CI/CD pipelines to block downstream stages when review sets are failed, + stale, or missing, or when files requiring review are not covered by any review-set. + Without --enforce the tool generates the plan and report but exits with code 0. + tests: + - Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent + children: [ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Dir + title: The tool shall support --dir flag to set the working directory for file operations. + justification: | + Allows users to target an evidence store or project directory without changing + the process working directory, enabling consistent scripting and CI/CD usage + without requiring a cd command before invoking the tool. + tests: + - Cli_DirFlag_FlagSupplied_SetsWorkingDirectory + children: [ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Elaborate + title: The tool shall support --elaborate flag to print a Markdown elaboration of a review set. + justification: | + When preparing for a review, the reviewer needs the review set ID, its current + fingerprint, and the full sorted list of files to be reviewed. The --elaborate + command provides this information formatted as Markdown so it can be copied + directly into review documentation. + tests: + - Cli_ElaborateFlag_ValidId_OutputsElaboration + children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] + + - id: ReviewMark-Cmd-Lint + title: The CLI shall validate the definition file and report issue messages on failure. + justification: | + Users need a way to verify that the .reviewmark.yaml configuration file is valid + before running the main tool, providing clear error messages about the cause and + location of any issues. + tests: + - Cli_LintFlag_InvalidConfig_ReportsIssueMessages + children: [ReviewMark-Config-Loading, ReviewMark-Program-Dispatch, ReviewMark-Program-LintVerbosity] + + - id: ReviewMark-Cmd-LintSilence + title: The CLI shall produce no output when validation succeeds. + justification: | + Suppressing the banner and summary output on success (silence-on-success) makes + lint suitable for direct use in scripts and CI pipelines. + tests: + - Cli_LintFlag_ValidConfig_ReportsSuccess + children: [ReviewMark-Program-LintVerbosity] diff --git a/docs/reqstream/review-mark/cli/cli.yaml b/docs/reqstream/review-mark/cli/cli.yaml deleted file mode 100644 index 25bc55e..0000000 --- a/docs/reqstream/review-mark/cli/cli.yaml +++ /dev/null @@ -1,228 +0,0 @@ ---- -# Command-Line Interface Subsystem Requirements -# -# PURPOSE: -# - Define requirements for the ReviewMark command-line interface subsystem -# - The CLI subsystem spans Context.cs (argument parsing) and Program.cs (orchestration) -# - Subsystem requirements describe the externally visible CLI behavior - -sections: - - title: Command-Line Interface Subsystem Requirements - requirements: - - id: ReviewMark-Cmd-Context - title: The CLI subsystem shall accept and parse command-line arguments into a structured execution context. - justification: | - Command-line arguments must be parsed into a consistent execution state so that all - downstream processing reads from a single, validated source of truth. This approach - is used consistently across DEMA Consulting DotNet Tools. - tests: - - Cli_Context_NoArgs_Parsed - children: [ReviewMark-Context-Parsing] - - - id: ReviewMark-Cmd-ExecutionState - title: >- - The CLI subsystem shall maintain execution state (output channels, exit code) - for the duration of the operation. - justification: | - A single context object owns stdout, the optional log file, and the process exit - code so that all output from any subsystem is routed consistently and the final - exit code reflects all errors encountered during the run. - tests: - - Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode - children: [ReviewMark-Context-Output] - - - id: ReviewMark-Cmd-Version - title: The tool shall support -v and --version flags to display version information. - justification: | - Users need to quickly identify the version of the tool they are using for - troubleshooting and compatibility verification. - tests: - - Cli_VersionFlag_FlagSupplied_OutputsVersionOnly - children: [ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Help - title: The tool shall support -?, -h, and --help flags to display usage information. - justification: | - Users need access to command-line usage documentation without requiring - external resources. - tests: - - Cli_HelpFlag_FlagSupplied_OutputsUsageInformation - children: [ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Silent - title: The tool shall support --silent flag to suppress console output. - justification: | - Enables automated scripts and CI/CD pipelines to run the tool without - cluttering output logs. - tests: - - Cli_SilentFlag_FlagSupplied_SuppressesOutput - children: [ReviewMark-Context-Output] - - - id: ReviewMark-Cmd-Validate - title: The tool shall support --validate flag to run self-validation tests. - justification: | - Provides a built-in mechanism to verify the tool is functioning correctly - in the deployment environment. - tests: - - Cli_ValidateFlag_FlagSupplied_RunsValidation - children: [ReviewMark-Program-Dispatch, ReviewMark-Validation-Run] - - - id: ReviewMark-Cmd-Results - title: The tool shall support --results flag to write validation results in TRX or JUnit format. - justification: | - Enables integration with CI/CD systems that expect standard test result formats. - tests: - - Cli_ResultsFlag_FlagSupplied_GeneratesTrxFile - children: [ReviewMark-Validation-ResultsFile] - - - id: ReviewMark-Cmd-Log - title: The tool shall support --log flag to write output to a log file. - justification: | - Provides persistent logging for debugging and audit trails. - tests: - - Cli_LogFlag_FlagSupplied_WritesOutputToFile - children: [ReviewMark-Context-Output, ReviewMark-Context-LogFileError] - - - id: ReviewMark-Cmd-Depth - title: The tool shall support --depth flag to set the default Markdown heading depth. - justification: | - Allows users to specify a default Markdown heading depth on the command line. - Default depth is 1 when not specified. - tests: - - Cli_DepthFlag_FlagSupplied_SetsDefaultHeadingDepth - - Cli_DepthFlag_BelowMinimum_ThrowsArgumentException - - Cli_DepthFlag_AboveMaximum_ThrowsArgumentException - children: [ReviewMark-Context-Parsing] - - - id: ReviewMark-Cmd-ErrorOutput - title: The tool shall write error messages to stderr. - justification: | - Error messages must be written to stderr so they remain visible to the user - without polluting stdout, which consumers may pipe or redirect for data capture. - tests: - - Cli_ErrorOutput_UnknownArg_WritesToStderr - children: [ReviewMark-Context-Output] - - - id: ReviewMark-Cmd-InvalidArgs - title: The tool shall reject unknown or malformed command-line arguments with a descriptive error. - justification: | - Providing clear feedback for invalid arguments helps users quickly correct - mistakes and prevents silent misconfiguration. - tests: - - Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode - children: [ReviewMark-Context-Parsing] - - - id: ReviewMark-Cmd-ExitCode - title: The tool shall return a non-zero exit code on failure. - justification: | - Callers (scripts, CI/CD pipelines) must be able to detect failure conditions - programmatically via the process exit code. - tests: - - Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode - children: [ReviewMark-Context-Output] - - - id: ReviewMark-Cmd-Definition - title: The tool shall support --definition flag to specify the definition YAML file. - justification: | - Users must be able to specify the path to the .reviewmark.yaml definition file, - which configures needs-review patterns, evidence source, and review set definitions. - tests: - - Cli_DefinitionFlag_FlagSupplied_LoadsSpecifiedFile - children: [ReviewMark-Config-Loading, ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Plan - title: The tool shall support --plan flag to write the review plan to a Markdown file. - justification: | - Enables automated generation of a review plan document that lists all review sets - and coverage status, suitable for inclusion in release documentation. - tests: - - Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan - children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-PlanDepth - title: The tool shall support --plan-depth flag to set the Markdown heading depth for the review plan. - justification: | - Allows the review plan to be embedded at any heading level within a larger - Markdown document, overriding --depth when specified. Default depth is 1 when - neither --plan-depth nor --depth is specified. - tests: - - Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth - - Cli_DepthFlag_BelowMinimum_ThrowsArgumentException - - Cli_DepthFlag_AboveMaximum_ThrowsArgumentException - children: [ReviewMark-Context-Parsing] - - - id: ReviewMark-Cmd-Report - title: The tool shall support --report flag to write the review report to a Markdown file. - justification: | - Enables automated generation of a review report document showing the current - status of each review set against the evidence index, suitable for release documentation. - tests: - - Cli_ReportFlag_FlagSupplied_GeneratesReviewReport - children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-ReportDepth - title: The tool shall support --report-depth flag to set the Markdown heading depth for the review report. - justification: | - Allows the review report to be embedded at any heading level within a larger - Markdown document, overriding --depth when specified. Default depth is 1 when - neither --report-depth nor --depth is specified. - tests: - - Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth - - Cli_DepthFlag_BelowMinimum_ThrowsArgumentException - - Cli_DepthFlag_AboveMaximum_ThrowsArgumentException - children: [ReviewMark-Context-Parsing] - - - id: ReviewMark-Cmd-Index - title: The tool shall support --index flag to scan PDF evidence files matching a glob path and write index.json. - justification: | - Provides a mechanism to regenerate the review evidence index from scanned PDF - files, reading embedded metadata from each PDF's Keywords field to populate - the index with review IDs, fingerprints, dates, results, and file names. - tests: - - Cli_IndexFlag_FlagSupplied_CreatesIndexJson - children: [ReviewMark-Index-PdfParsing, ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Enforce - title: The tool shall support --enforce flag to exit with a non-zero code when there are review issues. - justification: | - Enables CI/CD pipelines to block downstream stages when review sets are failed, - stale, or missing, or when files requiring review are not covered by any review-set. - Without --enforce the tool generates the plan and report but exits with code 0. - tests: - - Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent - children: [ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Dir - title: The tool shall support --dir flag to set the working directory for file operations. - justification: | - Allows users to target an evidence store or project directory without changing - the process working directory, enabling consistent scripting and CI/CD usage - without requiring a cd command before invoking the tool. - tests: - - Cli_DirFlag_FlagSupplied_SetsWorkingDirectory - children: [ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Elaborate - title: The tool shall support --elaborate flag to print a Markdown elaboration of a review set. - justification: | - When preparing for a review, the reviewer needs the review set ID, its current - fingerprint, and the full sorted list of files to be reviewed. The --elaborate - command provides this information formatted as Markdown so it can be copied - directly into review documentation. - tests: - - Cli_ElaborateFlag_ValidId_OutputsElaboration - children: [ReviewMark-Config-Reading, ReviewMark-Program-Dispatch] - - - id: ReviewMark-Cmd-Lint - title: >- - The tool shall support --lint flag to validate the definition file, printing only - issue messages on failure and producing no output on success. - justification: | - Users need a way to verify that the .reviewmark.yaml configuration file is valid - before running the main tool, providing clear error messages about the cause and - location of any issues. Suppressing the banner and summary output on success - (silence-on-success) makes lint suitable for direct use in scripts and CI pipelines. - tests: - - Cli_LintFlag_ValidConfig_ReportsSuccess - - Cli_LintFlag_InvalidConfig_ReportsIssueMessages - children: [ReviewMark-Config-Loading, ReviewMark-Program-Dispatch, ReviewMark-Program-LintVerbosity] diff --git a/docs/reqstream/review-mark/cli/context.yaml b/docs/reqstream/review-mark/cli/context.yaml index bb79fcf..086f6b5 100644 --- a/docs/reqstream/review-mark/cli/context.yaml +++ b/docs/reqstream/review-mark/cli/context.yaml @@ -1,102 +1,100 @@ --- -# Context Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the Context software unit -# - This unit parses command-line arguments into an in-memory context object -# - It also provides unified output and logging across the tool - +# Context unit requirements sections: - - title: Context Unit Requirements - requirements: - - id: ReviewMark-Context-Parsing - title: >- - The Context unit shall parse command-line arguments into a structured - representation of the requested operation and its options. - justification: | - All downstream processing reads options from the parsed representation rather than - directly from the raw argument array. Arguments are processed sequentially, - recognizing flag and value arguments. Unknown arguments must cause an error so - the caller can report a clear diagnostic. - tests: - - Context_Create_NoArguments_ReturnsDefaultContext - - Context_Create_VersionFlag_SetsVersionTrue - - Context_Create_ShortVersionFlag_SetsVersionTrue - - Context_Create_HelpFlag_SetsHelpTrue - - Context_Create_ShortHelpFlag_H_SetsHelpTrue - - Context_Create_ShortHelpFlag_Question_SetsHelpTrue - - Context_Create_SilentFlag_SetsSilentTrue - - Context_Create_ValidateFlag_SetsValidateTrue - - Context_Create_ResultsFlag_SetsResultsFile - - Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException - - Context_Create_ResultAlias_SetsResultsFile - - Context_Create_ResultAlias_WithoutValue_ThrowsArgumentException - - Context_Create_LogFlag_OpensLogFile - - Context_Create_LogFlag_WithoutValue_ThrowsArgumentException - - Context_Create_UnknownArgument_ThrowsArgumentException - - Context_Create_DefinitionFlag_SetsDefinitionFile - - Context_Create_DefinitionFlag_WithoutValue_ThrowsArgumentException - - Context_Create_PlanFlag_SetsPlanFile - - Context_Create_PlanDepthFlag_SetsPlanDepth - - Context_Create_PlanDepthFlag_WithInvalidValue_ThrowsArgumentException - - Context_Create_PlanDepthFlag_WithZeroValue_ThrowsArgumentException - - Context_Create_PlanDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException - - Context_Create_ReportFlag_SetsReportFile - - Context_Create_ReportDepthFlag_SetsReportDepth - - Context_Create_ReportDepthFlag_NonNumeric_ThrowsArgumentException - - Context_Create_ReportDepthFlag_Zero_ThrowsArgumentException - - Context_Create_ReportDepthFlag_MissingValue_ThrowsArgumentException - - Context_Create_ReportDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException - - Context_Create_IndexFlag_AddsIndexPath - - Context_Create_IndexFlag_MultipleTimes_AddsAllPaths - - Context_Create_NoArguments_IndexPathsEmpty - - Context_Create_NoArguments_PlanDepthDefaultsToOne - - Context_Create_NoArguments_ReportDepthDefaultsToOne - - Context_Create_EnforceFlag_SetsEnforceTrue - - Context_Create_NoArguments_EnforceFalse - - Context_Create_DirFlag_SetsWorkingDirectory - - Context_Create_NoArguments_WorkingDirectoryIsNull - - Context_Create_DirFlag_MissingValue_ThrowsArgumentException - - Context_Create_ElaborateFlag_SetsElaborateId - - Context_Create_NoArguments_ElaborateIdIsNull - - Context_Create_ElaborateFlag_WithoutValue_ThrowsArgumentException - - Context_Create_LintFlag_SetsLintTrue - - Context_Create_NoArguments_LintIsFalse - - Context_Create_DepthFlag_SetsDepth - - Context_Create_DepthFlag_PlanDepthOverride - - Context_Create_DepthFlag_ReportDepthOverride - - Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException - - Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException - - Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException - - Context_Create_DepthFlag_MissingValue_ThrowsArgumentException - - Context_Create_PlanFlag_WithoutValue_ThrowsArgumentException - - Context_Create_ReportFlag_WithoutValue_ThrowsArgumentException - - Context_Create_IndexFlag_WithoutValue_ThrowsArgumentException + - title: ReviewMark Requirements + sections: + - title: Cli Requirements + sections: + - title: Context Requirements + requirements: + - id: ReviewMark-Context-Parsing + title: >- + The Context unit shall parse command-line arguments into a structured + representation of the requested operation and its options. + justification: | + All downstream processing reads options from the parsed representation rather than + directly from the raw argument array. Arguments are processed sequentially, + recognizing flag and value arguments. Unknown arguments must cause an error so + the caller can report a clear diagnostic. + tests: + - Context_Create_NoArguments_ReturnsDefaultContext + - Context_Create_VersionFlag_SetsVersionTrue + - Context_Create_ShortVersionFlag_SetsVersionTrue + - Context_Create_HelpFlag_SetsHelpTrue + - Context_Create_ShortHelpFlag_H_SetsHelpTrue + - Context_Create_ShortHelpFlag_Question_SetsHelpTrue + - Context_Create_SilentFlag_SetsSilentTrue + - Context_Create_ValidateFlag_SetsValidateTrue + - Context_Create_ResultsFlag_SetsResultsFile + - Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException + - Context_Create_ResultAlias_SetsResultsFile + - Context_Create_ResultAlias_WithoutValue_ThrowsArgumentException + - Context_Create_LogFlag_OpensLogFile + - Context_Create_LogFlag_WithoutValue_ThrowsArgumentException + - Context_Create_UnknownArgument_ThrowsArgumentException + - Context_Create_DefinitionFlag_SetsDefinitionFile + - Context_Create_DefinitionFlag_WithoutValue_ThrowsArgumentException + - Context_Create_PlanFlag_SetsPlanFile + - Context_Create_PlanDepthFlag_SetsPlanDepth + - Context_Create_PlanDepthFlag_WithInvalidValue_ThrowsArgumentException + - Context_Create_PlanDepthFlag_WithZeroValue_ThrowsArgumentException + - Context_Create_PlanDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException + - Context_Create_ReportFlag_SetsReportFile + - Context_Create_ReportDepthFlag_SetsReportDepth + - Context_Create_ReportDepthFlag_NonNumeric_ThrowsArgumentException + - Context_Create_ReportDepthFlag_Zero_ThrowsArgumentException + - Context_Create_ReportDepthFlag_MissingValue_ThrowsArgumentException + - Context_Create_ReportDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException + - Context_Create_IndexFlag_AddsIndexPath + - Context_Create_IndexFlag_MultipleTimes_AddsAllPaths + - Context_Create_NoArguments_IndexPathsEmpty + - Context_Create_NoArguments_PlanDepthDefaultsToOne + - Context_Create_NoArguments_ReportDepthDefaultsToOne + - Context_Create_EnforceFlag_SetsEnforceTrue + - Context_Create_NoArguments_EnforceFalse + - Context_Create_DirFlag_SetsWorkingDirectory + - Context_Create_NoArguments_WorkingDirectoryIsNull + - Context_Create_DirFlag_MissingValue_ThrowsArgumentException + - Context_Create_ElaborateFlag_SetsElaborateId + - Context_Create_NoArguments_ElaborateIdIsNull + - Context_Create_ElaborateFlag_WithoutValue_ThrowsArgumentException + - Context_Create_LintFlag_SetsLintTrue + - Context_Create_NoArguments_LintIsFalse + - Context_Create_DepthFlag_SetsDepth + - Context_Create_DepthFlag_PlanDepthOverride + - Context_Create_DepthFlag_ReportDepthOverride + - Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException + - Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException + - Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException + - Context_Create_DepthFlag_MissingValue_ThrowsArgumentException + - Context_Create_PlanFlag_WithoutValue_ThrowsArgumentException + - Context_Create_ReportFlag_WithoutValue_ThrowsArgumentException + - Context_Create_IndexFlag_WithoutValue_ThrowsArgumentException - - id: ReviewMark-Context-LogFileError - title: The Context unit shall throw InvalidOperationException when the log file cannot be opened. - justification: | - If the log file path is invalid or the parent directory does not exist, the - Context cannot fulfil its logging contract. Throwing InvalidOperationException - wrapping the underlying file-system exception gives callers a typed signal that - the tool cannot start, enabling Program.Main to convert it into a clean error - exit with an actionable message rather than an unhandled exception crash. - tests: - - Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException + - id: ReviewMark-Context-LogFileError + title: The Context unit shall throw InvalidOperationException when the log file cannot be opened. + justification: | + If the log file path is invalid or the parent directory does not exist, the + Context cannot fulfil its logging contract. Throwing InvalidOperationException + wrapping the underlying file-system exception gives callers a typed signal that + the tool cannot start, enabling Program.Main to convert it into a clean error + exit with an actionable message rather than an unhandled exception crash. + tests: + - Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException - - id: ReviewMark-Context-Output - title: >- - The Context unit shall provide unified output and error logging, respecting - the --silent flag and optional --log file. - justification: | - All output goes through the Context so that the --silent flag is honoured and - optionally duplicated to a log file opened by the --log flag. Error output must - additionally set the error exit code so that the process exits with a non-zero - status when any error is reported. - tests: - - Context_WriteLine_NotSilent_WritesToConsole - - Context_WriteLine_Silent_DoesNotWriteToConsole - - Context_WriteError_NotSilent_WritesToConsole - - Context_WriteError_Silent_DoesNotWriteToConsole - - Context_WriteError_SetsErrorExitCode - - Context_WriteError_WritesToLogFile + - id: ReviewMark-Context-Output + title: >- + The Context unit shall provide unified output and error logging, respecting + the --silent flag and optional --log file. + justification: | + All output goes through the Context so that the --silent flag is honoured and + optionally duplicated to a log file opened by the --log flag. Error output must + additionally set the error exit code so that the process exits with a non-zero + status when any error is reported. + tests: + - Context_WriteLine_NotSilent_WritesToConsole + - Context_WriteLine_Silent_DoesNotWriteToConsole + - Context_WriteError_NotSilent_WritesToConsole + - Context_WriteError_Silent_DoesNotWriteToConsole + - Context_WriteError_SetsErrorExitCode + - Context_WriteError_WritesToLogFile diff --git a/docs/reqstream/review-mark/configuration.yaml b/docs/reqstream/review-mark/configuration.yaml new file mode 100644 index 0000000..4fad15c --- /dev/null +++ b/docs/reqstream/review-mark/configuration.yaml @@ -0,0 +1,105 @@ +--- +# Configuration subsystem requirements +sections: + - title: ReviewMark Requirements + sections: + - title: Configuration Requirements + requirements: + - id: ReviewMark-Configuration-NeedsReview + title: The tool shall identify all files requiring review by resolving needs-review glob patterns. + justification: | + Users configure which files require review using glob patterns. The Configuration + subsystem must resolve these patterns to a concrete list of files, applying includes + and excludes in declaration order, so that ReviewMark can detect uncovered files + and generate accurate review plans. + tests: + - Configuration_NeedsReview_ValidConfig_ResolvesFiles + children: + - ReviewMark-Config-Reading + - ReviewMark-GlobMatcher-IncludeExclude + - ReviewMark-GlobMatcher-NullBaseDirectoryRejection + - ReviewMark-GlobMatcher-NullPatternsRejection + - ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection + - ReviewMark-GlobMatcher-PathNormalization + + - id: ReviewMark-Configuration-Fingerprinting + title: The tool shall compute content-based fingerprints for review-sets to detect file changes. + justification: | + Review-set fingerprints are the mechanism by which ReviewMark detects that files + have changed since the last review. The fingerprint must be based on file content + rather than names alone, so that renamed files do not invalidate the fingerprint, + and changed content always produces a new fingerprint. + tests: + - Configuration_Fingerprinting_ContentModified_FingerprintDiffers + - Configuration_Fingerprinting_FileRenamed_FingerprintUnchanged + children: [ReviewMark-Config-Reading] + + - id: ReviewMark-Configuration-PlanGeneration + title: The tool shall generate a Review Plan Markdown document listing review-set coverage. + justification: | + The Review Plan is a compliance artifact that documents which review-sets exist + and what files they cover. It enables auditors to verify that all relevant files + are included in at least one review-set before reviews are conducted. + tests: + - Configuration_PlanGeneration_ValidConfig_Succeeds + children: + - ReviewMark-Config-Reading + - ReviewMark-Config-Loading + - ReviewMark-Config-PlanGeneration + - ReviewMark-Config-PlanMarkdownDepth + - ReviewMark-Config-PlanMarkdownDepthValidation + + - id: ReviewMark-Configuration-ReportGeneration + title: The tool shall generate a Review Report Markdown document showing review-set status. + justification: | + The Review Report is a compliance artifact that documents the current review status + of each review-set (Current, Stale, Missing, or Failed), enabling auditors to + confirm that all review-sets have current evidence before a release. + tests: + - Configuration_ReportGeneration_ValidConfig_Succeeds + children: + - ReviewMark-Config-Reading + - ReviewMark-Config-Loading + - ReviewMark-Config-ReportGeneration + - ReviewMark-Config-ReportMarkdownDepth + - ReviewMark-Config-ReportMarkdownDepthValidation + + - id: ReviewMark-Configuration-Elaboration + title: The tool shall generate a Markdown elaboration document for a named review-set. + justification: | + When preparing for a code review, the reviewer needs the review set ID, its current + fingerprint, and the full sorted list of files to be reviewed. The elaboration + command provides this formatted as Markdown so it can be copied directly into + review documentation. + tests: + - Configuration_Elaboration_ValidId_Succeeds + children: + - ReviewMark-Config-Reading + - ReviewMark-Config-Elaboration + - ReviewMark-Config-ElaborationMarkdownDepth + - ReviewMark-Config-ElaborationMarkdownDepthValidation + - ReviewMark-Config-ElaborationNullRejection + + - id: ReviewMark-Configuration-MalformedYaml + title: The tool shall return a null configuration with diagnostic issues when the YAML file is malformed. + justification: | + A malformed YAML file cannot be parsed into a valid configuration model. Returning + null with descriptive diagnostic issues allows callers to detect the failure and + report a meaningful error message to the user, rather than propagating a raw + YAML parser exception. + tests: + - Configuration_LoadConfig_MalformedYaml_ReturnsIssues + children: [ReviewMark-Config-LoadingNullOnError] + + - id: ReviewMark-Configuration-ElaborateUnknownId + title: >- + The tool shall throw ArgumentException when ElaborateReviewSet is called + with an ID that does not exist in the configuration. + justification: | + Passing an unknown review-set ID to ElaborateReviewSet is a programming error that + cannot be resolved without correcting the caller. Throwing ArgumentException with + a clear message enables callers to detect and report the mistake immediately rather + than silently producing empty output. + tests: + - Configuration_ElaborateReviewSet_UnknownId_ThrowsArgumentException + children: [ReviewMark-Config-ElaborationUnknownIdRejection] diff --git a/docs/reqstream/review-mark/configuration/configuration.yaml b/docs/reqstream/review-mark/configuration/configuration.yaml deleted file mode 100644 index 6dc1ffd..0000000 --- a/docs/reqstream/review-mark/configuration/configuration.yaml +++ /dev/null @@ -1,110 +0,0 @@ ---- -# Configuration Subsystem Requirements -# -# PURPOSE: -# - Define requirements for the ReviewMark Configuration subsystem -# - The Configuration subsystem spans ReviewMarkConfiguration.cs (config loading and processing) -# and GlobMatcher.cs (file pattern matching) -# - Subsystem requirements describe the externally visible configuration capabilities - -sections: - - title: Configuration Subsystem Requirements - requirements: - - id: ReviewMark-Configuration-NeedsReview - title: The tool shall identify all files requiring review by resolving needs-review glob patterns. - justification: | - Users configure which files require review using glob patterns. The Configuration - subsystem must resolve these patterns to a concrete list of files, applying includes - and excludes in declaration order, so that ReviewMark can detect uncovered files - and generate accurate review plans. - tests: - - Configuration_NeedsReview_ValidConfig_ResolvesFiles - children: - - ReviewMark-Config-Reading - - ReviewMark-GlobMatcher-IncludeExclude - - ReviewMark-GlobMatcher-NullBaseDirectoryRejection - - ReviewMark-GlobMatcher-NullPatternsRejection - - ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection - - ReviewMark-GlobMatcher-PathNormalization - - - id: ReviewMark-Configuration-Fingerprinting - title: The tool shall compute content-based fingerprints for review-sets to detect file changes. - justification: | - Review-set fingerprints are the mechanism by which ReviewMark detects that files - have changed since the last review. The fingerprint must be based on file content - rather than names alone, so that renamed files do not invalidate the fingerprint, - and changed content always produces a new fingerprint. - tests: - - Configuration_Fingerprinting_ContentModified_FingerprintDiffers - - Configuration_Fingerprinting_FileRenamed_FingerprintUnchanged - children: [ReviewMark-Config-Reading] - - - id: ReviewMark-Configuration-PlanGeneration - title: The tool shall generate a Review Plan Markdown document listing review-set coverage. - justification: | - The Review Plan is a compliance artifact that documents which review-sets exist - and what files they cover. It enables auditors to verify that all relevant files - are included in at least one review-set before reviews are conducted. - tests: - - Configuration_PlanGeneration_ValidConfig_Succeeds - children: - - ReviewMark-Config-Reading - - ReviewMark-Config-Loading - - ReviewMark-Config-PlanGeneration - - ReviewMark-Config-PlanMarkdownDepth - - ReviewMark-Config-PlanMarkdownDepthValidation - - - id: ReviewMark-Configuration-ReportGeneration - title: The tool shall generate a Review Report Markdown document showing review-set status. - justification: | - The Review Report is a compliance artifact that documents the current review status - of each review-set (Current, Stale, Missing, or Failed), enabling auditors to - confirm that all review-sets have current evidence before a release. - tests: - - Configuration_ReportGeneration_ValidConfig_Succeeds - children: - - ReviewMark-Config-Reading - - ReviewMark-Config-Loading - - ReviewMark-Config-ReportGeneration - - ReviewMark-Config-ReportMarkdownDepth - - ReviewMark-Config-ReportMarkdownDepthValidation - - - id: ReviewMark-Configuration-Elaboration - title: The tool shall generate a Markdown elaboration document for a named review-set. - justification: | - When preparing for a code review, the reviewer needs the review set ID, its current - fingerprint, and the full sorted list of files to be reviewed. The elaboration - command provides this formatted as Markdown so it can be copied directly into - review documentation. - tests: - - Configuration_Elaboration_ValidId_Succeeds - children: - - ReviewMark-Config-Reading - - ReviewMark-Config-Elaboration - - ReviewMark-Config-ElaborationMarkdownDepth - - ReviewMark-Config-ElaborationMarkdownDepthValidation - - ReviewMark-Config-ElaborationNullRejection - - - id: ReviewMark-Configuration-MalformedYaml - title: The tool shall return a null configuration with diagnostic issues when the YAML file is malformed. - justification: | - A malformed YAML file cannot be parsed into a valid configuration model. Returning - null with descriptive diagnostic issues allows callers to detect the failure and - report a meaningful error message to the user, rather than propagating a raw - YAML parser exception. - tests: - - Configuration_LoadConfig_MalformedYaml_ReturnsIssues - children: [ReviewMark-Config-LoadingNullOnError] - - - id: ReviewMark-Configuration-ElaborateUnknownId - title: >- - The tool shall throw ArgumentException when ElaborateReviewSet is called - with an ID that does not exist in the configuration. - justification: | - Passing an unknown review-set ID to ElaborateReviewSet is a programming error that - cannot be resolved without correcting the caller. Throwing ArgumentException with - a clear message enables callers to detect and report the mistake immediately rather - than silently producing empty output. - tests: - - Configuration_LoadConfig_ElaborateUnknownId_ThrowsArgumentException - children: [ReviewMark-Config-ElaborationUnknownIdRejection] diff --git a/docs/reqstream/review-mark/configuration/glob-matcher.yaml b/docs/reqstream/review-mark/configuration/glob-matcher.yaml index 46a23aa..d0290ca 100644 --- a/docs/reqstream/review-mark/configuration/glob-matcher.yaml +++ b/docs/reqstream/review-mark/configuration/glob-matcher.yaml @@ -1,66 +1,64 @@ --- -# GlobMatcher Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the GlobMatcher software unit -# - This unit resolves ordered include/exclude glob patterns to a list of files -# - It is used by ReviewMarkConfiguration to resolve needs-review and review-set file lists - +# GlobMatcher unit requirements sections: - - title: GlobMatcher Unit Requirements - requirements: - - id: ReviewMark-GlobMatcher-IncludeExclude - title: >- - The GlobMatcher shall resolve ordered include and exclude glob patterns to a sorted list of - relative file paths. - justification: | - Review-set and needs-review configurations specify files using ordered glob patterns, - where patterns prefixed with '!' are exclusions. The GlobMatcher must apply these - patterns in declaration order so that a later include can re-add files removed by an - earlier exclude, and vice versa. The result must be sorted to ensure deterministic - fingerprinting regardless of filesystem iteration order. - tests: - - GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles - - GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles - - GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles - - GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles - - GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList - - GlobMatcher_GetMatchingFiles_EmptyPatterns_ReturnsEmptyList - - GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching + - title: ReviewMark Requirements + sections: + - title: Configuration Requirements + sections: + - title: GlobMatcher Requirements + requirements: + - id: ReviewMark-GlobMatcher-IncludeExclude + title: >- + The GlobMatcher shall resolve ordered include and exclude glob patterns to a sorted list of + relative file paths. + justification: | + Review-set and needs-review configurations specify files using ordered glob patterns, + where patterns prefixed with '!' are exclusions. The GlobMatcher must apply these + patterns in declaration order so that a later include can re-add files removed by an + earlier exclude, and vice versa. The result must be sorted to ensure deterministic + fingerprinting regardless of filesystem iteration order. + tests: + - GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles + - GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles + - GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles + - GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles + - GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList + - GlobMatcher_GetMatchingFiles_EmptyPatterns_ReturnsEmptyList + - GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching - - id: ReviewMark-GlobMatcher-NullBaseDirectoryRejection - title: The GlobMatcher shall reject a null baseDirectory parameter with an ArgumentNullException. - justification: | - When baseDirectory is null, the operation must be rejected with an ArgumentNullException - so callers receive a clear diagnostic rather than an unhandled error. - tests: - - GlobMatcher_GetMatchingFiles_NullBaseDirectory_ThrowsArgumentNullException + - id: ReviewMark-GlobMatcher-NullBaseDirectoryRejection + title: The GlobMatcher shall reject a null baseDirectory parameter with an ArgumentNullException. + justification: | + When baseDirectory is null, the operation must be rejected with an ArgumentNullException + so callers receive a clear diagnostic rather than an unhandled error. + tests: + - GlobMatcher_GetMatchingFiles_NullBaseDirectory_ThrowsArgumentNullException - - id: ReviewMark-GlobMatcher-NullPatternsRejection - title: The GlobMatcher shall reject a null patterns parameter with an ArgumentNullException. - justification: | - When patterns is null, the operation must be rejected with an ArgumentNullException - so callers receive a clear diagnostic rather than an unhandled error. - tests: - - GlobMatcher_GetMatchingFiles_NullPatterns_ThrowsArgumentNullException + - id: ReviewMark-GlobMatcher-NullPatternsRejection + title: The GlobMatcher shall reject a null patterns parameter with an ArgumentNullException. + justification: | + When patterns is null, the operation must be rejected with an ArgumentNullException + so callers receive a clear diagnostic rather than an unhandled error. + tests: + - GlobMatcher_GetMatchingFiles_NullPatterns_ThrowsArgumentNullException - - id: ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection - title: >- - The GlobMatcher shall reject an empty or whitespace-only baseDirectory - parameter with an ArgumentException. - justification: | - When baseDirectory is empty or contains only whitespace, the operation must be - rejected with an ArgumentException so callers receive a clear diagnostic rather - than an unhandled error when invalid values are passed. - tests: - - GlobMatcher_GetMatchingFiles_EmptyBaseDirectory_ThrowsArgumentException - - GlobMatcher_GetMatchingFiles_WhitespaceBaseDirectory_ThrowsArgumentException + - id: ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection + title: >- + The GlobMatcher shall reject an empty or whitespace-only baseDirectory + parameter with an ArgumentException. + justification: | + When baseDirectory is empty or contains only whitespace, the operation must be + rejected with an ArgumentException so callers receive a clear diagnostic rather + than an unhandled error when invalid values are passed. + tests: + - GlobMatcher_GetMatchingFiles_EmptyBaseDirectory_ThrowsArgumentException + - GlobMatcher_GetMatchingFiles_WhitespaceBaseDirectory_ThrowsArgumentException - - id: ReviewMark-GlobMatcher-PathNormalization - title: The GlobMatcher shall normalize returned relative paths to use forward slashes as directory separators. - justification: | - Returned paths must use forward slashes regardless of the host operating system's - native directory separator to ensure consistent fingerprint computation across - platforms (Windows, Linux, macOS). - tests: - - GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator + - id: ReviewMark-GlobMatcher-PathNormalization + title: The GlobMatcher shall normalize returned relative paths to use forward slashes as directory separators. + justification: | + Returned paths must use forward slashes regardless of the host operating system's + native directory separator to ensure consistent fingerprint computation across + platforms (Windows, Linux, macOS). + tests: + - GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator diff --git a/docs/reqstream/review-mark/configuration/review-mark-configuration.yaml b/docs/reqstream/review-mark/configuration/review-mark-configuration.yaml index 848410c..6235cbe 100644 --- a/docs/reqstream/review-mark/configuration/review-mark-configuration.yaml +++ b/docs/reqstream/review-mark/configuration/review-mark-configuration.yaml @@ -1,195 +1,193 @@ --- -# ReviewMarkConfiguration Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the ReviewMarkConfiguration software unit -# - This unit parses the .reviewmark.yaml definition file into an in-memory model -# - It computes SHA256 fingerprints for review-sets and generates plan/report Markdown - +# ReviewMarkConfiguration unit requirements sections: - - title: ReviewMarkConfiguration Unit Requirements - requirements: - - id: ReviewMark-Config-Reading - title: >- - ReviewMarkConfiguration shall read and parse the .reviewmark.yaml file into an in-memory - configuration model. - justification: | - Enables the tool to read its configuration from the standard `.reviewmark.yaml` file, - exposing needs-review patterns, evidence source, and review set definitions. Review sets - support content-based fingerprinting to detect changes to covered files. - tests: - - ReviewMarkConfiguration_Parse_NullYaml_ThrowsArgumentNullException - - ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration - - ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly - - ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly - - ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly - - ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly - - ReviewMarkConfiguration_GetNeedsReviewFiles_ReturnsMatchingFiles - - ReviewSet_GetFingerprint_SameContent_ReturnsSameFingerprint - - ReviewSet_GetFingerprint_DifferentContent_ReturnsDifferentFingerprint - - ReviewSet_GetFingerprint_RenameFile_ReturnsSameFingerprint - - ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath - - ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly - - ReviewMarkConfiguration_Parse_NoneEvidenceSource_NoLocationRequired + - title: ReviewMark Requirements + sections: + - title: Configuration Requirements + sections: + - title: ReviewMarkConfiguration Requirements + requirements: + - id: ReviewMark-Config-Reading + title: >- + ReviewMarkConfiguration shall read and parse the .reviewmark.yaml file into an in-memory + configuration model. + justification: | + Enables the tool to read its configuration from the standard `.reviewmark.yaml` file, + exposing needs-review patterns, evidence source, and review set definitions. Review sets + support content-based fingerprinting to detect changes to covered files. + tests: + - ReviewMarkConfiguration_Parse_NullYaml_ThrowsArgumentNullException + - ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration + - ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly + - ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly + - ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly + - ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly + - ReviewMarkConfiguration_GetNeedsReviewFiles_ReturnsMatchingFiles + - ReviewSet_GetFingerprint_SameContent_ReturnsSameFingerprint + - ReviewSet_GetFingerprint_DifferentContent_ReturnsDifferentFingerprint + - ReviewSet_GetFingerprint_RenameFile_ReturnsSameFingerprint + - ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath + - ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly + - ReviewMarkConfiguration_Parse_NoneEvidenceSource_NoLocationRequired - - id: ReviewMark-Config-Loading - title: >- - The ReviewMarkConfiguration unit shall perform linting during configuration - loading, returning a result containing both the configuration and all - detected issues. - justification: | - Combining configuration parsing and linting in a single loading operation ensures - callers receive comprehensive diagnostics without performing two separate passes. - All detectable issues are accumulated and returned so the caller can report all - problems at once. - tests: - - ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues - - ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue - - ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues - - ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues - - ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext - - ReviewMarkConfiguration_Load_WhitespaceOnlyPaths_ReturnsLintError + - id: ReviewMark-Config-Loading + title: >- + The ReviewMarkConfiguration unit shall perform linting during configuration + loading, returning a result containing both the configuration and all + detected issues. + justification: | + Combining configuration parsing and linting in a single loading operation ensures + callers receive comprehensive diagnostics without performing two separate passes. + All detectable issues are accumulated and returned so the caller can report all + problems at once. + tests: + - ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues + - ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue + - ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues + - ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues + - ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext + - ReviewMarkConfiguration_Load_WhitespaceOnlyPaths_ReturnsLintError - - id: ReviewMark-Config-LoadingNullOnError - title: >- - The ReviewMarkConfiguration unit shall return a null configuration in the - load result when any error-level lint issue is detected. - justification: | - Returning null when errors are detected allows callers to distinguish between a - completely invalid file and a file with only warnings. Null signals that the - configuration cannot be used. - tests: - - ReviewMarkConfiguration_Load_NonExistentFile_ReturnsNullConfigWithErrorIssue - - ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue - - ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue + - id: ReviewMark-Config-LoadingNullOnError + title: >- + The ReviewMarkConfiguration unit shall return a null configuration in the + load result when any error-level lint issue is detected. + justification: | + Returning null when errors are detected allows callers to distinguish between a + completely invalid file and a file with only warnings. Null signals that the + configuration cannot be used. + tests: + - ReviewMarkConfiguration_Load_NonExistentFile_ReturnsNullConfigWithErrorIssue + - ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue + - ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue - - id: ReviewMark-Config-PlanGeneration - title: >- - The ReviewMarkConfiguration unit shall generate a Markdown review plan - listing all files in the needs-review set and their review-set coverage. - justification: | - The tool must generate a Markdown review plan document that lists every file - in the needs-review file-set and identifies which review-sets provide coverage - for each file. - tests: - - ReviewMarkConfiguration_PublishReviewPlan_AllCovered_NoIssues - - ReviewMarkConfiguration_PublishReviewPlan_UncoveredFiles_HasIssues + - id: ReviewMark-Config-PlanGeneration + title: >- + The ReviewMarkConfiguration unit shall generate a Markdown review plan + listing all files in the needs-review set and their review-set coverage. + justification: | + The tool must generate a Markdown review plan document that lists every file + in the needs-review file-set and identifies which review-sets provide coverage + for each file. + tests: + - ReviewMarkConfiguration_PublishReviewPlan_AllCovered_NoIssues + - ReviewMarkConfiguration_PublishReviewPlan_UncoveredFiles_HasIssues - - id: ReviewMark-Config-PlanMarkdownDepth - title: >- - The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to - control the heading level in generated review plan documents. - justification: | - The heading depth controls the section heading level in the generated plan document - and must be within the supported range (1–5) so that subheadings do not exceed - the maximum Markdown heading level of 6. - tests: - - ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepth_UsedForHeadings + - id: ReviewMark-Config-PlanMarkdownDepth + title: >- + The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to + control the heading level in generated review plan documents. + justification: | + The heading depth controls the section heading level in the generated plan document + and must be within the supported range (1–5) so that subheadings do not exceed + the maximum Markdown heading level of 6. + tests: + - ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepth_UsedForHeadings - - id: ReviewMark-Config-PlanMarkdownDepthValidation - title: >- - The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 - for plan generation with ArgumentOutOfRangeException. - justification: | - Subheadings in the generated plan document are at depth+1. Allowing depth > 5 would - require a heading level of 7 or more, which exceeds the maximum supported by Markdown. - Explicit validation with ArgumentOutOfRangeException surfaces the problem immediately - at the call site. - tests: - - ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepthAbove5_Throws + - id: ReviewMark-Config-PlanMarkdownDepthValidation + title: >- + The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 + for plan generation with ArgumentOutOfRangeException. + justification: | + Subheadings in the generated plan document are at depth+1. Allowing depth > 5 would + require a heading level of 7 or more, which exceeds the maximum supported by Markdown. + Explicit validation with ArgumentOutOfRangeException surfaces the problem immediately + at the call site. + tests: + - ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepthAbove5_Throws - - id: ReviewMark-Config-ReportGeneration - title: >- - The ReviewMarkConfiguration unit shall generate a Markdown review report - listing each review-set and its current status. - justification: | - The tool must generate a Markdown review report document that lists every - review-set with its current status (Current, Stale, Missing, or Failed). - tests: - - ReviewMarkConfiguration_PublishReviewReport_CurrentReview_NoIssues - - ReviewMarkConfiguration_PublishReviewReport_StaleReview_HasIssues - - ReviewMarkConfiguration_PublishReviewReport_FailedReview_HasIssues - - ReviewMarkConfiguration_PublishReviewReport_MissingReview_HasIssues + - id: ReviewMark-Config-ReportGeneration + title: >- + The ReviewMarkConfiguration unit shall generate a Markdown review report + listing each review-set and its current status. + justification: | + The tool must generate a Markdown review report document that lists every + review-set with its current status (Current, Stale, Missing, or Failed). + tests: + - ReviewMarkConfiguration_PublishReviewReport_CurrentReview_NoIssues + - ReviewMarkConfiguration_PublishReviewReport_StaleReview_HasIssues + - ReviewMarkConfiguration_PublishReviewReport_FailedReview_HasIssues + - ReviewMarkConfiguration_PublishReviewReport_MissingReview_HasIssues - - id: ReviewMark-Config-ReportMarkdownDepth - title: >- - The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to - control the heading level in generated review report documents. - justification: | - The heading depth controls the section heading level in the generated report document - and must be within the supported range (1–5) so that subheadings do not exceed - the maximum Markdown heading level of 6. - tests: - - ReviewMarkConfiguration_PublishReviewReport_MarkdownDepth_UsedForHeadings + - id: ReviewMark-Config-ReportMarkdownDepth + title: >- + The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to + control the heading level in generated review report documents. + justification: | + The heading depth controls the section heading level in the generated report document + and must be within the supported range (1–5) so that subheadings do not exceed + the maximum Markdown heading level of 6. + tests: + - ReviewMarkConfiguration_PublishReviewReport_MarkdownDepth_UsedForHeadings - - id: ReviewMark-Config-ReportMarkdownDepthValidation - title: >- - The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 - for report generation with ArgumentOutOfRangeException. - justification: | - Subheadings in the generated report document are at depth+1. Allowing depth > 5 would - require a heading level of 7 or more, which exceeds the maximum supported by Markdown. - Explicit validation with ArgumentOutOfRangeException surfaces the problem immediately - at the call site. - tests: - - ReviewMarkConfiguration_PublishReviewReport_MarkdownDepthAbove5_Throws + - id: ReviewMark-Config-ReportMarkdownDepthValidation + title: >- + The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 + for report generation with ArgumentOutOfRangeException. + justification: | + Subheadings in the generated report document are at depth+1. Allowing depth > 5 would + require a heading level of 7 or more, which exceeds the maximum supported by Markdown. + Explicit validation with ArgumentOutOfRangeException surfaces the problem immediately + at the call site. + tests: + - ReviewMarkConfiguration_PublishReviewReport_MarkdownDepthAbove5_Throws - - id: ReviewMark-Config-Elaboration - title: >- - The ReviewMarkConfiguration unit shall generate Markdown elaboration for a - named review set, including its ID, fingerprint, and file list. - justification: | - The tool must generate a Markdown elaboration document for a named review-set, - containing the review-set ID, title, fingerprint, and all matched files listed - as inline code. - tests: - - ReviewMarkConfiguration_ElaborateReviewSet_ValidId_ReturnsElaboration - - ReviewMarkConfiguration_ElaborateReviewSet_ContainsFullFingerprint + - id: ReviewMark-Config-Elaboration + title: >- + The ReviewMarkConfiguration unit shall generate Markdown elaboration for a + named review set, including its ID, fingerprint, and file list. + justification: | + The tool must generate a Markdown elaboration document for a named review-set, + containing the review-set ID, title, fingerprint, and all matched files listed + as inline code. + tests: + - ReviewMarkConfiguration_ElaborateReviewSet_ValidId_ReturnsElaboration + - ReviewMarkConfiguration_ElaborateReviewSet_ContainsFullFingerprint - - id: ReviewMark-Config-ElaborationNullRejection - title: >- - The ReviewMarkConfiguration unit shall throw ArgumentNullException for null - review-set ID and ArgumentException for whitespace/empty ID in - ElaborateReviewSet. - justification: | - Null and whitespace/empty review-set IDs must be rejected with typed exceptions - (ArgumentNullException for null, ArgumentException for whitespace/empty) so callers - receive clear diagnostics at the point of the invalid call. - tests: - - ReviewMarkConfiguration_ElaborateReviewSet_NullId_ThrowsArgumentNullException - - ReviewMarkConfiguration_ElaborateReviewSet_WhitespaceId_ThrowsArgumentException + - id: ReviewMark-Config-ElaborationNullRejection + title: >- + The ReviewMarkConfiguration unit shall throw ArgumentNullException for null + review-set ID and ArgumentException for whitespace/empty ID in + ElaborateReviewSet. + justification: | + Null and whitespace/empty review-set IDs must be rejected with typed exceptions + (ArgumentNullException for null, ArgumentException for whitespace/empty) so callers + receive clear diagnostics at the point of the invalid call. + tests: + - ReviewMarkConfiguration_ElaborateReviewSet_NullId_ThrowsArgumentNullException + - ReviewMarkConfiguration_ElaborateReviewSet_WhitespaceId_ThrowsArgumentException - - id: ReviewMark-Config-ElaborationUnknownIdRejection - title: >- - The ReviewMarkConfiguration unit shall throw ArgumentException when - ElaborateReviewSet is called with an ID that does not exist in the - configuration. - justification: | - An unrecognized review-set ID is a programming error that cannot be resolved without - correcting the caller. Throwing ArgumentException with a clear message enables callers - to detect and report the mistake immediately rather than silently producing empty output. - tests: - - ReviewMarkConfiguration_ElaborateReviewSet_UnknownId_ThrowsArgumentException + - id: ReviewMark-Config-ElaborationUnknownIdRejection + title: >- + The ReviewMarkConfiguration unit shall throw ArgumentException when + ElaborateReviewSet is called with an ID that does not exist in the + configuration. + justification: | + An unrecognized review-set ID is a programming error that cannot be resolved without + correcting the caller. Throwing ArgumentException with a clear message enables callers + to detect and report the mistake immediately rather than silently producing empty output. + tests: + - ReviewMarkConfiguration_ElaborateReviewSet_UnknownId_ThrowsArgumentException - - id: ReviewMark-Config-ElaborationMarkdownDepth - title: >- - The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to - control the heading level in generated elaboration documents. - justification: | - The heading depth controls the heading level in the generated elaboration document - and must be within the supported range (1–5) so that subheadings do not exceed - the maximum Markdown heading level of 6. - tests: - - ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepth_UsedForHeadings + - id: ReviewMark-Config-ElaborationMarkdownDepth + title: >- + The ReviewMarkConfiguration unit shall apply the markdownDepth parameter to + control the heading level in generated elaboration documents. + justification: | + The heading depth controls the heading level in the generated elaboration document + and must be within the supported range (1–5) so that subheadings do not exceed + the maximum Markdown heading level of 6. + tests: + - ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepth_UsedForHeadings - - id: ReviewMark-Config-ElaborationMarkdownDepthValidation - title: >- - The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 - for elaboration with ArgumentOutOfRangeException. - justification: | - Subheadings in the generated elaboration document are at depth+1. Allowing depth > 5 - would require a heading level of 7 or more, which exceeds the maximum supported by - Markdown. Explicit validation with ArgumentOutOfRangeException surfaces the problem - immediately at the call site. - tests: - - ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepthAbove5_Throws + - id: ReviewMark-Config-ElaborationMarkdownDepthValidation + title: >- + The ReviewMarkConfiguration unit shall reject markdownDepth values above 5 + for elaboration with ArgumentOutOfRangeException. + justification: | + Subheadings in the generated elaboration document are at depth+1. Allowing depth > 5 + would require a heading level of 7 or more, which exceeds the maximum supported by + Markdown. Explicit validation with ArgumentOutOfRangeException surfaces the problem + immediately at the call site. + tests: + - ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepthAbove5_Throws diff --git a/docs/reqstream/review-mark/indexing.yaml b/docs/reqstream/review-mark/indexing.yaml new file mode 100644 index 0000000..6903de8 --- /dev/null +++ b/docs/reqstream/review-mark/indexing.yaml @@ -0,0 +1,75 @@ +--- +# Indexing subsystem requirements +sections: + - title: ReviewMark Requirements + sections: + - title: Indexing Requirements + requirements: + - id: ReviewMark-Indexing-LoadEvidence + title: >- + The tool shall load review evidence from a fileshare or url EvidenceSource. + justification: | + The Indexing subsystem must support loading evidence from remote or local + file-based stores (fileshare and url types) so that plan and report generation + can determine the review status of each review-set. Missing files, unreadable + files, and HTTP failures must each produce a clear error so the operator can + diagnose and correct the evidence configuration without inspecting raw exceptions. + tests: + - Indexing_SafePathCombine_WithIndexPath_LoadsIndex + - Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex + - Indexing_ReviewIndex_Load_MissingFile_ThrowsInvalidOperationException + - Indexing_ReviewIndex_Load_MalformedJson_ThrowsInvalidOperationException + children: + - ReviewMark-Index-EvidenceSource + - ReviewMark-Index-GetEvidence + - ReviewMark-Index-HasId + - ReviewMark-Index-GetAllForId + + - id: ReviewMark-Indexing-CreateEvidence + title: >- + The tool shall create a new empty index when the EvidenceSource type is 'none'. + justification: | + When a project is first starting out, the evidence source can be set to 'none' + until an evidence store is provisioned. In this case the Indexing subsystem must + return an empty index immediately without accessing the file system or network, + allowing the tool to operate during initial repository setup without error. + tests: + - Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex + children: + - ReviewMark-Index-EvidenceSource-None + - ReviewMark-Index-Empty + + - id: ReviewMark-Indexing-ScanPdfEvidence + title: The tool shall scan PDF evidence files and extract embedded review metadata to build an index. + justification: | + Review evidence is stored as PDF files with metadata embedded in the Keywords + field. The Indexing subsystem must be able to scan directories for PDF files + and extract the review ID, fingerprint, date, and result from each file to + populate the evidence index used for report generation. + tests: + - Indexing_ReviewIndex_Scan_WithNoPdfs_ReturnsEmptyIndex + - Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex + children: [ReviewMark-Index-PdfParsing, ReviewMark-Index-Freshness] + + - id: ReviewMark-Indexing-Save + title: The tool shall save the review evidence index to a JSON file for later loading. + justification: | + After scanning PDF evidence files, the resulting index must be persisted to disk + so that plan and report generation can consume the evidence without re-scanning. + The save format must be compatible with the load format so that a round-trip + preserves all evidence entries without data loss. + tests: + - Indexing_ReviewIndex_SaveAndLoad_RoundTrip + children: [ReviewMark-Index-Save] + + - id: ReviewMark-Indexing-SafePathCombine + title: The tool shall combine file paths safely, rejecting path traversal sequences. + justification: | + Path traversal sequences (such as '..') in file paths could allow access to + files outside the intended directory. The Indexing subsystem must reject such + sequences to prevent unintended file system access in both evidence scanning + and index file operations. + tests: + - Indexing_SafePathCombine_WithIndexPath_LoadsIndex + - Indexing_SafePathCombine_WithTraversalInputs_Throws + children: [ReviewMark-PathHelpers-SafeCombine, ReviewMark-PathHelpers-NullRejection] diff --git a/docs/reqstream/review-mark/indexing/indexing.yaml b/docs/reqstream/review-mark/indexing/indexing.yaml deleted file mode 100644 index 8e7ed9b..0000000 --- a/docs/reqstream/review-mark/indexing/indexing.yaml +++ /dev/null @@ -1,69 +0,0 @@ ---- -# Indexing Subsystem Requirements -# -# PURPOSE: -# - Define requirements for the ReviewMark Indexing subsystem -# - The Indexing subsystem spans ReviewIndex.cs (evidence loading) and PathHelpers.cs (path utilities) -# - Subsystem requirements describe the externally visible evidence-loading behavior - -sections: - - title: Indexing Subsystem Requirements - requirements: - - id: ReviewMark-Indexing-LoadEvidence - title: >- - The tool shall load review evidence from a configured EvidenceSource - supporting none, fileshare, and url types. - justification: | - The Indexing subsystem must support multiple evidence source types to accommodate - different deployment environments. The 'none' type allows the tool to operate - during initial project setup without an evidence store. The 'fileshare' type - supports loading from a local or network file path. The 'url' type supports - downloading evidence over HTTP(S), enabling centralized evidence stores accessible - from any CI/CD environment. - tests: - - Indexing_SafePathCombine_WithIndexPath_LoadsIndex - - Indexing_ReviewIndex_SaveAndLoad_RoundTrip - - Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex - - Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex - children: - - ReviewMark-Index-EvidenceSource - - ReviewMark-EvidenceSource-None - - ReviewMark-Index-Empty - - ReviewMark-Index-GetEvidence - - ReviewMark-Index-HasId - - ReviewMark-Index-GetAllForId - - - id: ReviewMark-Indexing-ScanPdfEvidence - title: The tool shall scan PDF evidence files and extract embedded review metadata to build an index. - justification: | - Review evidence is stored as PDF files with metadata embedded in the Keywords - field. The Indexing subsystem must be able to scan directories for PDF files - and extract the review ID, fingerprint, date, and result from each file to - populate the evidence index used for report generation. - tests: - - Indexing_ReviewIndex_Scan_WithNoPdfs_ReturnsEmptyIndex - - Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex - children: [ReviewMark-Index-PdfParsing, ReviewMark-Index-Freshness] - - - id: ReviewMark-Indexing-Save - title: The tool shall save the review evidence index to a JSON file for later loading. - justification: | - After scanning PDF evidence files, the resulting index must be persisted to disk - so that plan and report generation can consume the evidence without re-scanning. - The save format must be compatible with the load format so that a round-trip - preserves all evidence entries without data loss. - tests: - - Indexing_ReviewIndex_SaveAndLoad_RoundTrip - children: [ReviewMark-Index-Save] - - - id: ReviewMark-Indexing-SafePathCombine - title: The tool shall combine file paths safely, rejecting path traversal sequences. - justification: | - Path traversal sequences (such as '..') in file paths could allow access to - files outside the intended directory. The Indexing subsystem must reject such - sequences to prevent unintended file system access in both evidence scanning - and index file operations. - tests: - - Indexing_SafePathCombine_WithIndexPath_LoadsIndex - - Indexing_SafePathCombine_WithTraversalInputs_Throws - children: [ReviewMark-PathHelpers-SafeCombine, ReviewMark-PathHelpers-NullRejection] diff --git a/docs/reqstream/review-mark/indexing/path-helpers.yaml b/docs/reqstream/review-mark/indexing/path-helpers.yaml index 4cd9b1c..b1aacd6 100644 --- a/docs/reqstream/review-mark/indexing/path-helpers.yaml +++ b/docs/reqstream/review-mark/indexing/path-helpers.yaml @@ -1,37 +1,45 @@ --- -# PathHelpers Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the PathHelpers software unit -# - This unit provides safe path operations that prevent path traversal attacks -# - It is used by ReviewIndex.cs and Validation.cs when constructing file paths - +# PathHelpers unit requirements sections: - - title: PathHelpers Unit Requirements - requirements: - - id: ReviewMark-PathHelpers-SafeCombine - title: The PathHelpers shall safely combine a base path and a relative path, rejecting path traversal attempts. - justification: | - When constructing file paths from user-supplied or externally-sourced components - (such as relative paths read from an evidence index), the tool must prevent path - traversal attacks. The combined path must be verified to remain within the base - directory, ensuring that no input can cause access to files outside the intended - directory scope. - tests: - - PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly - - PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException - - PathHelpers_SafePathCombine_AbsolutePath_ThrowsArgumentException - - PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly - - PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException - - PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly - - PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath + - title: ReviewMark Requirements + sections: + - title: Indexing Requirements + sections: + - title: PathHelpers Requirements + requirements: + - id: ReviewMark-PathHelpers-SafeCombine + title: The PathHelpers shall safely combine a base path and a relative path. + justification: | + When constructing file paths from user-supplied or externally-sourced components + (such as relative paths read from an evidence index), the tool must be able to + combine a base directory path with a relative path to produce a valid result. + This is a prerequisite for any subsequent path-traversal validation. + tests: + - PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly + - PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly + - PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly + - PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath + + - id: ReviewMark-PathHelpers-TraversalRejection + title: The PathHelpers shall reject path traversal attempts by throwing ArgumentException. + justification: | + When combining paths from user-supplied or externally-sourced components, the tool + must prevent path traversal attacks. The combined path must be verified to remain + within the base directory, ensuring that no input can cause access to files outside + the intended directory scope. This includes rejection of relative traversal segments + (such as `..`) and absolute path injection. + tests: + - PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException + - PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException + - PathHelpers_SafePathCombine_AbsoluteUnixPath_ThrowsArgumentException + - windows@PathHelpers_SafePathCombine_AbsoluteWindowsPath_ThrowsArgumentException - - id: ReviewMark-PathHelpers-NullRejection - title: "The PathHelpers shall reject null inputs by throwing ArgumentNullException." - justification: | - When basePath or relativePath is null, the operation must be rejected with a - clear error so the caller can report a diagnostic rather than encountering an - unhandled error. - tests: - - PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException - - PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException + - id: ReviewMark-PathHelpers-NullRejection + title: "The PathHelpers shall reject null inputs by throwing ArgumentNullException." + justification: | + When basePath or relativePath is null, the operation must be rejected with a + clear error so the caller can report a diagnostic rather than encountering an + unhandled error. + tests: + - PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException + - PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException diff --git a/docs/reqstream/review-mark/indexing/review-index.yaml b/docs/reqstream/review-mark/indexing/review-index.yaml index 00f3133..28f92f4 100644 --- a/docs/reqstream/review-mark/indexing/review-index.yaml +++ b/docs/reqstream/review-mark/indexing/review-index.yaml @@ -1,131 +1,160 @@ --- -# ReviewIndex Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the ReviewIndex software unit -# - This unit loads review evidence from an EvidenceSource (none/fileshare/url) -# - It also scans PDF files to extract embedded review metadata for indexing - +# ReviewIndex unit requirements sections: - - title: Index Unit Requirements - requirements: - - id: ReviewMark-Index-EvidenceSource - title: The tool shall load a ReviewIndex from an EvidenceSource supporting none, fileshare, and url types. - justification: | - The tool must be able to load review evidence index data from the EvidenceSource - specified in its configuration. Three source types are supported: `none` returns an - empty index immediately (useful during initial project setup), `fileshare` loads - the index JSON from a local or network file path, and `url` downloads it over - HTTP(S) with optional Basic-auth credentials read from environment variables. - tests: - - ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException - - ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException - - ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex - - ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex - - ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile - - ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException - - ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException - - ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex - - ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex - - ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries - - ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex - - ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException - - ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException - - ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException + - title: ReviewMark Requirements + sections: + - title: Indexing Requirements + sections: + - title: ReviewIndex Requirements + requirements: + - id: ReviewMark-Index-EvidenceSource + title: >- + The tool shall dispatch to the appropriate evidence loader based on the + EvidenceSource type and reject null or unsupported source types. + justification: | + The ReviewIndex.Load method must validate its inputs and route loading to the + correct strategy based on the evidence source type. Null sources must be rejected + immediately with ArgumentNullException. Unrecognized source types must fail with + a clear InvalidOperationException so callers receive actionable diagnostics. + The three supported source types (none, fileshare, url) are each handled by a + dedicated atomic child requirement. + tests: + - ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException + - ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException + - ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException + children: + - ReviewMark-Index-LoadFromFile + - ReviewMark-Index-LoadFromStream + - ReviewMark-Index-EvidenceSource-None + + - id: ReviewMark-Index-LoadFromFile + title: >- + The tool shall load a ReviewIndex from a fileshare EvidenceSource by reading + the index JSON from the specified file path. + justification: | + When the evidence source type is 'fileshare', the tool must read index.json from + the path specified in EvidenceSource.Location and deserialize it into a ReviewIndex. + Missing files, unreadable files, and malformed JSON must each produce an + InvalidOperationException with a message identifying the path and the underlying + failure, so callers receive actionable diagnostics without needing to inspect raw + I/O exceptions. + tests: + - ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile + - ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException + - ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException + - ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex + - ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex + - ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries + + - id: ReviewMark-Index-LoadFromStream + title: >- + The tool shall load a ReviewIndex from a url EvidenceSource by downloading + the index JSON over HTTP(S). + justification: | + When the evidence source type is 'url', the tool must issue an HTTP GET request + to the URL in EvidenceSource.Location and deserialize the response body as + index JSON. Non-success HTTP responses and malformed JSON responses must each + produce an InvalidOperationException identifying the URL and the failure reason. + A testable overload accepting a caller-supplied HttpClient enables unit tests to + verify URL-source behavior without real network access. + tests: + - ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex + - ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException + - ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException - - id: ReviewMark-EvidenceSource-None - title: The tool shall support a 'none' evidence source type that provides no review evidence. - justification: | - When a project is first starting out, it should be able to set the evidence-source - to 'none' until an evidence store is provisioned. The 'none' type requires no - location field and always returns an empty index, allowing the tool to run without - error during initial repository setup. - tests: - - ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex - - ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex + - id: ReviewMark-Index-EvidenceSource-None + title: The tool shall support a 'none' evidence source type that provides no review evidence. + justification: | + When a project is first starting out, it should be able to set the evidence-source + to 'none' until an evidence store is provisioned. The 'none' type requires no + location field and always returns an empty index, allowing the tool to run without + error during initial repository setup. + tests: + - ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex + - ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex - - id: ReviewMark-Index-PdfParsing - title: The tool shall parse PDF metadata from the Keywords field when indexing evidence files. - justification: | - When scanning PDF evidence files, the tool must read the standard PDF Keywords - field and extract space-separated `name=value` pairs. All four fields — id, - fingerprint, date, and result — are required for an entry to be indexed; PDFs - whose Keywords field is missing any of these fields (or is entirely absent) must - be skipped with a warning, ensuring the index only contains complete, valid entries. - tests: - - ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex - - ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning - - ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning - - ReviewIndex_Scan_PdfWithMissingDate_SkipsWithWarning - - ReviewIndex_Scan_PdfWithMissingResult_SkipsWithWarning - - ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning - - ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty - - ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries + - id: ReviewMark-Index-PdfParsing + title: The tool shall parse PDF metadata from the Keywords field when indexing evidence files. + justification: | + When scanning PDF evidence files, the tool must read the standard PDF Keywords + field and extract space-separated `name=value` pairs. All four fields — id, + fingerprint, date, and result — are required for an entry to be indexed; PDFs + whose Keywords field is missing any of these fields (or is entirely absent) must + be skipped with a warning, ensuring the index only contains complete, valid entries. + tests: + - ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex + - ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning + - ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning + - ReviewIndex_Scan_PdfWithMissingDate_SkipsWithWarning + - ReviewIndex_Scan_PdfWithMissingResult_SkipsWithWarning + - ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning + - ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty + - ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries - - id: ReviewMark-Index-Freshness - title: >- - The ReviewIndex.Scan() method shall return a new index containing only the - records found in the current scan. - justification: | - The Scan() factory method always creates a fresh ReviewIndex, ensuring that - entries from any previous index do not contaminate the result. This guarantees - that the scan result reflects only the evidence files present in the scanned directory. - tests: - - ReviewIndex_Scan_ClearsExistingEntries + - id: ReviewMark-Index-Freshness + title: >- + The ReviewIndex.Scan() method shall return a new index containing only the + records found in the current scan. + justification: | + The Scan() factory method always creates a fresh ReviewIndex, ensuring that + entries from any previous index do not contaminate the result. This guarantees + that the scan result reflects only the evidence files present in the scanned directory. + tests: + - ReviewIndex_Scan_ClearsExistingEntries - - id: ReviewMark-Index-Empty - title: The ReviewIndex unit shall support creating a new empty index with no evidence records. - justification: | - When the evidence source type is 'none', or when an empty index is needed - as an initial state, the ReviewIndex must provide a consistent way to create - an empty index without loading from any external source. - tests: - - ReviewIndex_Empty_ReturnsEmptyIndex + - id: ReviewMark-Index-Empty + title: The ReviewIndex unit shall support creating a new empty index with no evidence records. + justification: | + When the evidence source type is 'none', or when an empty index is needed + as an initial state, the ReviewIndex must provide a consistent way to create + an empty index without loading from any external source. + tests: + - ReviewIndex_Empty_ReturnsEmptyIndex - - id: ReviewMark-Index-Save - title: The ReviewIndex unit shall persist the evidence index to a file or stream. - justification: | - After scanning PDF evidence files, the resulting index must be persisted so - that other tools can consume it. The ReviewIndex must support writing to both - a file path and a stream, enabling both direct file output and in-memory - serialization for testing. - tests: - - ReviewIndex_Save_Stream_NullStream_ThrowsArgumentNullException - - ReviewIndex_Save_File_EmptyPath_ThrowsArgumentException - - ReviewIndex_Save_File_NullPath_ThrowsArgumentException - - ReviewIndex_Save_RoundTrip_PreservesAllEntries + - id: ReviewMark-Index-Save + title: The ReviewIndex unit shall persist the evidence index to a file or stream. + justification: | + After scanning PDF evidence files, the resulting index must be persisted so + that other tools can consume it. The ReviewIndex must support writing to both + a file path and a stream, enabling both direct file output and in-memory + serialization for testing. + tests: + - ReviewIndex_Save_Stream_NullStream_ThrowsArgumentNullException + - ReviewIndex_Save_File_EmptyPath_ThrowsArgumentException + - ReviewIndex_Save_File_NullPath_ThrowsArgumentException + - ReviewIndex_Save_RoundTrip_PreservesAllEntries - - id: ReviewMark-Index-GetEvidence - title: >- - The ReviewIndex unit shall look up a review evidence record by ID and - fingerprint, returning null if no match exists. - justification: | - When looking up review evidence by ID and fingerprint, the ReviewIndex must - return the matching evidence record if one exists, or null if no record matches - both the given ID and fingerprint. - tests: - - ReviewIndex_GetEvidence_ExistingEntry_ReturnsEvidence - - ReviewIndex_GetEvidence_WrongFingerprint_ReturnsNull - - ReviewIndex_GetEvidence_UnknownId_ReturnsNull + - id: ReviewMark-Index-GetEvidence + title: >- + The ReviewIndex unit shall look up a review evidence record by ID and + fingerprint, returning null if no match exists. + justification: | + When looking up review evidence by ID and fingerprint, the ReviewIndex must + return the matching evidence record if one exists, or null if no record matches + both the given ID and fingerprint. + tests: + - ReviewIndex_GetEvidence_ExistingEntry_ReturnsEvidence + - ReviewIndex_GetEvidence_WrongFingerprint_ReturnsNull + - ReviewIndex_GetEvidence_UnknownId_ReturnsNull - - id: ReviewMark-Index-HasId - title: The ReviewIndex unit shall indicate whether any evidence record exists for a given ID. - justification: | - When checking whether an ID has any associated review evidence, the ReviewIndex - must return true if at least one record exists for the given ID regardless of - fingerprint, and false if no record exists. - tests: - - ReviewIndex_HasId_ExistingId_ReturnsTrue - - ReviewIndex_HasId_UnknownId_ReturnsFalse + - id: ReviewMark-Index-HasId + title: The ReviewIndex unit shall indicate whether any evidence record exists for a given ID. + justification: | + When checking whether an ID has any associated review evidence, the ReviewIndex + must return true if at least one record exists for the given ID regardless of + fingerprint, and false if no record exists. + tests: + - ReviewIndex_HasId_ExistingId_ReturnsTrue + - ReviewIndex_HasId_UnknownId_ReturnsFalse - - id: ReviewMark-Index-GetAllForId - title: >- - The ReviewIndex unit shall retrieve all evidence records for a given ID, - returning an empty collection when none exist. - justification: | - When retrieving all review evidence for a given ID, the ReviewIndex must return - all matching records as a collection. If no records exist for the ID, an empty - collection must be returned. - tests: - - ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries - - ReviewIndex_GetAllForId_UnknownId_ReturnsEmptyList + - id: ReviewMark-Index-GetAllForId + title: >- + The ReviewIndex unit shall retrieve all evidence records for a given ID, + returning an empty collection when none exist. + justification: | + When retrieving all review evidence for a given ID, the ReviewIndex must return + all matching records as a collection. If no records exist for the ID, an empty + collection must be returned. + tests: + - ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries + - ReviewIndex_GetAllForId_UnknownId_ReturnsEmptyList diff --git a/docs/reqstream/review-mark/platform-requirements.yaml b/docs/reqstream/review-mark/platform-requirements.yaml index a80c0ba..3fb98da 100644 --- a/docs/reqstream/review-mark/platform-requirements.yaml +++ b/docs/reqstream/review-mark/platform-requirements.yaml @@ -1,104 +1,108 @@ --- -# Platform Support Requirements -# -# PURPOSE: -# - Define requirements for cross-platform support -# - These requirements verify the tool builds and runs on all supported operating systems -# and .NET runtime versions -# - Tests are linked with source filters to ensure results come from specific platforms - +# ReviewMark platform requirements sections: - - title: Platform Support Requirements - requirements: - - id: ReviewMark-Platform-Windows - title: The tool shall build and run on Windows platforms. - justification: | - DEMA Consulting tools must support Windows as a major development platform. - tests: - # Tests link to "windows" to ensure results come from Windows platform - - "windows@ReviewMark_VersionDisplay" - - "windows@ReviewMark_HelpDisplay" - - "windows@ReviewMark_ReviewPlanGeneration" - - "windows@ReviewMark_ReviewReportGeneration" - - "windows@ReviewMark_IndexScan" - - "windows@ReviewMark_Enforce" - - "windows@ReviewMark_WorkingDirectoryOverride" - - "windows@ReviewMark_Elaborate" - - "windows@ReviewMark_Lint" + - title: ReviewMark Requirements + sections: + - title: Platform Support Requirements + requirements: + - id: ReviewMark-Platform-Windows + title: The tool shall build and run on Windows platforms. + justification: | + DEMA Consulting tools must support Windows as a major development platform. + tests: + # Test source pattern "windows@" ensures these tests ran on Windows. + # This filtering is necessary to prove Windows OS functionality. + - "windows@ReviewMark_VersionDisplay" + - "windows@ReviewMark_HelpDisplay" + - "windows@ReviewMark_ReviewPlanGeneration" + - "windows@ReviewMark_ReviewReportGeneration" + - "windows@ReviewMark_IndexScan" + - "windows@ReviewMark_Enforce" + - "windows@ReviewMark_WorkingDirectoryOverride" + - "windows@ReviewMark_Elaborate" + - "windows@ReviewMark_Lint" - - id: ReviewMark-Platform-Linux - title: The tool shall build and run on Linux platforms. - justification: | - DEMA Consulting tools must support Linux for CI/CD and containerized environments. - tests: - # Tests link to "ubuntu" to ensure results come from Linux platform - - "ubuntu@ReviewMark_VersionDisplay" - - "ubuntu@ReviewMark_HelpDisplay" - - "ubuntu@ReviewMark_ReviewPlanGeneration" - - "ubuntu@ReviewMark_ReviewReportGeneration" - - "ubuntu@ReviewMark_IndexScan" - - "ubuntu@ReviewMark_Enforce" - - "ubuntu@ReviewMark_WorkingDirectoryOverride" - - "ubuntu@ReviewMark_Elaborate" - - "ubuntu@ReviewMark_Lint" + - id: ReviewMark-Platform-Linux + title: The tool shall build and run on Linux platforms. + justification: | + DEMA Consulting tools must support Linux for CI/CD and containerized environments. + tests: + # Test source pattern "ubuntu@" ensures these tests ran on Linux. + # This filtering is necessary to prove Linux OS functionality. + - "ubuntu@ReviewMark_VersionDisplay" + - "ubuntu@ReviewMark_HelpDisplay" + - "ubuntu@ReviewMark_ReviewPlanGeneration" + - "ubuntu@ReviewMark_ReviewReportGeneration" + - "ubuntu@ReviewMark_IndexScan" + - "ubuntu@ReviewMark_Enforce" + - "ubuntu@ReviewMark_WorkingDirectoryOverride" + - "ubuntu@ReviewMark_Elaborate" + - "ubuntu@ReviewMark_Lint" - - id: ReviewMark-Platform-MacOS - title: The tool shall build and run on macOS platforms. - justification: | - DEMA Consulting tools must support macOS for developers using Apple platforms. - tests: - # Tests link to "macos" to ensure results come from macOS platform - - "macos@ReviewMark_VersionDisplay" - - "macos@ReviewMark_HelpDisplay" - - "macos@ReviewMark_ReviewPlanGeneration" - - "macos@ReviewMark_ReviewReportGeneration" - - "macos@ReviewMark_IndexScan" - - "macos@ReviewMark_Enforce" - - "macos@ReviewMark_WorkingDirectoryOverride" - - "macos@ReviewMark_Elaborate" - - "macos@ReviewMark_Lint" + - id: ReviewMark-Platform-MacOS + title: The tool shall build and run on macOS platforms. + justification: | + DEMA Consulting tools must support macOS for developers using Apple platforms. + tests: + # Test source pattern "macos@" ensures these tests ran on macOS. + # This filtering is necessary to prove macOS OS functionality. + - "macos@ReviewMark_VersionDisplay" + - "macos@ReviewMark_HelpDisplay" + - "macos@ReviewMark_ReviewPlanGeneration" + - "macos@ReviewMark_ReviewReportGeneration" + - "macos@ReviewMark_IndexScan" + - "macos@ReviewMark_Enforce" + - "macos@ReviewMark_WorkingDirectoryOverride" + - "macos@ReviewMark_Elaborate" + - "macos@ReviewMark_Lint" - - id: ReviewMark-Platform-Net8 - title: The tool shall support .NET 8 runtime. - justification: | - .NET 8 is an LTS release providing long-term stability for enterprise users. - tests: - - "dotnet8.x@ReviewMark_VersionDisplay" - - "dotnet8.x@ReviewMark_HelpDisplay" - - "dotnet8.x@ReviewMark_ReviewPlanGeneration" - - "dotnet8.x@ReviewMark_ReviewReportGeneration" - - "dotnet8.x@ReviewMark_IndexScan" - - "dotnet8.x@ReviewMark_Enforce" - - "dotnet8.x@ReviewMark_WorkingDirectoryOverride" - - "dotnet8.x@ReviewMark_Elaborate" - - "dotnet8.x@ReviewMark_Lint" + - id: ReviewMark-Platform-Net8 + title: The tool shall support .NET 8 runtime. + justification: | + .NET 8 is an LTS release providing long-term stability for enterprise users. + tests: + # Test source pattern "dotnet8.x@" ensures these tests ran on .NET 8. + # This filtering is necessary to prove .NET 8 runtime support. + - "dotnet8.x@ReviewMark_VersionDisplay" + - "dotnet8.x@ReviewMark_HelpDisplay" + - "dotnet8.x@ReviewMark_ReviewPlanGeneration" + - "dotnet8.x@ReviewMark_ReviewReportGeneration" + - "dotnet8.x@ReviewMark_IndexScan" + - "dotnet8.x@ReviewMark_Enforce" + - "dotnet8.x@ReviewMark_WorkingDirectoryOverride" + - "dotnet8.x@ReviewMark_Elaborate" + - "dotnet8.x@ReviewMark_Lint" - - id: ReviewMark-Platform-Net9 - title: The tool shall support .NET 9 runtime. - justification: | - .NET 9 support enables users to leverage the latest .NET features. - tests: - - "dotnet9.x@ReviewMark_VersionDisplay" - - "dotnet9.x@ReviewMark_HelpDisplay" - - "dotnet9.x@ReviewMark_ReviewPlanGeneration" - - "dotnet9.x@ReviewMark_ReviewReportGeneration" - - "dotnet9.x@ReviewMark_IndexScan" - - "dotnet9.x@ReviewMark_Enforce" - - "dotnet9.x@ReviewMark_WorkingDirectoryOverride" - - "dotnet9.x@ReviewMark_Elaborate" - - "dotnet9.x@ReviewMark_Lint" + - id: ReviewMark-Platform-Net9 + title: The tool shall support .NET 9 runtime. + justification: | + .NET 9 support enables users to leverage the latest .NET features. + tests: + # Test source pattern "dotnet9.x@" ensures these tests ran on .NET 9. + # This filtering is necessary to prove .NET 9 runtime support. + - "dotnet9.x@ReviewMark_VersionDisplay" + - "dotnet9.x@ReviewMark_HelpDisplay" + - "dotnet9.x@ReviewMark_ReviewPlanGeneration" + - "dotnet9.x@ReviewMark_ReviewReportGeneration" + - "dotnet9.x@ReviewMark_IndexScan" + - "dotnet9.x@ReviewMark_Enforce" + - "dotnet9.x@ReviewMark_WorkingDirectoryOverride" + - "dotnet9.x@ReviewMark_Elaborate" + - "dotnet9.x@ReviewMark_Lint" - - id: ReviewMark-Platform-Net10 - title: The tool shall support .NET 10 runtime. - justification: | - .NET 10 support ensures the tool remains compatible with the latest .NET ecosystem. - tests: - - "dotnet10.x@ReviewMark_VersionDisplay" - - "dotnet10.x@ReviewMark_HelpDisplay" - - "dotnet10.x@ReviewMark_ReviewPlanGeneration" - - "dotnet10.x@ReviewMark_ReviewReportGeneration" - - "dotnet10.x@ReviewMark_IndexScan" - - "dotnet10.x@ReviewMark_Enforce" - - "dotnet10.x@ReviewMark_WorkingDirectoryOverride" - - "dotnet10.x@ReviewMark_Elaborate" - - "dotnet10.x@ReviewMark_Lint" + - id: ReviewMark-Platform-Net10 + title: The tool shall support .NET 10 runtime. + justification: | + .NET 10 support ensures the tool remains compatible with the latest .NET ecosystem. + tests: + # Test source pattern "dotnet10.x@" ensures these tests ran on .NET 10. + # This filtering is necessary to prove .NET 10 runtime support. + - "dotnet10.x@ReviewMark_VersionDisplay" + - "dotnet10.x@ReviewMark_HelpDisplay" + - "dotnet10.x@ReviewMark_ReviewPlanGeneration" + - "dotnet10.x@ReviewMark_ReviewReportGeneration" + - "dotnet10.x@ReviewMark_IndexScan" + - "dotnet10.x@ReviewMark_Enforce" + - "dotnet10.x@ReviewMark_WorkingDirectoryOverride" + - "dotnet10.x@ReviewMark_Elaborate" + - "dotnet10.x@ReviewMark_Lint" diff --git a/docs/reqstream/review-mark/program.yaml b/docs/reqstream/review-mark/program.yaml index 201fb78..4dc128a 100644 --- a/docs/reqstream/review-mark/program.yaml +++ b/docs/reqstream/review-mark/program.yaml @@ -1,93 +1,120 @@ --- -# Program Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the Program software unit -# - This unit is the main entry point and top-level orchestrator of the tool -# - It dispatches to processing logic based on parsed CLI flags - +# Program unit requirements sections: - - title: Program Unit Requirements - requirements: - - id: ReviewMark-Program-EntryPoint - title: >- - The Program unit shall parse command-line arguments, execute the requested operation, - and return an exit code reflecting success or failure. - justification: | - The Program unit is the process entry point. It must parse command-line arguments, - perform the requested operation, and return an exit code so that callers can detect - success or failure programmatically. Argument errors and invalid operations are - converted to exit code 1. Other unexpected exceptions are written to error output - and then rethrown, so callers may observe either a normal exit code or a process - termination due to an unhandled exception. - tests: - - Program_Run_WithVersionFlag_DisplaysVersionOnly - - Program_Version_ReturnsNonEmptyString - - Program_Run_WithHelpFlag_DisplaysUsageInformation + - title: ReviewMark Requirements + sections: + - title: Program Requirements + requirements: + - id: ReviewMark-Program-ParseArguments + title: The Program unit shall parse command-line arguments and construct an execution context. + justification: | + The Program unit is the process entry point. Parsing command-line arguments into a + structured execution context is the first step of every invocation and the mechanism + by which all flags (--version, --help, --lint, --index, etc.) are recognized. + Argument errors are caught during parsing and converted to exit code 1 so that + callers can detect malformed invocations programmatically. + tests: + - Program_Run_WithVersionFlag_DisplaysVersionOnly + - Program_Version_ReturnsNonEmptyString + + - id: ReviewMark-Program-ExecuteOperation + title: The Program unit shall execute exactly the requested operation based on the parsed context. + justification: | + After parsing, the Program unit dispatches to exactly one operation per invocation + based on the priority order of CLI flags (--version → --help → --validate → --lint + → --index → plan/report). Only the first matching flag action is executed; later + flags are not reached. This deterministic dispatch ensures predictable and + documented behavior. + tests: + - Program_Run_WithVersionFlag_DisplaysVersionOnly + - Program_Run_WithHelpFlag_DisplaysUsageInformation + - Program_Run_NoArguments_DisplaysDefaultBehavior + + - id: ReviewMark-Program-ExitCode + title: The Program unit shall return exit code 0 on success and 1 on failure. + justification: | + Callers (scripts, CI/CD pipelines) must be able to detect success or failure + programmatically via the process exit code. Argument errors and invalid operations + produce exit code 1. Unexpected exceptions are written to error output and rethrown, + so callers may observe either a normal exit code or a process termination due to an + unhandled exception. + tests: + - Program_HandleIssues_WithEnforce_SetsExitCode1 + + - id: ReviewMark-Program-Dispatch + title: >- + The Program unit shall dispatch to exactly one operation per invocation based on + the priority order of CLI flags. + justification: | + --version, --help, --validate, --lint, --index, and plan/report operations must + be evaluated in a fixed priority order so that the behavior is predictable and + documented. Only the first matching flag action is executed; later flags are + not reached. + tests: + - Program_Run_WithVersionFlag_DisplaysVersionOnly + - Program_Run_WithHelpFlag_DisplaysUsageInformation + - Program_Run_WithValidateFlag_RunsValidation + - Program_Run_WithLintFlag_ValidConfig_ReportsSuccess + - Program_Run_WithHelpFlag_IncludesElaborateOption + - Program_Run_WithHelpFlag_IncludesLintOption + - Program_Run_WithElaborateFlag_OutputsElaboration + - Program_Run_WithElaborateFlag_UnknownId_ReportsError + - Program_Run_NoArguments_DisplaysDefaultBehavior + - Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError - - id: ReviewMark-Program-Dispatch - title: >- - The Program unit shall dispatch to exactly one operation per invocation based on - the priority order of CLI flags. - justification: | - --version, --help, --validate, --lint, --index, and plan/report operations must - be evaluated in a fixed priority order so that the behavior is predictable and - documented. Only the first matching flag action is executed; later flags are - not reached. - tests: - - Program_Run_WithVersionFlag_DisplaysVersionOnly - - Program_Run_WithHelpFlag_DisplaysUsageInformation - - Program_Run_WithValidateFlag_RunsValidation - - Program_Run_WithLintFlag_ValidConfig_ReportsSuccess - - Program_Run_WithHelpFlag_IncludesElaborateOption - - Program_Run_WithHelpFlag_IncludesLintOption - - Program_Run_WithElaborateFlag_OutputsElaboration - - Program_Run_WithElaborateFlag_UnknownId_ReportsError - - Program_Run_NoArguments_DisplaysDefaultBehavior - - Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError + - id: ReviewMark-Program-LintVerbosity + title: >- + The Program unit shall suppress the application banner and produce no output + when lint succeeds, printing only issue messages on failure. + justification: | + When --lint is used from scripts or CI pipelines, any output other than the + actual issues pollutes the stream and complicates result detection. Suppressing + the banner and summary follows the Unix convention of silence-on-success, + making the exit code the sole signal for a clean definition file. + tests: + - Program_Run_WithLintFlag_ValidConfig_SuppressesBanner + - Program_Run_WithLintFlag_MissingConfig_ReportsError + - Program_Run_WithLintFlag_DuplicateIds_ReportsError + - Program_Run_WithLintFlag_UnknownSourceType_ReportsError + - Program_Run_WithLintFlag_CorruptedYaml_ReportsError + - Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError + - Program_Run_WithLintFlag_MultipleErrors_ReportsAll + - Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError - - id: ReviewMark-Program-LintVerbosity - title: >- - The Program unit shall suppress the application banner and produce no output - when lint succeeds, printing only issue messages on failure. - justification: | - When --lint is used from scripts or CI pipelines, any output other than the - actual issues pollutes the stream and complicates result detection. Suppressing - the banner and summary follows the Unix convention of silence-on-success, - making the exit code the sole signal for a clean definition file. - tests: - - Program_Run_WithLintFlag_ValidConfig_ReportsSuccess - - Program_Run_WithLintFlag_ValidConfig_SuppressesBanner - - Program_Run_WithLintFlag_MissingConfig_ReportsError - - Program_Run_WithLintFlag_DuplicateIds_ReportsError - - Program_Run_WithLintFlag_UnknownSourceType_ReportsError - - Program_Run_WithLintFlag_CorruptedYaml_ReportsError - - Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError - - Program_Run_WithLintFlag_MultipleErrors_ReportsAll - - Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError + - id: ReviewMark-Program-HandleIssues-Enforce + title: >- + The Program unit shall set the exit code to 1 when --enforce is set and review + issues are present. + justification: | + --enforce enables CI/CD pipelines to treat non-current reviews as a blocking + failure. When --enforce is set and issues are found, the Program unit must exit + with code 1 so that callers and pipeline gates can detect the failure + programmatically. This policy is applied consistently across all plan and report + operations. + tests: + - Program_HandleIssues_WithEnforce_SetsExitCode1 - - id: ReviewMark-Program-HandleIssues - title: >- - The Program unit shall set the exit code to 1 when --enforce is set and review - issues are present, and emit a non-fatal warning otherwise. - justification: | - --enforce enables CI/CD pipelines to treat non-current reviews as a blocking - failure. Without --enforce, the same issues produce only a warning so that - developers can run report generation locally without failing the process. This - dual-mode behavior is controlled by a single helper so that the policy is - applied consistently across all plan and report operations. - tests: - - Program_HandleIssues_WithEnforce_SetsExitCode1 - - Program_HandleIssues_WithoutEnforce_EmitsWarning + - id: ReviewMark-Program-HandleIssues-Warn + title: >- + The Program unit shall emit a non-fatal warning message and exit with code 0 when + issues are present but --enforce is not set. + justification: | + Without --enforce, the same issues that would block a pipeline should not prevent + a developer from running report generation locally. Emitting a warning message + while still exiting with code 0 surfaces the problem without terminating the + process abnormally, giving developers early visibility without hard-failing their + workflow. + tests: + - Program_HandleIssues_WithoutEnforce_EmitsWarning - - id: ReviewMark-Program-Index - title: >- - The Program unit shall scan PDF evidence files and write an index.json when the - --index flag is provided. - justification: | - When --index is provided, the Program unit must invoke RunIndexLogic() to scan - the specified glob paths for PDF evidence files and write the resulting index to - index.json in the working directory. This makes the --index flag a first-class - operation dispatched by Program.Run, just like --validate and --lint. - tests: - - Program_Run_WithIndexFlag_ScansAndWritesIndexFile + - id: ReviewMark-Program-Index + title: >- + The Program unit shall scan PDF evidence files and write an index.json when the + --index flag is provided. + justification: | + When --index is provided, the Program unit must invoke RunIndexLogic() to scan + the specified glob paths for PDF evidence files and write the resulting index to + index.json in the working directory. This makes the --index flag a first-class + operation dispatched by Program.Run, just like --validate and --lint. + tests: + - Program_Run_WithIndexFlag_ScansAndWritesIndexFile diff --git a/docs/reqstream/review-mark/self-test.yaml b/docs/reqstream/review-mark/self-test.yaml new file mode 100644 index 0000000..f8f4ce3 --- /dev/null +++ b/docs/reqstream/review-mark/self-test.yaml @@ -0,0 +1,71 @@ +--- +# SelfTest subsystem requirements +sections: + - title: ReviewMark Requirements + sections: + - title: SelfTest Requirements + requirements: + - id: ReviewMark-SelfTest-Qualification + title: The tool shall provide a self-validation mechanism to qualify the tool in its deployment environment. + justification: | + In regulated environments, tool qualification evidence is required to demonstrate + that the tool functions correctly in its deployment environment before it is used + to generate compliance artifacts. The SelfTest subsystem provides a built-in + self-validation suite that exercises core behaviors and produces a pass/fail + summary, enabling quality assurance teams to obtain tool qualification evidence + without requiring a separate test harness. + tests: + - SelfTest_Run_AllTestsPass_ExitCodeIsZero + children: [ReviewMark-Validation-Run] + + - id: ReviewMark-SelfTest-ResultsOutput-Trx + title: >- + The tool shall write self-validation results to a TRX (MSTest) file + when --results is provided with a .trx extension. + justification: | + CI/CD pipelines and requirements traceability tools (such as ReqStream) consume + TRX test result files. By supporting TRX output, the SelfTest subsystem enables + self-validation results to be fed directly into pipeline tooling and traceability + reports without additional conversion steps, satisfying audit trail requirements. + tests: + - SelfTest_Run_WithTrxResultsFile_WritesFile + children: [ReviewMark-Validation-ResultsFile] + + - id: ReviewMark-SelfTest-ResultsOutput-Junit + title: >- + The tool shall write self-validation results to a JUnit XML file + when --results is provided with a .xml extension. + justification: | + CI/CD pipelines and requirements traceability tools consume JUnit XML test result + files. By supporting JUnit XML output, the SelfTest subsystem enables + self-validation results to be fed directly into pipeline tooling without + additional conversion steps, satisfying audit trail requirements. + tests: + - SelfTest_Run_WithJUnitResultsFile_WritesFile + children: [ReviewMark-Validation-ResultsFile] + + - id: ReviewMark-SelfTest-ExitCodeOnFailure + title: The tool shall set the process exit code to 1 when any validation error occurs during self-validation. + justification: | + Callers such as CI/CD pipelines and automated qualification scripts rely on + the process exit code to determine whether tool qualification succeeded. + A non-zero exit code on any validation error (including test failures and + results output errors) ensures that a broken deployment environment is + detected without requiring inspection of the output text. + tests: + - SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero + children: [ReviewMark-Validation-Run] + + - id: ReviewMark-SelfTest-ConsoleSummary + title: >- + The tool shall write a human-readable summary of pass and fail counts to + the console after running self-validation. + justification: | + Operators running self-validation manually need an immediate summary of the + results without parsing machine-readable output. A console summary including + total, passed, and failed counts provides an at-a-glance status that lets + operators quickly confirm that qualification succeeded or identify how many + tests require investigation. + tests: + - SelfTest_Run_AllTestsPass_ExitCodeIsZero + children: [ReviewMark-Validation-Run] diff --git a/docs/reqstream/review-mark/self-test/self-test.yaml b/docs/reqstream/review-mark/self-test/self-test.yaml deleted file mode 100644 index edb431b..0000000 --- a/docs/reqstream/review-mark/self-test/self-test.yaml +++ /dev/null @@ -1,65 +0,0 @@ ---- -# SelfTest Subsystem Requirements -# -# PURPOSE: -# - Define requirements for the ReviewMark SelfTest subsystem -# - The SelfTest subsystem spans Validation.cs (self-validation test execution) -# - Subsystem requirements describe the self-validation mechanism for tool qualification -# in regulated environments - -sections: - - title: SelfTest Subsystem Requirements - requirements: - - id: ReviewMark-SelfTest-Qualification - title: The tool shall provide a self-validation mechanism to qualify the tool in its deployment environment. - justification: | - In regulated environments, tool qualification evidence is required to demonstrate - that the tool functions correctly in its deployment environment before it is used - to generate compliance artifacts. The SelfTest subsystem provides a built-in - self-validation suite that exercises core behaviors and produces a pass/fail - summary, enabling quality assurance teams to obtain tool qualification evidence - without requiring a separate test harness. - tests: - - SelfTest_Run_AllTestsPass_ExitCodeIsZero - children: [ReviewMark-Validation-Run] - - - id: ReviewMark-SelfTest-ResultsOutput - title: >- - The tool shall write self-validation results to a TRX (MSTest) or JUnit XML - file when --results is provided. - justification: | - CI/CD pipelines and requirements traceability tools (such as ReqStream) consume - test result files in standard formats. By supporting both TRX (MSTest) and JUnit - XML output, the SelfTest subsystem enables self-validation results to be fed - directly into pipeline tooling and traceability reports without additional - conversion steps, satisfying audit trail requirements. - tests: - - SelfTest_Run_GeneratesResultsFile - - SelfTest_Run_GeneratesJUnitResultsFile - children: [ReviewMark-Validation-ResultsFile] - - - id: ReviewMark-SelfTest-ExitCodeOnFailure - title: The tool shall set the process exit code to 1 when any validation error occurs during self-validation. - justification: | - Callers such as CI/CD pipelines and automated qualification scripts rely on - the process exit code to determine whether tool qualification succeeded. - A non-zero exit code on any validation error (including test failures and - results output errors) ensures that a broken deployment environment is - detected without requiring inspection of the output text. - tests: - - SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero - children: [ReviewMark-Validation-Run] - - - id: ReviewMark-SelfTest-ConsoleSummary - title: >- - The tool shall write a human-readable summary of pass and fail counts to - the console after running self-validation. - justification: | - Operators running self-validation manually need an immediate summary of the - results without parsing machine-readable output. A console summary including - total, passed, and failed counts provides an at-a-glance status that lets - operators quickly confirm that qualification succeeded or identify how many - tests require investigation. - tests: - - SelfTest_Run_AllTestsPass_ExitCodeIsZero - children: [ReviewMark-Validation-Run] diff --git a/docs/reqstream/review-mark/self-test/validation.yaml b/docs/reqstream/review-mark/self-test/validation.yaml index fc54c64..535176e 100644 --- a/docs/reqstream/review-mark/self-test/validation.yaml +++ b/docs/reqstream/review-mark/self-test/validation.yaml @@ -1,38 +1,36 @@ --- -# Validation Software Unit Requirements -# -# PURPOSE: -# - Define requirements for the Validation software unit -# - This unit provides self-validation test execution for regulated environments -# - Self-validation proves the tool is functioning correctly in its deployment environment - +# Validation unit requirements sections: - - title: Validation Unit Requirements - requirements: - - id: ReviewMark-Validation-Run - title: The tool shall execute self-validation tests and report results when the --validate flag is provided. - justification: | - In regulated environments, tool qualification evidence is required to demonstrate - that the tool functions correctly in its deployment environment. Self-validation - runs a suite of functional tests covering core behaviors and reports pass/fail - results with a summary count, giving quality assurance teams the evidence they need. - tests: - - Validation_Run_NullContext_ThrowsArgumentNullException - - Validation_Run_WritesValidationHeader - - Validation_Run_WritesSummaryWithTotalTests - - Validation_Run_AllTestsPass_ExitCodeIsZero + - title: ReviewMark Requirements + sections: + - title: SelfTest Requirements + sections: + - title: Validation Requirements + requirements: + - id: ReviewMark-Validation-Run + title: The tool shall execute self-validation tests and report results when the --validate flag is provided. + justification: | + In regulated environments, tool qualification evidence is required to demonstrate + that the tool functions correctly in its deployment environment. Self-validation + runs a suite of functional tests covering core behaviors and reports pass/fail + results with a summary count, giving quality assurance teams the evidence they need. + tests: + - Validation_Run_NullContext_ThrowsArgumentNullException + - Validation_Run_WritesValidationHeader + - Validation_Run_WritesSummaryWithTotalTests + - Validation_Run_AllTestsPass_ExitCodeIsZero - - id: ReviewMark-Validation-ResultsFile - title: The tool shall write self-validation results to a TRX or JUnit XML file when --results is provided. - justification: | - CI/CD pipelines and requirements traceability tools (such as ReqStream) consume - test result files in standard formats. By supporting both TRX (MSTest) and JUnit - XML output, the self-validation results can be fed directly into pipeline tooling - without additional conversion steps. If the results file has an unsupported - extension, an error is written via WriteError and the validation run continues - without writing a results file. - tests: - - Validation_Run_WithTrxResultsFile_WritesFile - - Validation_Run_WithXmlResultsFile_WritesFile - - Validation_Run_WithResultsFileInNewDirectory_CreatesDirectory - - Validation_Run_WithUnsupportedResultsFileExtension_WritesError + - id: ReviewMark-Validation-ResultsFile + title: The tool shall write self-validation results to a TRX or JUnit XML file when --results is provided. + justification: | + CI/CD pipelines and requirements traceability tools (such as ReqStream) consume + test result files in standard formats. By supporting both TRX (MSTest) and JUnit + XML output, the self-validation results can be fed directly into pipeline tooling + without additional conversion steps. If the results file has an unsupported + extension, an error is written via WriteError and the validation run continues + without writing a results file. + tests: + - Validation_Run_WithTrxResultsFile_WritesFile + - Validation_Run_WithXmlResultsFile_WritesFile + - Validation_Run_WithResultsFileInNewDirectory_CreatesDirectory + - Validation_Run_WithUnsupportedResultsFileExtension_WritesError diff --git a/docs/requirements_doc/definition.yaml b/docs/requirements_doc/definition.yaml index 628b789..bc9c807 100644 --- a/docs/requirements_doc/definition.yaml +++ b/docs/requirements_doc/definition.yaml @@ -1,12 +1,10 @@ --- -resource-path: - - docs/requirements_doc - - docs/template +resource-path: [docs/requirements_doc, docs/template] input-files: - docs/requirements_doc/title.txt - docs/requirements_doc/introduction.md - - docs/requirements_doc/generated/requirements.md - - docs/requirements_doc/generated/justifications.md + - docs/requirements_doc/generated/requirements.md # Generated by ReqStream (requirements listing) + - docs/requirements_doc/generated/justifications.md # Generated by ReqStream (requirement justifications) template: template.html table-of-contents: true number-sections: true diff --git a/docs/requirements_doc/introduction.md b/docs/requirements_doc/introduction.md index d7ff046..141e698 100644 --- a/docs/requirements_doc/introduction.md +++ b/docs/requirements_doc/introduction.md @@ -1,30 +1,16 @@ # Introduction -This document contains the requirements for the ReviewMark project. +This document lists all requirements for DemaConsulting.ReviewMark. ## Purpose -ReviewMark is a reference implementation demonstrating best practices for DEMA Consulting -.NET command-line tools. It provides a standardized approach to command-line argument parsing, -self-validation, and comprehensive documentation generation. +To provide a complete, traceable record of all requirements for DemaConsulting.ReviewMark, +including requirements at the system, subsystem, and unit levels, plus OTS and Shared Package requirements. ## Scope -This requirements document covers: +This document covers all requirements defined in `docs/reqstream/` for DemaConsulting.ReviewMark. -- Command-line interface and options -- Self-validation framework -- Test result output formats -- Logging capabilities -- Multi-platform support -- Documentation generation -- CI/CD integration +## References -## Audience - -This document is intended for: - -- Software developers working on ReviewMark -- Quality assurance teams validating requirements -- Project stakeholders reviewing project scope -- Users understanding the tool's capabilities +N/A diff --git a/docs/requirements_doc/title.txt b/docs/requirements_doc/title.txt index ebec54c..4eb8d65 100644 --- a/docs/requirements_doc/title.txt +++ b/docs/requirements_doc/title.txt @@ -1,13 +1,14 @@ --- -title: ReviewMark Requirements -subtitle: Requirements Specification for the ReviewMark +title: "DemaConsulting.ReviewMark Requirements Document" +subtitle: "Automated file-review evidence management" author: DEMA Consulting -description: Requirements Specification for the ReviewMark +description: "Requirements Document for DemaConsulting.ReviewMark" lang: en-US keywords: - ReviewMark - Requirements - Specification - .NET + - C# - Command-Line Tool --- diff --git a/docs/requirements_report/definition.yaml b/docs/requirements_report/definition.yaml index 9ee62a4..e8b5571 100644 --- a/docs/requirements_report/definition.yaml +++ b/docs/requirements_report/definition.yaml @@ -1,11 +1,9 @@ --- -resource-path: - - docs/requirements_report - - docs/template +resource-path: [docs/requirements_report, docs/template] input-files: - docs/requirements_report/title.txt - docs/requirements_report/introduction.md - - docs/requirements_report/generated/trace_matrix.md + - docs/requirements_report/generated/trace_matrix.md # Generated by ReqStream (requirements traceability matrix) template: template.html table-of-contents: true number-sections: true diff --git a/docs/requirements_report/introduction.md b/docs/requirements_report/introduction.md index ed694f1..ccd02f2 100644 --- a/docs/requirements_report/introduction.md +++ b/docs/requirements_report/introduction.md @@ -1,21 +1,16 @@ # Introduction -This document contains the requirements traceability matrix for the ReviewMark project. +This document provides the requirements Trace Matrix for DemaConsulting.ReviewMark, +mapping each requirement to its corresponding test evidence. ## Purpose -The trace matrix provides traceability between requirements and test cases, ensuring that -all requirements are validated through appropriate testing. This demonstrates that the -ReviewMark meets its specified requirements. +To demonstrate that every requirement is covered by at least one passing test, +providing compliance evidence for DemaConsulting.ReviewMark. ## Scope -This traceability matrix covers: - -- Mapping of requirements to test cases -- Test execution results linked to requirements -- Validation of requirement coverage -- Platform-specific test execution +This document covers all requirements in `docs/reqstream/` and their test evidence. ## Audience @@ -25,3 +20,7 @@ This document is intended for: - Quality assurance teams validating requirements coverage - Project stakeholders reviewing test coverage - Auditors verifying requirements traceability + +## References + +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) diff --git a/docs/requirements_report/title.txt b/docs/requirements_report/title.txt index 70820a4..bb80763 100644 --- a/docs/requirements_report/title.txt +++ b/docs/requirements_report/title.txt @@ -1,14 +1,15 @@ --- -title: ReviewMark Trace Matrix -subtitle: Requirements Traceability Matrix for the ReviewMark +title: DemaConsulting.ReviewMark Trace Matrix +subtitle: Automated file-review evidence management author: DEMA Consulting -description: Requirements Traceability Matrix for the ReviewMark +description: Trace Matrix for DemaConsulting.ReviewMark lang: en-US keywords: - ReviewMark + - Requirements - Trace Matrix - Traceability - - Requirements - Testing - .NET + - C# --- diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 8e5f721..fa28c2a 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -1,29 +1,42 @@ # Introduction +This guide describes how to install, configure, and use ReviewMark. + ## Purpose -ReviewMark is a tool for automated file-review evidence management in regulated environments. -It computes cryptographic fingerprints of defined file-sets, queries a review evidence store -for corresponding code-review PDFs, and produces compliance documents with every CI/CD run. +ReviewMark is a DEMA Consulting command-line tool for automated file-review evidence management in +regulated environments. It computes cryptographic fingerprints of defined file-sets, queries a +review evidence store for corresponding code-review PDFs, and produces compliance documents on every +CI/CD run. ## Scope -This user guide covers: +This guide covers: -- Installation instructions +- Installation of the ReviewMark .NET global tool - Usage examples for common tasks - Command-line options reference +- Configuration of the `.reviewmark.yaml` definition file +- End-to-end typical workflow for a regulated repository - Practical examples for various scenarios +Prerequisites: a supported .NET runtime (8, 9, or 10) and access to a review evidence store (URL or +file share), or `type: none` for initial setup. + +## References + +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) +- [Continuous Compliance](https://github.com/demaconsulting/ContinuousCompliance) + # Continuous Compliance -This tool follows the [Continuous Compliance][continuous-compliance] methodology, which ensures -compliance evidence is generated automatically on every CI run. +This tool follows the Continuous Compliance methodology, which ensures compliance evidence is +generated automatically on every CI run. ## Key Practices -- **Requirements Traceability**: Every requirement is linked to passing tests, and a trace matrix is - auto-generated on each release +- **Requirements Traceability**: Every requirement is linked to passing tests, and a trace matrix + is auto-generated on each release - **Linting Enforcement**: markdownlint, cspell, and yamllint are enforced before any build proceeds - **Automated Audit Documentation**: Each release ships with generated requirements, justifications, trace matrix, and quality reports @@ -57,8 +70,8 @@ reviewmark --help ## Self-Validation -Self-validation produces a report demonstrating that ReviewMark is functioning -correctly. This is useful in regulated industries where tool validation evidence is required. +Self-validation produces a report demonstrating that ReviewMark is functioning correctly. This is +useful in regulated industries where tool validation evidence is required. ### Running Validation @@ -74,13 +87,13 @@ To save validation results to a file: reviewmark --validate --results results.trx ``` -The results file format is determined by the file extension: `.trx` for TRX (MSTest) format, -or `.xml` for JUnit format. +The results file format is determined by the file extension: `.trx` for TRX (MSTest) format, or +`.xml` for JUnit format. ### Validation Report -The validation report contains the tool version, machine name, operating system version, -.NET runtime version, timestamp, and test results. +The validation report contains the tool version, machine name, operating system version, .NET +runtime version, timestamp, and test results. Example validation report: @@ -119,16 +132,17 @@ Each test proves specific functionality works correctly: - **`ReviewMark_ReviewPlanGeneration`** - `--definition` + `--plan` generates a review plan. - **`ReviewMark_ReviewReportGeneration`** - `--definition` + `--report` generates a review report. - **`ReviewMark_IndexScan`** - `--index` scans PDF evidence files and writes `index.json`. -- **`ReviewMark_WorkingDirectoryOverride`** - `--dir` overrides the working directory for file operations. +- **`ReviewMark_WorkingDirectoryOverride`** - `--dir` overrides the working directory for file + operations. - **`ReviewMark_Enforce`** - `--enforce` exits with non-zero code when reviews have issues. - **`ReviewMark_Elaborate`** - `--elaborate` prints a Markdown elaboration of a review set. - **`ReviewMark_Lint`** - `--lint` validates a definition file and reports issues. ## Lint Definition File -The `--lint` command validates the definition file (`.reviewmark.yaml`) and reports all -structural and semantic issues in a single pass. Unlike running the full tool, `--lint` never -queries the evidence store — it only checks the definition file itself. +The `--lint` command validates the definition file (`.reviewmark.yaml`) and reports all structural +and semantic issues in a single pass. Unlike running the full tool, `--lint` never queries the +evidence store — it only checks the definition file itself. A successful lint exits with code 0; any issue causes a non-zero exit code. @@ -162,19 +176,18 @@ All detected issues are reported together so you can fix multiple problems in on ### Lint Verbosity -When linting, the application banner and any summary messages are suppressed. Only the -actual issue messages are printed, making the output suitable for direct integration -with linting scripts and CI pipelines: +When linting, the application banner and any summary messages are suppressed. Only the actual issue +messages are printed, making the output suitable for direct integration with linting scripts and CI +pipelines: -- **Success (exit code 0)** — no output is produced. Silence means the definition file - is valid. -- **Failure (exit code 1)** — only the issue messages are printed, with no surrounding - banner or summary text. +- **Success (exit code 0)** — no output is produced. Silence means the definition file is valid. +- **Failure (exit code 1)** — only the issue messages are printed, with no surrounding banner or + summary text. ### Lint Error Messages -Lint errors follow the standard `[location]: [severity]: [issue]` format. For YAML syntax -errors the location includes the line and column number: +Lint errors follow the standard `[location]: [severity]: [issue]` format. For YAML syntax errors +the location includes the line and column number: ```text definition.yaml:3:5: error: (yaml parse details) @@ -239,9 +252,9 @@ The following command-line options are supported: ## Unknown and Invalid Arguments -If an unrecognized or malformed argument is supplied, ReviewMark writes a descriptive -error message to stderr in the format `Error: {message}` and exits with code 1. No -output is produced to stdout. For example: +If an unrecognized or malformed argument is supplied, ReviewMark writes a descriptive error message +to stderr in the format `Error: {message}` and exits with code 1. No output is produced to stdout. +For example: ```bash reviewmark --unknown-flag @@ -251,20 +264,19 @@ reviewmark --unknown-flag ## Working Directory (`--dir`) -`--dir` sets the root directory used for operations that do not have an explicit path -provided by another argument. Specifically it affects: +`--dir` sets the root directory used for operations that do not have an explicit path provided by +another argument. Specifically it affects: -- **Default definition file** — when `--definition` is omitted, `.reviewmark.yaml` is - resolved relative to `--dir` (or the current directory if `--dir` is also omitted). -- **Glob scanning** — `--index` glob patterns are rooted at `--dir`, and `index.json` - is written there. -- **File scanning** — when generating a plan or report, source files are enumerated - relative to `--dir`. +- **Default definition file** — when `--definition` is omitted, `.reviewmark.yaml` is resolved + relative to `--dir` (or the current directory if `--dir` is also omitted). +- **Glob scanning** — `--index` glob patterns are rooted at `--dir`, and `index.json` is written + there. +- **File scanning** — when generating a plan or report, source files are enumerated relative to + `--dir`. -Paths that the caller explicitly supplies via `--definition`, `--plan`, or `--report` -are used exactly as provided and are **not** re-rooted under `--dir`. This keeps each -argument independent: specifying one argument's path cannot silently change another -argument's path. +Paths that the caller explicitly supplies via `--definition`, `--plan`, or `--report` are used +exactly as provided and are **not** re-rooted under `--dir`. This keeps each argument independent: +specifying one argument's path cannot silently change another argument's path. # Configuration @@ -337,10 +349,10 @@ The same `!`-prefix syntax applies to the top-level `needs-review` list. ### Fingerprinting -ReviewMark computes a cryptographic fingerprint for each review set from the hashes of all -matched files. The fingerprint changes whenever files are **added, removed, or modified**, but is -stable across renames or moves that keep the same set of file contents, so those do not -invalidate a review. +ReviewMark computes a cryptographic fingerprint for each review set from the hashes of all matched +files. The fingerprint changes whenever files are **added, removed, or modified**, but is stable +across renames or moves that keep the same set of file contents, so those do not invalidate a +review. When a reviewer creates evidence, they record the current fingerprint in the PDF Keywords field. ReviewMark matches that recorded fingerprint against the current fingerprint to determine whether @@ -352,9 +364,9 @@ Good review sets share these properties: - **Cohesive** — group implementation files with their corresponding tests and any documentation they are paired with (e.g. a module's `.md` file alongside its `.cs` files). -- **Stable `id`** — choose an identifier that will not need to change as the code evolves, such - as `authentication-module` or `payment-api`. The `id` is embedded in every evidence PDF, so - renaming it breaks the evidence chain. +- **Stable `id`** — choose an identifier that will not need to change as the code evolves, such as + `authentication-module` or `payment-api`. The `id` is embedded in every evidence PDF, so renaming + it breaks the evidence chain. - **Right-sized** — a set that is too large is difficult to review in a single sitting; a set that is too small creates an unmanageable number of review artifacts. - **Exclude generated files** — use `!` patterns to omit `obj/`, `bin/`, and other build outputs @@ -377,17 +389,17 @@ reviews: ## Evidence Source The `evidence-source` block configures how ReviewMark obtains review evidence. For `url` and -`fileshare` sources it points to `index.json` — the catalogue of completed review PDFs. The -`none` source skips loading any index and always returns empty evidence (useful during initial -project setup before an evidence store is provisioned). +`fileshare` sources it points to `index.json` — the catalogue of completed review PDFs. The `none` +source skips loading any index and always returns empty evidence (useful during initial project +setup before an evidence store is provisioned). ### Source Types -| Type | Description | -| :----------- | :----------------------------------------------------------------------------------- | -| `none` | No evidence source; always returns an empty index (for initial project setup) | -| `fileshare` | Full UNC or local file-system path to `index.json` | -| `url` | Full HTTP or HTTPS URL to `index.json` | +| Type | Description | +| :---------- | :---------------------------------------------------------------------------------- | +| `none` | No evidence source; always returns an empty index (for initial project setup) | +| `fileshare` | Full UNC or local file-system path to `index.json` | +| `url` | Full HTTP or HTTPS URL to `index.json` | #### None @@ -406,6 +418,10 @@ evidence-source: location: \\reviews.example.com\evidence\index.json ``` +> **Note**: The `\\server\share\` syntax above is Windows UNC notation. On Linux and macOS, mount +> the network share first (for example via NFS or CIFS) and reference the mounted path instead, +> e.g. `location: /mnt/reviews/evidence/index.json`. + #### URL ```yaml @@ -416,8 +432,8 @@ evidence-source: ### Credentials -For authenticated URL sources, supply credentials through environment variables so that secrets -are never stored in the definition file or source control: +For authenticated URL sources, supply credentials through environment variables so that secrets are +never stored in the definition file or source control: ```yaml evidence-source: @@ -478,13 +494,14 @@ any review set. reviewmark --plan docs/review/review-plan.md ``` -The plan is checked into the repository alongside the source code so that reviewers have a structured -starting point. +The plan is checked into the repository alongside the source code so that reviewers have a +structured starting point. ## Step 3 — Elaborate the Review Set Before beginning a review, use `--elaborate` to obtain the precise fingerprint and the full sorted -list of files for a specific review set. This information is required when creating the evidence PDF: +list of files for a specific review set. This information is required when creating the evidence +PDF: ```bash reviewmark --elaborate Core-Logic @@ -523,8 +540,8 @@ id=core-module fingerprint=a3f9c2d1... date=2026-03-15 result=pass ``` All four fields are **required** — a PDF without any one of them will be skipped with a warning -when the evidence store is scanned. The PDF is deposited in the evidence store folder. -ReviewMark never dictates file names — the reviewer uses whatever name the QMS requires. +when the evidence store is scanned. The PDF is deposited in the evidence store folder. ReviewMark +never dictates file names — the reviewer uses whatever name the QMS requires. ## Step 5 — Update the Evidence Index @@ -541,10 +558,14 @@ The `--index` flag may be repeated to cover evidence organized across multiple s reviewmark --dir \\reviews.example.com\evidence\ --index "2025/**/*.pdf" --index "2026/**/*.pdf" ``` +> **Note**: The `\\server\share\` path syntax above is Windows UNC notation. On Linux and macOS, +> mount the network share first (for example via NFS or CIFS) and use the resulting mount point +> instead, e.g. `--dir /mnt/reviews/evidence/`. + ## Step 6 — Generate the Review Report Run ReviewMark with both `--plan` and `--report` to produce the Review Report alongside the plan. -The report shows the status of each review set — Current, Stale, Failed, or Missing — and lists +The report shows the status of each review set — Current, Stale, Missing, or Failed — and lists the referenced evidence documents. ```bash @@ -679,6 +700,3 @@ reviewmark --validate --results validation-results.trx ```bash reviewmark --silent --log tool-output.log ``` - - -[continuous-compliance]: https://github.com/demaconsulting/ContinuousCompliance diff --git a/docs/user_guide/title.txt b/docs/user_guide/title.txt index c22668a..b4e71c4 100644 --- a/docs/user_guide/title.txt +++ b/docs/user_guide/title.txt @@ -1,11 +1,12 @@ --- -title: ReviewMark User Guide -subtitle: File-Review Evidence Management for Regulated Environments -author: DEMA Consulting -description: File-Review Evidence Management for Regulated Environments +title: "ReviewMark User Guide" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +description: "User Guide for ReviewMark" lang: en-US keywords: - ReviewMark + - User Guide - .NET - Command-Line Tool - File Review diff --git a/docs/verification/definition.yaml b/docs/verification/definition.yaml index 798e890..7b4f713 100644 --- a/docs/verification/definition.yaml +++ b/docs/verification/definition.yaml @@ -26,8 +26,11 @@ input-files: - docs/verification/review-mark/self-test/validation.md - docs/verification/ots.md - docs/verification/ots/buildmark.md + - docs/verification/ots/dema-consulting-test-results.md - docs/verification/ots/fileassert.md + - docs/verification/ots/microsoft-extensions-file-system-globbing.md - docs/verification/ots/pandoc.md + - docs/verification/ots/pdfsharp.md - docs/verification/ots/reqstream.md - docs/verification/ots/reviewmark.md - docs/verification/ots/sarifmark.md @@ -35,6 +38,7 @@ input-files: - docs/verification/ots/versionmark.md - docs/verification/ots/weasyprint.md - docs/verification/ots/xunit.md + - docs/verification/ots/yamldotnet.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md index 2d540af..5537ca9 100644 --- a/docs/verification/introduction.md +++ b/docs/verification/introduction.md @@ -1,52 +1,62 @@ # Introduction -This document provides the verification design for ReviewMark, a .NET command-line tool -for automated file-review evidence management in regulated environments. +This document defines how the ReviewMark verification collection is organized and how the +repository records verification coverage for the ReviewMark system, its local software items, +and its off-the-shelf software items. ## Purpose -The purpose of this document is to describe how each software requirement for ReviewMark -is verified. For each unit, subsystem, and OTS component it identifies the test class, -test methods, mock or stub dependencies, and the requirement identifiers that each test -satisfies. The document provides a traceable record of verification coverage that -supports formal code review, compliance audit, and ongoing maintenance. +This document provides the verification design context for ReviewMark, a .NET command-line tool +for automated file-review evidence management in regulated environments. It tells reviewers which +software items are covered by this collection, how verification artifacts align with requirements, +design, source, and tests, and where OTS qualification evidence is recorded so coverage can be +confirmed without reverse-engineering the test code. ## Scope -This document covers the verification design for the complete ReviewMark system, -including all in-house subsystems and units and all Off-The-Shelf (OTS) components. +This collection covers verification documentation for the following software items. -In-house software items verified in this document: +Local items: -- **Program** - entry point and execution orchestrator -- **Cli** subsystem - `Context` unit (command-line argument parser and I/O owner) -- **Configuration** subsystem - `ReviewMarkConfiguration` and `GlobMatcher` units -- **Indexing** subsystem - `ReviewIndex` and `PathHelpers` units -- **SelfTest** subsystem - `Validation` unit +- **ReviewMark** - system-level verification +- **Program** - unit verification +- **Cli** subsystem and **Context** unit +- **Configuration** subsystem with **ReviewMarkConfiguration** and **GlobMatcher** units +- **Indexing** subsystem with **ReviewIndex** and **PathHelpers** units +- **SelfTest** subsystem with **Validation** unit -OTS components verified in this document: +OTS items: -- **BuildMark** - build notes generation tool -- **FileAssert** - file content assertion tool -- **Pandoc** - document conversion tool -- **ReqStream** - requirements traceability tool -- **ReviewMark** - code review enforcement tool (self-referential) -- **SarifMark** - SARIF report generation tool -- **SonarMark** - SonarCloud report generation tool -- **VersionMark** - tool version capture tool -- **WeasyPrint** - HTML-to-PDF renderer -- **xUnit** - unit testing framework +- **BuildMark** +- **DemaConsulting.TestResults** +- **FileAssert** +- **Microsoft.Extensions.FileSystemGlobbing** +- **Pandoc** +- **PDFsharp** +- **ReqStream** +- **ReviewMark** +- **SarifMark** +- **SonarMark** +- **VersionMark** +- **WeasyPrint** +- **xUnit** +- **YamlDotNet** -The following topics are out of scope: +Shared packages: -- External library internals not listed above -- Build pipeline configuration beyond the steps referenced as evidence -- Deployment and packaging +- N/A - ReviewMark does not consume shared packages from other repositories. + +Out of scope: + +- Test projects as standalone software items +- Build pipeline implementation details except where CI outputs are cited as verification evidence +- Deployment and packaging details +- Internals of third-party components beyond the integration surfaces used by ReviewMark ## Software Structure -The following tree shows how the ReviewMark software items are organized across the -system, subsystem, and unit levels: +The following tree shows how the ReviewMark software items are organized across the system, +subsystem, and unit levels: ```text ReviewMark (System) @@ -61,87 +71,99 @@ ReviewMark (System) │ └── PathHelpers (Unit) └── SelfTest (Subsystem) └── Validation (Unit) + +OTS Software Items (integration and qualification evidence in docs/design/ots/ and +docs/verification/ots/): +├── BuildMark +├── DemaConsulting.TestResults +├── FileAssert +├── Microsoft.Extensions.FileSystemGlobbing +├── Pandoc +├── PDFsharp +├── ReqStream +├── ReviewMark +├── SarifMark +├── SonarMark +├── VersionMark +├── WeasyPrint +├── xUnit +└── YamlDotNet ``` ## Companion Artifact Structure -The list below shows how each artifact type maps to the same software structure, -using per-item path patterns: +Local items have parallel artifacts in the following repository locations: -- **System** — Req: `docs/reqstream/review-mark.yaml`, - Design: `docs/design/review-mark.md`, - Verification: `docs/verification/review-mark.md`, - Tests: `test/.../IntegrationTests.cs` -- **Program** — Req: `docs/reqstream/review-mark/program.yaml`, +- **System** - Req: `docs/reqstream/review-mark.yaml`, Design: `docs/design/review-mark.md`, + Verification: `docs/verification/review-mark.md`, Tests: + `test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs` +- **Program** - Req: `docs/reqstream/review-mark/program.yaml`, Design: `docs/design/review-mark/program.md`, - Verification: `docs/verification/review-mark/program.md`, - Source: `src/.../Program.cs`, Tests: `test/.../ProgramTests.cs` -- **Cli subsystem** — Req: `docs/reqstream/review-mark/cli/cli.yaml`, + Verification: `docs/verification/review-mark/program.md`, Source: + `src/DemaConsulting.ReviewMark/Program.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/ProgramTests.cs` +- **Cli subsystem** - Req: `docs/reqstream/review-mark/cli.yaml`, Design: `docs/design/review-mark/cli.md`, - Verification: `docs/verification/review-mark/cli.md`, - Source: `src/.../Cli/` -- **Context** — Req: `docs/reqstream/review-mark/cli/context.yaml`, + Verification: `docs/verification/review-mark/cli.md`, Source: + `src/DemaConsulting.ReviewMark/Cli/` +- **Context** - Req: `docs/reqstream/review-mark/cli/context.yaml`, Design: `docs/design/review-mark/cli/context.md`, - Verification: `docs/verification/review-mark/cli/context.md`, - Source: `src/.../Cli/Context.cs`, Tests: `test/.../ContextTests.cs` -- **Configuration subsystem** — - Req: `docs/reqstream/review-mark/configuration/configuration.yaml`, + Verification: `docs/verification/review-mark/cli/context.md`, Source: + `src/DemaConsulting.ReviewMark/Cli/Context.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/ContextTests.cs` +- **Configuration subsystem** - Req: `docs/reqstream/review-mark/configuration.yaml`, Design: `docs/design/review-mark/configuration.md`, - Verification: `docs/verification/review-mark/configuration.md`, - Source: `src/.../Configuration/` -- **ReviewMarkConfiguration** — + Verification: `docs/verification/review-mark/configuration.md`, Source: + `src/DemaConsulting.ReviewMark/Configuration/` +- **ReviewMarkConfiguration** - Req: `docs/reqstream/review-mark/configuration/review-mark-configuration.yaml`, Design: `docs/design/review-mark/configuration/review-mark-configuration.md`, Verification: `docs/verification/review-mark/configuration/review-mark-configuration.md`, - Source: `src/.../Configuration/ReviewMarkConfiguration.cs`, - Tests: `test/.../ReviewMarkConfigurationTests.cs` -- **GlobMatcher** — Req: `docs/reqstream/review-mark/configuration/glob-matcher.yaml`, + Source: `src/DemaConsulting.ReviewMark/Configuration/ReviewMarkConfiguration.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/Configuration/ReviewMarkConfigurationTests.cs` +- **GlobMatcher** - Req: `docs/reqstream/review-mark/configuration/glob-matcher.yaml`, Design: `docs/design/review-mark/configuration/glob-matcher.md`, - Verification: `docs/verification/review-mark/configuration/glob-matcher.md`, - Source: `src/.../Configuration/GlobMatcher.cs`, - Tests: `test/.../GlobMatcherTests.cs` -- **Indexing subsystem** — Req: `docs/reqstream/review-mark/indexing/indexing.yaml`, + Verification: `docs/verification/review-mark/configuration/glob-matcher.md`, Source: + `src/DemaConsulting.ReviewMark/Configuration/GlobMatcher.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/Configuration/GlobMatcherTests.cs` +- **Indexing subsystem** - Req: `docs/reqstream/review-mark/indexing.yaml`, Design: `docs/design/review-mark/indexing.md`, - Verification: `docs/verification/review-mark/indexing.md`, - Source: `src/.../Indexing/` -- **ReviewIndex** — Req: `docs/reqstream/review-mark/indexing/review-index.yaml`, + Verification: `docs/verification/review-mark/indexing.md`, Source: + `src/DemaConsulting.ReviewMark/Indexing/` +- **ReviewIndex** - Req: `docs/reqstream/review-mark/indexing/review-index.yaml`, Design: `docs/design/review-mark/indexing/review-index.md`, - Verification: `docs/verification/review-mark/indexing/review-index.md`, - Source: `src/.../Indexing/ReviewIndex.cs`, Tests: `test/.../IndexingTests.cs` -- **PathHelpers** — Req: `docs/reqstream/review-mark/indexing/path-helpers.yaml`, + Verification: `docs/verification/review-mark/indexing/review-index.md`, Source: + `src/DemaConsulting.ReviewMark/Indexing/ReviewIndex.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/IndexingTests.cs` +- **PathHelpers** - Req: `docs/reqstream/review-mark/indexing/path-helpers.yaml`, Design: `docs/design/review-mark/indexing/path-helpers.md`, - Verification: `docs/verification/review-mark/indexing/path-helpers.md`, - Source: `src/.../Indexing/PathHelpers.cs`, Tests: `test/.../IndexingTests.cs` -- **SelfTest subsystem** — Req: `docs/reqstream/review-mark/self-test/self-test.yaml`, + Verification: `docs/verification/review-mark/indexing/path-helpers.md`, Source: + `src/DemaConsulting.ReviewMark/Indexing/PathHelpers.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/IndexingTests.cs` +- **SelfTest subsystem** - Req: `docs/reqstream/review-mark/self-test.yaml`, Design: `docs/design/review-mark/self-test.md`, - Verification: `docs/verification/review-mark/self-test.md`, - Source: `src/.../SelfTest/` -- **Validation** — Req: `docs/reqstream/review-mark/self-test/validation.yaml`, + Verification: `docs/verification/review-mark/self-test.md`, Source: + `src/DemaConsulting.ReviewMark/SelfTest/` +- **Validation** - Req: `docs/reqstream/review-mark/self-test/validation.yaml`, Design: `docs/design/review-mark/self-test/validation.md`, - Verification: `docs/verification/review-mark/self-test/validation.md`, - Source: `src/.../SelfTest/Validation.cs`, Tests: `test/.../ValidationTests.cs` - -OTS components verified in this document have their requirements at: - -| OTS Component | Requirements | -| ------------- | ------------ | -| ReviewMark (self-referential) | `docs/reqstream/ots/reviewmark.yaml` | -| BuildMark | `docs/reqstream/ots/buildmark.yaml` | -| FileAssert | `docs/reqstream/ots/fileassert.yaml` | -| Pandoc | `docs/reqstream/ots/pandoc.yaml` | -| ReqStream | `docs/reqstream/ots/reqstream.yaml` | -| SarifMark | `docs/reqstream/ots/sarifmark.yaml` | -| SonarMark | `docs/reqstream/ots/sonarmark.yaml` | -| VersionMark | `docs/reqstream/ots/versionmark.yaml` | -| WeasyPrint | `docs/reqstream/ots/weasyprint.yaml` | -| xUnit | `docs/reqstream/ots/xunit.yaml` | - -Each chapter in this verification document corresponds to a unit or subsystem chapter -in the design document. Requirement IDs referenced in the Requirements Coverage sections -match identifiers defined in the ReqStream YAML files under `docs/reqstream/`. + Verification: `docs/verification/review-mark/self-test/validation.md`, Source: + `src/DemaConsulting.ReviewMark/SelfTest/Validation.cs`, Tests: + `test/DemaConsulting.ReviewMark.Tests/ValidationTests.cs` + +OTS items use repository-level integration and qualification artifacts parallel to the system +folders: + +- Requirements: `docs/reqstream/ots/{ots-name}.yaml` +- Design: `docs/design/ots/{ots-name}.md` +- Verification: `docs/verification/ots/{ots-name}.md` + +Shared packages: + +- N/A - no shared package verification artifacts are required for this repository. + +Review-sets are defined in `.reviewmark.yaml`. ## References -- See the *ReviewMark Software Design* document for implementation details of each unit. -- See the *ReviewMark Requirements* document for the full requirements specification. -- See the ReviewMark repository at . +- [Continuous Compliance](https://github.com/demaconsulting/ContinuousCompliance) +- [ReviewMark releases](https://github.com/demaconsulting/ReviewMark/releases) diff --git a/docs/verification/ots.md b/docs/verification/ots.md index 6ad176b..a1dee84 100644 --- a/docs/verification/ots.md +++ b/docs/verification/ots.md @@ -1,16 +1,52 @@ -# Off-The-Shelf Components - -This section documents the verification strategy applied to each off-the-shelf (OTS) -component used by ReviewMark. For each OTS component, acceptance is based on one of -the following approaches: - -- **Automated test coverage** — unit or integration tests exercise the component's - integration surface and confirm the expected behaviour. -- **Established industry use** — the component is a widely adopted, actively maintained - open-source project with its own test suite and release process. -- **Vendor assurance** — the component is supplied and maintained by the tool vendor - with published quality practices. - -The subsections below address each component individually. Component version constraints -are defined in the relevant project files and the requirements YAML in -`docs/reqstream/ots/`. +# OTS Verification + +This section describes the overall qualification strategy for off-the-shelf software used by +ReviewMark. The per-item files under `docs/verification/ots/` provide the detailed verification +approach and requirements coverage for each individual OTS software item. + +## Verification Strategy + +ReviewMark uses three complementary approaches to verify OTS software items. + +- Runtime libraries used directly by the product code, including YamlDotNet, PDFsharp, + DemaConsulting.TestResults, and Microsoft.Extensions.FileSystemGlobbing, are verified through + repository unit and integration tests that exercise the exact parsing, metadata, + serialization, and globbing behaviors ReviewMark depends on. +- xUnit is qualified through successful execution of the ReviewMark test suites and their TRX + outputs because the repository depends on xUnit for test discovery, execution, and reporting. +- Tooling OTS items such as BuildMark, ReqStream, ReviewMark, SarifMark, SonarMark, + VersionMark, Pandoc, WeasyPrint, and FileAssert are verified through a combination of + self-validation (`--validate`) and output assertions in the GitHub Actions workflow defined in + `.github/workflows/build.yaml`. + +The detailed evidence source and requirement mapping for each component are recorded in the +companion files under `docs/verification/ots/`. + +## Qualification Evidence + +Qualification evidence is collected automatically by `.github/workflows/build.yaml` and published +as CI artifacts. Primary evidence includes: + +- Self-validation TRX files such as `artifacts/buildmark-self-validation.trx`, + `artifacts/versionmark-self-validation.trx`, `artifacts/reviewmark-self-validation.trx`, + `artifacts/sarifmark-self-validation.trx`, `artifacts/sonarmark-self-validation.trx`, and + `artifacts/reqstream-self-validation.trx` +- `dotnet test` TRX output from the repository test projects, which provides evidence for xUnit + and for runtime libraries exercised by ReviewMark unit and integration tests +- FileAssert TRX outputs such as `artifacts/fileassert-build-notes.trx`, + `artifacts/fileassert-code-quality.trx`, `artifacts/fileassert-code-review.trx`, + `artifacts/fileassert-design.trx`, `artifacts/fileassert-verification.trx`, + `artifacts/fileassert-user-guide.trx`, `artifacts/fileassert-self-validation.trx`, and + `artifacts/fileassert-requirements.trx`, which confirm Pandoc and WeasyPrint produced the + expected documents and metadata +- VersionMark capture artifacts that record the exact OTS tool versions used during the build and + support upgrade impact assessment + +## Regression Approach + +When an OTS version changes, ReviewMark re-qualifies the affected component by re-running the full +repository build and CI matrix, reviewing the vendor release notes for the changed package or +tool, and updating the affected requirements, design, and verification artifacts if the consumed +behavior changes. An upgrade is not accepted until the refreshed automated evidence remains green, +including `dotnet test`, the ReviewMark integration-test matrix, all applicable self-validation +steps, and the FileAssert checks that cover generated document outputs. diff --git a/docs/verification/ots/buildmark.md b/docs/verification/ots/buildmark.md index be0feac..df4ba30 100644 --- a/docs/verification/ots/buildmark.md +++ b/docs/verification/ots/buildmark.md @@ -1,20 +1,35 @@ ## BuildMark -**Component**: DemaConsulting.BuildMark -**Role**: Provides the `buildmark` CLI tool used in the build pipeline. -**Acceptance approach**: Established industry use and automated build pipeline verification. +### Verification Approach -BuildMark is maintained by DemaConsulting and is used as a build tool in the CI/CD -pipeline. Its integration is verified through the GitHub Actions workflow (`build.yaml`), -where the "Run BuildMark self-validation" step and the "Generate Build Notes with BuildMark" -step run as part of the `build-docs` job. A successful CI pipeline run provides evidence -that BuildMark executed without error and produced its expected markdown output. +This repository pins DemaConsulting.BuildMark version 1.2.2 in the local tool +manifest and uses that CLI in the `build-docs` job of `build.yaml` to generate +the build-notes evidence package. Fitness for use is +verified by both self-validation and live report generation. The `Run BuildMark +self-validation` step executes `dotnet buildmark --validate` and writes +`artifacts/buildmark-self-validation.trx`. The `Generate Build Notes with +BuildMark` step then runs the same pinned tool version against the current +GitHub Actions context and writes `docs/build_notes/generated/build_notes.md`. +Because either failure stops the workflow, a passing CI run shows that BuildMark +can query the workflow metadata needed by this repository and render the +required markdown evidence. No project-specific qualification issues are +currently recorded for this pinned version. -### Test scenario coverage +### Test Scenarios -- **`BuildMark_MarkdownReportGeneration`** — BuildMark successfully queries the GitHub - Actions API and generates a markdown build-notes document from workflow run metadata. - CI Evidence: "Run BuildMark self-validation" step in the `build-docs` job of - `build.yaml`, writing results to `artifacts/buildmark-self-validation.trx`. +**BuildMarkMarkdownReportGeneration**: BuildMark queries the active GitHub +Actions run and renders the resulting metadata into +`docs/build_notes/generated/build_notes.md`, proving the tool is fit for the +repository's intended use as the build-notes generator. The expected outcome is +a generated markdown report together with a passing self-validation result in +`artifacts/buildmark-self-validation.trx`. This scenario is tested by +`BuildMark_MarkdownReportGeneration`. -**Requirement coverage**: `ReviewMark-OTS-BuildMark` +### Requirements Coverage + +- **BuildMark-Core-GenerateBuildNotes**: BuildMark shall generate build-notes + documentation from GitHub Actions metadata. + - *BuildMarkMarkdownReportGeneration*: verifies BuildMark retrieves GitHub + Actions metadata and renders the build-notes markdown report used by this + repository. + - `BuildMark_MarkdownReportGeneration` diff --git a/docs/verification/ots/dema-consulting-test-results.md b/docs/verification/ots/dema-consulting-test-results.md new file mode 100644 index 0000000..8b90374 --- /dev/null +++ b/docs/verification/ots/dema-consulting-test-results.md @@ -0,0 +1,39 @@ +## DemaConsulting.TestResults + +### Verification Approach + +ReviewMark uses DemaConsulting.TestResults 1.7.0, referenced from +`DemaConsulting.ReviewMark.csproj`, to serialize self-validation output in the SelfTest subsystem. +The integration surface is `Validation.WriteResultsFile()`, which selects +`TrxSerializer.Serialize(testResults)` for `.trx` paths and `JUnitSerializer.Serialize(testResults)` +for `.xml` paths after `Validation.Run()` has accumulated results with `CreateTestResult()` and +`FinalizeTestResult()`. Fitness for intended use is verified by dedicated OTS tests in +`test/OtsSoftwareTests/DemaConsultingTestResultsTests.cs`, self-validation integration tests in +`test/DemaConsulting.ReviewMark.Tests/SelfTest/ValidationTests.cs`, and the `dotnet test` step in +the `build` matrix job of `build.yaml`, which publishes TRX evidence to `artifacts/`. No +project-specific issues have been observed in this validated serialization path. + +### Test Scenarios + +**TestResultsTrxSerialization**: A completed self-validation run can be serialized to MSTest TRX so +CI systems and downstream compliance tooling can consume the output without custom adapters. This +scenario is tested by `TrxSerializer_Serialize_CompletedTestRun_ContainsTestRunElement` and +`Validation_Run_WithTrxResultsFile_WritesFile`. + +**TestResultsJUnitSerialization**: The same completed self-validation run can also be serialized to +JUnit XML when a `.xml` results path is requested, preserving portability across CI environments. +This scenario is tested by `JUnitSerializer_Serialize_CompletedTestRun_ContainsTestSuitesElement` +and `Validation_Run_WithXmlResultsFile_WritesFile`. + +### Requirements Coverage + +- **ReviewMark-OTS-TestResults-TrxSerialize**: DemaConsulting.TestResults shall serialize test run + results to MSTest TRX format. + - *TestResultsTrxSerialization* + - `TrxSerializer_Serialize_CompletedTestRun_ContainsTestRunElement` + - `Validation_Run_WithTrxResultsFile_WritesFile` +- **ReviewMark-OTS-TestResults-JUnitSerialize**: DemaConsulting.TestResults shall serialize test + run results to JUnit XML format. + - *TestResultsJUnitSerialization* + - `JUnitSerializer_Serialize_CompletedTestRun_ContainsTestSuitesElement` + - `Validation_Run_WithXmlResultsFile_WritesFile` diff --git a/docs/verification/ots/fileassert.md b/docs/verification/ots/fileassert.md index d87ad4c..32150d0 100644 --- a/docs/verification/ots/fileassert.md +++ b/docs/verification/ots/fileassert.md @@ -1,28 +1,44 @@ ## FileAssert -**Component**: DemaConsulting.FileAssert -**Role**: Validates that required files are present and well-formed as part of the CI build. -**Acceptance approach**: Automated build pipeline verification. +### Verification Approach -FileAssert is invoked in the GitHub Actions CI workflow (`build.yaml`) within the -`build-docs` job. After Pandoc and WeasyPrint generate each document group, a dedicated -"Assert ... Documents with FileAssert" step validates the outputs. The "Run FileAssert -self-validation" step runs `dotnet fileassert --validate` after all document groups are -generated, producing `artifacts/fileassert-self-validation.trx`. A non-zero exit from -any FileAssert step fails the build, providing evidence that FileAssert is operating -correctly. +This repository pins DemaConsulting.FileAssert version 0.3.0 in the local tool +manifest and uses that CLI as the document-output assertion gate in the +`build-docs` job of `build.yaml`. Fitness for use is verified in two +complementary ways. First, FileAssert is exercised repeatedly in the live +pipeline through the `Assert ... Documents with FileAssert` steps, which execute +the checked-in assertions from `.fileassert.yaml` against generated HTML and PDF +artifacts for the build notes, code quality, code review, design, verification, +user guide, and requirements document sets. Second, the `Run FileAssert +self-validation` step executes `dotnet fileassert --validate` and writes +`artifacts/fileassert-self-validation.trx`. Because any assertion failure or +self-validation failure stops the workflow, a passing CI run shows that the +pinned tool version is fit for the repository's intended use as an automated +artifact validator. No project-specific qualification issues are currently +recorded for this pinned version. -### Test scenario coverage +### Test Scenarios -- **`FileAssert_VersionDisplay`** — FileAssert's self-validation confirms the tool can - display its version, proving it is correctly installed and operationally available. - CI Evidence: "Run FileAssert self-validation" step in the `build-docs` job of - `build.yaml`, writing results to `artifacts/fileassert-self-validation.trx`. -- **`FileAssert_HelpDisplay`** — FileAssert's self-validation confirms the tool can - display its help text, proving the CLI interface is correctly wired. - CI Evidence: Same "Run FileAssert self-validation" step, same TRX file. +**FileAssertVersionDisplay**: FileAssert reports its version during +self-validation, proving the pinned tool is installed correctly and can be +invoked by the build pipeline before it is used to gate generated documents. The +expected outcome is a passing self-validation result in +`artifacts/fileassert-self-validation.trx`. This scenario is tested by +`FileAssert_VersionDisplay`. -Both scenarios together confirm `ReviewMark-OTS-FileAssert`: FileAssert is present, -operational, and able to perform its assertion role in the pipeline. +**FileAssertHelpDisplay**: FileAssert reports its help text during +self-validation, proving the CLI surface expected by the workflow is available +and operational. The expected outcome is a passing self-validation result in +`artifacts/fileassert-self-validation.trx`. This scenario is tested by +`FileAssert_HelpDisplay`. -**Requirement coverage**: `ReviewMark-OTS-FileAssert` +### Requirements Coverage + +- **ReviewMark-OTS-FileAssert**: FileAssert shall confirm operational + availability by successfully completing self-validation. + - *FileAssertVersionDisplay*: verifies FileAssert is installed correctly and + can execute its version-display self-check. + - `FileAssert_VersionDisplay` + - *FileAssertHelpDisplay*: verifies FileAssert exposes the expected CLI help + surface during self-validation. + - `FileAssert_HelpDisplay` diff --git a/docs/verification/ots/microsoft-extensions-file-system-globbing.md b/docs/verification/ots/microsoft-extensions-file-system-globbing.md new file mode 100644 index 0000000..58e44e1 --- /dev/null +++ b/docs/verification/ots/microsoft-extensions-file-system-globbing.md @@ -0,0 +1,57 @@ +## Microsoft.Extensions.FileSystemGlobbing + +### Verification Approach + +ReviewMark uses Microsoft.Extensions.FileSystemGlobbing 10.0.8, referenced from +`DemaConsulting.ReviewMark.csproj`, to resolve ordered include and exclude patterns from +`.reviewmark.yaml`. The integration surface is `Matcher.AddInclude`, `Matcher.AddExclude`, and +`Matcher.GetResultsInFullPath`, exercised through `GlobMatcher.GetMatchingFiles()`, which applies +patterns one at a time so later includes can re-add files removed by earlier excludes and then +normalizes returned paths to forward slashes. Fitness for intended use is verified by dedicated OTS +tests in `test/OtsSoftwareTests/MicrosoftExtensionsFileSystemGlobbingTests.cs`, companion globbing +tests in `test/DemaConsulting.ReviewMark.Tests/Configuration/GlobMatcherTests.cs`, and the +`dotnet test` step in the `build` matrix job of `build.yaml`, which publishes TRX evidence to +`artifacts/`. No project-specific issues have been observed in this validated matching surface. + +### Test Scenarios + +**FileSystemGlobbingWildcardMatching**: Include patterns correctly resolve files in the root +directory and nested directories, support `**` traversal, combine multiple include patterns, and +return normalized relative paths suitable for deterministic review-set processing. This scenario is +tested by `Matcher_GetResultsInFullPath_DoubleWildcard_MatchesFilesInSubdirectories`, +`Matcher_GetResultsInFullPath_SingleWildcard_MatchesFilesInDirectory`, +`GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles`, +`GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching`, +`GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles`, +`GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles`, +`GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList`, and +`GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator`. + +**FileSystemGlobbingExclusionPrefix**: Exclusion patterns remove unwanted matches from the active +file set while preserving ReviewMark's ordered semantics, including the ability to re-include a +specific path later in the pattern list. This scenario is tested by +`Matcher_GetResultsInFullPath_ExcludePattern_OmitsMatchingFiles`, +`GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles`, +`GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles`, and +`GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles`. + +### Requirements Coverage + +- **ReviewMark-OTS-FileSystemGlobbing-WildcardMatching**: + Microsoft.Extensions.FileSystemGlobbing shall match files using `**` double-wildcard patterns. + - *FileSystemGlobbingWildcardMatching* + - `Matcher_GetResultsInFullPath_DoubleWildcard_MatchesFilesInSubdirectories` + - `Matcher_GetResultsInFullPath_SingleWildcard_MatchesFilesInDirectory` + - `GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles` + - `GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching` + - `GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles` + - `GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles` + - `GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList` + - `GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator` +- **ReviewMark-OTS-FileSystemGlobbing-ExclusionPrefix**: + Microsoft.Extensions.FileSystemGlobbing shall exclude files matching a `!`-prefixed pattern. + - *FileSystemGlobbingExclusionPrefix* + - `Matcher_GetResultsInFullPath_ExcludePattern_OmitsMatchingFiles` + - `GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles` + - `GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles` + - `GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles` diff --git a/docs/verification/ots/pandoc.md b/docs/verification/ots/pandoc.md index 257308d..ac6f41a 100644 --- a/docs/verification/ots/pandoc.md +++ b/docs/verification/ots/pandoc.md @@ -1,52 +1,88 @@ ## Pandoc -**Component**: Pandoc () -**Role**: Converts Markdown source documents into valid HTML as part of the documentation -build pipeline. WeasyPrint subsequently renders the HTML to PDF. -**Acceptance approach**: Automated test coverage. - -Pandoc is a widely adopted open-source universal document converter with over a decade -of active development, extensive automated testing, and broad production usage. - -ReviewMark does not embed Pandoc; it is an external build dependency. Correct Pandoc -behaviour is confirmed by FileAssert integration tests in the GitHub Actions CI workflow -(`build.yaml`), which run within the `build-docs` job. Each document group has a -dedicated Pandoc HTML generation step followed by a FileAssert validation step that -asserts the HTML file exists, contains a valid `` element, and includes expected -content strings. - -### Test scenario coverage - -- **`Pandoc_BuildNotesHtml`** — Pandoc generated - `docs/build_notes/generated/build_notes.html` with a valid title element and - "Build Notes" content. CI Evidence: "Assert Build Notes Documents with FileAssert" - step → `artifacts/fileassert-build-notes.trx`. -- **`Pandoc_CodeQualityHtml`** — Pandoc generated - `docs/code_quality/generated/quality.html` with a valid title element and - "CodeQL" content. CI Evidence: "Assert Code Quality Documents with FileAssert" - step → `artifacts/fileassert-code-quality.trx`. -- **`Pandoc_ReviewPlanHtml`** — Pandoc generated - `docs/code_review_plan/generated/plan.html` with a valid title element and - "Review Plan" content. CI Evidence: "Assert Code Review Documents with FileAssert" - step → `artifacts/fileassert-code-review.trx`. -- **`Pandoc_ReviewReportHtml`** — Pandoc generated - `docs/code_review_report/generated/report.html` with a valid title element and - "Review Report" content. CI Evidence: "Assert Code Review Documents with FileAssert" - step → `artifacts/fileassert-code-review.trx`. -- **`Pandoc_DesignHtml`** — Pandoc generated - `docs/design/generated/design.html` with a valid title element and "Design" - content. CI Evidence: "Assert Design Documents with FileAssert" - step → `artifacts/fileassert-design.trx`. -- **`Pandoc_VerificationHtml`** — Pandoc generated - `docs/verification/generated/verification.html` with a valid title element and - "Verification" content. CI Evidence: "Assert Verification Documents with FileAssert" - step → `artifacts/fileassert-verification.trx`. -- **`Pandoc_UserGuideHtml`** — Pandoc generated - `docs/user_guide/generated/user_guide.html` with a valid title element and - "User Guide" content. CI Evidence: "Assert User Guide Documents with FileAssert" - step → `artifacts/fileassert-user-guide.trx`. - -Each scenario directly satisfies `ReviewMark-OTS-Pandoc` by providing FileAssert-verified -evidence that Pandoc converted Markdown to well-formed HTML containing expected content. - -**Requirement coverage**: `ReviewMark-OTS-Pandoc` +### Verification Approach + +This repository pins DemaConsulting.PandocTool version 3.9.0.2 in the local +tool manifest, which exposes the `pandoc` command used throughout `build.yaml` +to convert repository markdown collections into HTML before +WeasyPrint renders the final PDFs. Pandoc is not embedded into the ReviewMark +codebase, so fitness for use is verified through repeated pipeline execution of +its real integration path rather than through a local wrapper test. The +`build-docs` job runs `dotnet pandoc` with each document collection's +`definition.yaml` file and then immediately runs FileAssert checks from +`.fileassert.yaml` against the generated HTML outputs. Because the build fails if +any expected HTML file is missing, lacks a title element, or omits required +content, a passing CI run shows that the pinned Pandoc tool version can convert +this repository's markdown collections into usable HTML evidence. No +project-specific qualification issues are currently recorded for this pinned +version. + +### Test Scenarios + +**PandocBuildNotesHtmlGeneration**: Pandoc converts the build notes collection +into `docs/build_notes/generated/build_notes.html`, proving it can render the +repository's release-note inputs into HTML with a valid title and expected +content. The expected outcome is a passing FileAssert check for the generated +file. This scenario is tested by `Pandoc_BuildNotesHtml`. + +**PandocCodeQualityHtmlGeneration**: Pandoc converts the code quality collection +into `docs/code_quality/generated/quality.html`, proving it can render the +CodeQL and SonarCloud markdown inputs used by this repository. The expected +outcome is a passing FileAssert check for the generated file. This scenario is +tested by `Pandoc_CodeQualityHtml`. + +**PandocReviewPlanHtmlGeneration**: Pandoc converts the code review plan +collection into `docs/code_review_plan/generated/plan.html`, proving it can +render ReviewMark's generated planning markdown into the published HTML form. +The expected outcome is a passing FileAssert check for the generated file. This +scenario is tested by `Pandoc_ReviewPlanHtml`. + +**PandocReviewReportHtmlGeneration**: Pandoc converts the code review report +collection into `docs/code_review_report/generated/report.html`, proving it can +render ReviewMark's generated reporting markdown into the published HTML form. +The expected outcome is a passing FileAssert check for the generated file. This +scenario is tested by `Pandoc_ReviewReportHtml`. + +**PandocDesignHtmlGeneration**: Pandoc converts the software design collection +into `docs/design/generated/design.html`, proving it can render the checked-in +technical design documentation used by this repository. The expected outcome is +a passing FileAssert check for the generated file. This scenario is tested by +`Pandoc_DesignHtml`. + +**PandocVerificationHtmlGeneration**: Pandoc converts the verification +collection into `docs/verification/generated/verification.html`, proving it can +render the verification design and OTS verification content used for compliance +evidence. The expected outcome is a passing FileAssert check for the generated +file. This scenario is tested by `Pandoc_VerificationHtml`. + +**PandocUserGuideHtmlGeneration**: Pandoc converts the user guide collection +into `docs/user_guide/generated/user_guide.html`, proving it can render the +end-user documentation published by the repository. The expected outcome is a +passing FileAssert check for the generated file. This scenario is tested by +`Pandoc_UserGuideHtml`. + +### Requirements Coverage + +- **ReviewMark-Pandoc-ConvertMarkdown**: Pandoc shall convert Markdown documents + to HTML containing a valid title element and expected document content. + - *PandocBuildNotesHtmlGeneration*: verifies Pandoc generates the build notes + HTML output with a title element and expected content. + - `Pandoc_BuildNotesHtml` + - *PandocCodeQualityHtmlGeneration*: verifies Pandoc generates the code + quality HTML output with a title element and expected content. + - `Pandoc_CodeQualityHtml` + - *PandocReviewPlanHtmlGeneration*: verifies Pandoc generates the review plan + HTML output with a title element and expected content. + - `Pandoc_ReviewPlanHtml` + - *PandocReviewReportHtmlGeneration*: verifies Pandoc generates the review + report HTML output with a title element and expected content. + - `Pandoc_ReviewReportHtml` + - *PandocDesignHtmlGeneration*: verifies Pandoc generates the design HTML + output with a title element and expected content. + - `Pandoc_DesignHtml` + - *PandocVerificationHtmlGeneration*: verifies Pandoc generates the + verification HTML output with a title element and expected content. + - `Pandoc_VerificationHtml` + - *PandocUserGuideHtmlGeneration*: verifies Pandoc generates the user guide + HTML output with a title element and expected content. + - `Pandoc_UserGuideHtml` diff --git a/docs/verification/ots/pdfsharp.md b/docs/verification/ots/pdfsharp.md new file mode 100644 index 0000000..db158fb --- /dev/null +++ b/docs/verification/ots/pdfsharp.md @@ -0,0 +1,42 @@ +## PDFsharp + +### Verification Approach + +ReviewMark uses PDFsharp 6.2.4, referenced from `DemaConsulting.ReviewMark.csproj`, to read PDF +document metadata in the Indexing subsystem. The integration surface is `PdfReader.Open` with +`PdfDocumentOpenMode.Import` and `doc.Info.Keywords` inside `ReviewIndex.ProcessPdf()`, where +ReviewMark extracts `id`, `fingerprint`, `date`, and `result` values from the Keywords field and +skips incomplete evidence files with warnings. Fitness for intended use is verified by dedicated +OTS tests in `test/OtsSoftwareTests/PDFsharpTests.cs`, index integration tests in +`test/DemaConsulting.ReviewMark.Tests/Indexing/IndexTests.cs`, and the `dotnet test` step in the +`build` matrix job of `build.yaml`, which publishes TRX evidence to `artifacts/`. No +project-specific issues have been observed in this validated import-only usage. + +### Test Scenarios + +**PDFsharpReadMetadata**: A PDF opened in import mode exposes the Keywords metadata that ReviewMark +uses as its evidence payload, and valid review metadata is carried through into the in-memory +review index. This scenario is tested by `PdfReader_Open_ImportMode_ExposesKeywordsField`, +`ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex`, and +`ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries`. + +**PDFsharpGracefulMetadataAbsenceHandling**: Missing or incomplete Keywords metadata does not cause +a crash; instead, ReviewMark skips the PDF and reports warnings so unrelated evidence files can +still be indexed. This scenario is tested by `PdfReader_Open_ImportMode_NoKeywords_ReturnsNullOrEmpty`, +`ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning`, +`ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning`, and +`ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning`. + +### Requirements Coverage + +- **ReviewMark-OTS-PDFsharp-ReadMetadata**: PDFsharp shall provide access to the Keywords metadata + field of a PDF document opened in import mode. + - *PDFsharpReadMetadata* + - `PdfReader_Open_ImportMode_ExposesKeywordsField` + - `ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex` + - `ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries` + - *PDFsharpGracefulMetadataAbsenceHandling* + - `PdfReader_Open_ImportMode_NoKeywords_ReturnsNullOrEmpty` + - `ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning` + - `ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning` + - `ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning` diff --git a/docs/verification/ots/reqstream.md b/docs/verification/ots/reqstream.md index 33644b4..f2d2352 100644 --- a/docs/verification/ots/reqstream.md +++ b/docs/verification/ots/reqstream.md @@ -1,28 +1,35 @@ ## ReqStream -**Component**: DemaConsulting.ReqStream -**Role**: Traces requirements from YAML definition files and validates coverage against test evidence. -**Acceptance approach**: Automated build pipeline verification. +### Verification Approach -ReqStream is invoked in the GitHub Actions CI workflow (`build.yaml`) within the -`build-docs` job. The "Run ReqStream self-validation" step runs `dotnet reqstream ---validate`, producing `artifacts/reqstream-self-validation.trx`. Subsequently, the -"Generate Requirements Report, Justifications, and Trace Matrix" step runs ReqStream -with `--enforce`, which exits non-zero if any requirement lacks test evidence, making -uncovered requirements a build-breaking condition. A successful CI pipeline run therefore -proves both that ReqStream is operational and that all requirements are covered. +This repository pins DemaConsulting.ReqStream version 1.10.0 in the local tool +manifest and uses that CLI as the final requirements traceability gate in the +`build-docs` job of `build.yaml`. Fitness for use is +verified by both self-validation and live enforcement. The `Run ReqStream +self-validation` step executes `dotnet reqstream --validate` and writes +`artifacts/reqstream-self-validation.trx`. The subsequent `Generate +Requirements Report, Justifications, and Trace Matrix` step runs +`dotnet reqstream --requirements requirements.yaml --tests "artifacts/**/*.trx" +--report ... --justifications ... --matrix ... --enforce`. Because that command +fails the workflow if any requirement lacks passing test evidence, a successful +run shows that the pinned tool version correctly processes the repository's +requirements set and accumulated TRX inputs. No project-specific qualification +issues are currently recorded for this pinned version. -### Test scenario coverage +### Test Scenarios -- **`ReqStream_EnforcementMode`** — ReqStream's self-validation confirms enforcement mode - behaviour: when run with `--enforce`, ReqStream exits non-zero if any requirement lacks - linked test evidence, making uncovered requirements a build-breaking condition. - CI Evidence: "Run ReqStream self-validation" step in the `build-docs` job of - `build.yaml`, writing results to `artifacts/reqstream-self-validation.trx`. +**ReqStreamEnforcementMode**: ReqStream processes `requirements.yaml` together +with the collected `artifacts/**/*.trx` files and enforces that every +requirement has linked passing evidence, proving the repository can rely on the +tool for release-gating traceability. The expected outcome is a passing +self-validation result and a successful `--enforce` run that generates the +requirements report, justifications, and trace matrix. This scenario is tested +by `ReqStream_EnforcementMode`. -The subsequent `--enforce` run (consuming all previously generated TRX files including -FileAssert, BuildMark, and OTS self-validation results) provides additional runtime -evidence that ReqStream correctly processed `requirements.yaml` and found all requirements -covered by passing tests. +### Requirements Coverage -**Requirement coverage**: `ReviewMark-OTS-ReqStream` +- **ReviewMark-OTS-ReqStream**: ReqStream shall enforce that every requirement + is linked to passing test evidence. + - *ReqStreamEnforcementMode*: verifies ReqStream enforces coverage across the + repository requirements set and the collected TRX evidence. + - `ReqStream_EnforcementMode` diff --git a/docs/verification/ots/reviewmark.md b/docs/verification/ots/reviewmark.md index d863945..4484ca5 100644 --- a/docs/verification/ots/reviewmark.md +++ b/docs/verification/ots/reviewmark.md @@ -1,36 +1,74 @@ ## ReviewMark -**Component**: DemaConsulting.ReviewMark (this tool) -**Role**: Scans file evidence stores, generates review plan and review report documents, -and enforces that all governed files have current review records. -**Acceptance approach**: Self-test and automated unit/integration test coverage. - -ReviewMark verifies itself through the `--validate` command (self-test). This executes -the tool's own built-in self-test suite and confirms the tool is correctly installed and -operating. The self-test is run via the "Run ReviewMark self-validation" step in the -`build-docs` job of the GitHub Actions workflow (`build.yaml`), producing -`artifacts/reviewmark-self-validation.trx`. - -Unit and integration tests in `test/` provide additional coverage of the individual -subsystems (Cli, Configuration, Indexing, SelfTest, Program) and the four OTS-level -capabilities described below. - -### Test scenario coverage - -- **`ReviewMark_ValidateFlag_Invoked_RunsValidation`** — ReviewMark runs its built-in - self-test suite via `--validate`, exits successfully, and outputs a validation summary - — confirming it can scan its own configuration and report review status. - Requirement: `ReviewMark-OTS-ReviewMark-Scan` -- **`ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero`** — ReviewMark exits with a - non-zero code when `--enforce` is supplied and the evidence source contains no matching - review records, proving enforcement behaviour is operative. - Requirement: `ReviewMark-OTS-ReviewMark-Enforce` -- **`ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan`** — ReviewMark generates - a markdown review plan file from a definition file, and the plan contains the configured - review-set identifier. Requirement: `ReviewMark-OTS-ReviewMark-Elaborate` -- **`ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport`** — ReviewMark - generates a markdown review report file from a definition file, and the report contains - the configured review-set identifier. Requirement: `ReviewMark-OTS-ReviewMark-Report` - -**Requirement coverage**: `ReviewMark-OTS-ReviewMark-Scan`, `ReviewMark-OTS-ReviewMark-Enforce`, -`ReviewMark-OTS-ReviewMark-Elaborate`, `ReviewMark-OTS-ReviewMark-Report` +### Verification Approach + +This repository pins DemaConsulting.ReviewMark version 1.2.0 in the local tool +manifest and also verifies the ReviewMark executable as an OTS build tool +within its own compliance pipeline. Fitness for use is shown by a +combination of self-validation, CI execution, and repository integration tests. +The `Run ReviewMark self-validation` step in the `build-docs` job executes +`dotnet reviewmark --validate` and writes +`artifacts/reviewmark-self-validation.trx`. The same job then runs +`dotnet reviewmark --plan ... --report ...` to produce the generated review plan +and review report consumed by the documentation pipeline. In addition, +`test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs` verifies plan +creation, report creation, enforcement failure behavior, and self-validation +entry points directly. One known project-specific limitation remains: the +`build-docs` workflow does not yet pass `--enforce` during document generation +until the `reviews` branch is populated with production review evidence, so the +repository relies on self-validation and integration tests for enforcement +qualification. + +### Test Scenarios + +**ReviewMarkSelfValidation**: ReviewMark runs its built-in validation suite when +invoked with `--validate`, proving the installed tool can execute its +self-checks and report results in a form the pipeline can archive. The expected +outcome is a zero exit code and validation summary output, with CI evidence in +`artifacts/reviewmark-self-validation.trx`. This scenario is tested by +`ReviewMark_ValidateFlag_Invoked_RunsValidation`. + +**ReviewMarkPlanGeneration**: ReviewMark reads a definition file and generates a +markdown review plan listing the configured review set, proving it can create +the planning artifact required by this repository's code review documentation. +The expected outcome is a generated plan file containing the configured +review-set identifier. This scenario is tested by +`ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan`. + +**ReviewMarkReportGeneration**: ReviewMark reads a definition file and generates +a markdown review report summarizing review status, proving it can scan the +configured evidence source and render the reporting artifact consumed by the +documentation pipeline. The expected outcome is a generated report file +containing the configured review-set identifier. This scenario is tested by +`ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport`. + +**ReviewMarkEnforcementFailure**: ReviewMark returns a non-zero exit code when +`--enforce` is used and no current evidence is available, proving the tool can +turn review gaps into a build-breaking condition. The expected outcome is a +failing command when the evidence source cannot satisfy the configured review +set. This scenario is tested by +`ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero`. + +### Requirements Coverage + +- **ReviewMark-OTS-ReviewMark-Scan**: ReviewMark shall scan file evidence stores + and produce a report of review status for all governed files. + - *ReviewMarkReportGeneration*: verifies ReviewMark generates the review + report that expresses scan results for the configured review set. + - `ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport` +- **ReviewMark-OTS-ReviewMark-Enforce**: ReviewMark shall enforce that all + governed files have current review records, failing the build when any are + missing or outdated. + - *ReviewMarkEnforcementFailure*: verifies ReviewMark returns a non-zero exit + code when `--enforce` detects missing current review evidence. + - `ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero` +- **ReviewMark-OTS-ReviewMark-Plan**: ReviewMark shall generate a review plan + document listing all review-sets and the files governed by each. + - *ReviewMarkPlanGeneration*: verifies ReviewMark generates a markdown review + plan containing the configured review-set identifier. + - `ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan` +- **ReviewMark-OTS-ReviewMark-Report**: ReviewMark shall generate a review + report document summarizing the evidence status for each review-set. + - *ReviewMarkReportGeneration*: verifies ReviewMark generates a markdown + review report containing the configured review-set identifier. + - `ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport` diff --git a/docs/verification/ots/sarifmark.md b/docs/verification/ots/sarifmark.md index 2d4f893..b72d1ab 100644 --- a/docs/verification/ots/sarifmark.md +++ b/docs/verification/ots/sarifmark.md @@ -1,32 +1,42 @@ ## SarifMark -**Component**: DemaConsulting.SarifMark -**Role**: Generates markdown reports from SARIF static analysis output files. -**Acceptance approach**: Automated build pipeline verification. +### Verification Approach -SarifMark is maintained by DemaConsulting and is used as a build tool in the CI/CD -pipeline. Its integration is verified through the GitHub Actions workflow (`build.yaml`), -where two steps run within the `build-docs` job. The "Run SarifMark self-validation" -step executes `dotnet sarifmark --validate` and writes results to -`artifacts/sarifmark-self-validation.trx`. The "Generate CodeQL Quality Report with -SarifMark" step reads the CodeQL SARIF output from `artifacts/csharp.sarif` and renders -it as a markdown quality report at `docs/code_quality/generated/codeql-quality.md`. A -non-zero exit from either step fails the CI build, providing evidence that SarifMark -read the SARIF file and generated the report correctly. +This repository pins DemaConsulting.SarifMark version 1.3.2 in the local tool +manifest and uses that CLI in the `build-docs` job of `build.yaml` to transform +CodeQL SARIF output into markdown evidence. Fitness +for use is verified by both self-validation and live pipeline execution. The +`Run SarifMark self-validation` step executes `dotnet sarifmark --validate` and +writes `artifacts/sarifmark-self-validation.trx`. The `Generate CodeQL Quality +Report with SarifMark` step then processes `artifacts/csharp.sarif` and writes +`docs/code_quality/generated/codeql-quality.md`, which is later checked by +FileAssert. Because either failure stops the workflow, a passing CI run shows +that the pinned tool version can read the repository's SARIF input and render +its required markdown report. No project-specific qualification issues are +currently recorded for this pinned version. -### Test scenario coverage +### Test Scenarios -- **`SarifMark_SarifReading`** — SarifMark successfully reads a SARIF file from CodeQL - code scanning and parses it without error. CI Evidence: "Run SarifMark self-validation" - step in the `build-docs` job of `build.yaml`, writing results to - `artifacts/sarifmark-self-validation.trx`. -- **`SarifMark_MarkdownReportGeneration`** — SarifMark generates a markdown quality - report from a CodeQL SARIF input, producing - `docs/code_quality/generated/codeql-quality.md`. CI Evidence: "Generate CodeQL Quality - Report with SarifMark" step in the `build-docs` job of `build.yaml`, confirmed by the - subsequent FileAssert validation. +**SarifMarkSarifReading**: SarifMark reads the CodeQL SARIF file produced in the +same workflow, proving the tool can parse the machine-readable analysis format +used by this repository. The expected outcome is a passing self-validation +result in `artifacts/sarifmark-self-validation.trx`. This scenario is tested by +`SarifMark_SarifReading`. -Both scenarios together confirm `ReviewMark-OTS-SarifMark`: SarifMark correctly reads -SARIF input produced by CodeQL and renders it as a human-readable markdown report. +**SarifMarkMarkdownReportGeneration**: SarifMark renders the parsed SARIF data +into `docs/code_quality/generated/codeql-quality.md`, proving the tool is fit +for its intended use as the CodeQL markdown report generator in this build. The +expected outcome is a generated report followed by a successful FileAssert +validation step. This scenario is tested by +`SarifMark_MarkdownReportGeneration`. -**Requirement coverage**: `ReviewMark-OTS-SarifMark` +### Requirements Coverage + +- **ReviewMark-OTS-SarifMark**: SarifMark shall convert CodeQL SARIF results + into a markdown report. + - *SarifMarkSarifReading*: verifies SarifMark reads and parses the CodeQL + SARIF input used by the build. + - `SarifMark_SarifReading` + - *SarifMarkMarkdownReportGeneration*: verifies SarifMark renders the parsed + SARIF input into the markdown report consumed by this repository. + - `SarifMark_MarkdownReportGeneration` diff --git a/docs/verification/ots/sonarmark.md b/docs/verification/ots/sonarmark.md index 7149220..9a20186 100644 --- a/docs/verification/ots/sonarmark.md +++ b/docs/verification/ots/sonarmark.md @@ -1,41 +1,64 @@ ## SonarMark -**Component**: DemaConsulting.SonarMark -**Role**: Generates markdown reports from SonarCloud/SonarQube analysis results. -**Acceptance approach**: Automated build pipeline verification. - -SonarMark is maintained by DemaConsulting and is used as a build tool in the CI/CD -pipeline. Its integration is verified through the GitHub Actions workflow (`build.yaml`), -where two steps run within the `build-docs` job. The "Run SonarMark self-validation" -step executes `dotnet sonarmark --validate` and writes results to -`artifacts/sonarmark-self-validation.trx`, confirming the tool is correctly installed -and its internal self-test scenarios pass. The "Generate SonarCloud Quality Report" step -calls `dotnet sonarmark --server https://sonarcloud.io … --report docs/code_quality/generated/sonar-quality.md`, -retrieving quality-gate, issues, and hotspots data from SonarCloud and rendering it as a -markdown document. A non-zero exit from either step fails the CI build. - -The `--validate` self-validation step exercises four named test scenarios that cover the -full retrieval-and-reporting workflow: quality-gate status retrieval, issues retrieval, -hotspots retrieval, and markdown report generation. - -### Test scenario coverage - -- **`SonarMark_QualityGateRetrieval`** — SonarMark successfully retrieves the quality-gate - status from a SonarCloud project. CI Evidence: "Run SonarMark self-validation" step in - the `build-docs` job of `build.yaml`, writing results to - `artifacts/sonarmark-self-validation.trx`. -- **`SonarMark_IssuesRetrieval`** — SonarMark successfully retrieves the list of open - issues from a SonarCloud project. CI Evidence: Same "Run SonarMark self-validation" - step, same TRX file. -- **`SonarMark_HotSpotsRetrieval`** — SonarMark successfully retrieves the list of - security hotspots from a SonarCloud project. CI Evidence: Same "Run SonarMark - self-validation" step, same TRX file. -- **`SonarMark_MarkdownReportGeneration`** — SonarMark generates a markdown quality report - from retrieved SonarCloud data, producing the expected report document. CI Evidence: - Same "Run SonarMark self-validation" step and the "Generate SonarCloud Quality Report" - step in the `build-docs` job, confirmed by successful report generation. - -All four scenarios together confirm `ReviewMark-OTS-SonarMark`: SonarMark correctly -retrieves SonarCloud analysis data and renders it as a human-readable markdown report. - -**Requirement coverage**: `ReviewMark-OTS-SonarMark` +### Verification Approach + +This repository pins DemaConsulting.SonarMark version 1.5.0 in the local tool +manifest and uses that CLI in the `build-docs` job of `build.yaml` to generate +the SonarCloud portion of the code quality evidence. +Fitness for use is verified in two ways. First, the `Run SonarMark +self-validation` step executes `dotnet sonarmark --validate` and writes +`artifacts/sonarmark-self-validation.trx`. Second, the `Generate SonarCloud +Quality Report` step uses the same pinned tool version to query SonarCloud for +the ReviewMark SonarCloud project and render +`docs/code_quality/generated/sonar-quality.md`. Because either failure stops the +workflow, a passing CI run shows that the tool can retrieve the data needed by +this repository and generate the required markdown output. No project-specific +qualification issues are currently recorded for this pinned version. + +### Test Scenarios + +**SonarMarkQualityGateRetrieval**: SonarMark retrieves the current quality-gate +status from the configured SonarCloud project, proving the pinned tool version +can authenticate and read the primary pass/fail signal used in the quality +report. The expected outcome is a passing self-validation result recorded in +`artifacts/sonarmark-self-validation.trx`. This scenario is tested by +`SonarMark_QualityGateRetrieval`. + +**SonarMarkIssuesRetrieval**: SonarMark retrieves the open-issues data set from +SonarCloud so that the repository can include actionable issue information in +its generated code quality evidence. The expected outcome is a passing +self-validation result for issue retrieval in +`artifacts/sonarmark-self-validation.trx`. This scenario is tested by +`SonarMark_IssuesRetrieval`. + +**SonarMarkHotSpotsRetrieval**: SonarMark retrieves the security hot spot data +set from SonarCloud so that the quality report covers the security review data +used by this project. The expected outcome is a passing self-validation result +for security hot spot retrieval in `artifacts/sonarmark-self-validation.trx`. +This +scenario is tested by `SonarMark_HotSpotsRetrieval`. + +**SonarMarkMarkdownReportGeneration**: SonarMark renders the retrieved +SonarCloud data into `docs/code_quality/generated/sonar-quality.md`, proving +that the tool is fit for the repository's intended use as a markdown evidence +producer. The expected outcome is a generated report with a zero-exit workflow +step in `build.yaml`. This scenario is tested by +`SonarMark_MarkdownReportGeneration`. + +### Requirements Coverage + +- **ReviewMark-OTS-SonarMark**: SonarMark shall generate a SonarCloud quality + report. + - *SonarMarkQualityGateRetrieval*: verifies SonarMark retrieves the current + quality-gate status needed by the report. + - `SonarMark_QualityGateRetrieval` + - *SonarMarkIssuesRetrieval*: verifies SonarMark retrieves the open-issues + data included in the report. + - `SonarMark_IssuesRetrieval` + - *SonarMarkHotSpotsRetrieval*: verifies SonarMark retrieves the + security hot spot data included in the report. + - `SonarMark_HotSpotsRetrieval` + - *SonarMarkMarkdownReportGeneration*: verifies SonarMark renders the + retrieved SonarCloud data into the markdown report consumed by this + repository. + - `SonarMark_MarkdownReportGeneration` diff --git a/docs/verification/ots/versionmark.md b/docs/verification/ots/versionmark.md index 55dd68a..c9ae4d7 100644 --- a/docs/verification/ots/versionmark.md +++ b/docs/verification/ots/versionmark.md @@ -1,44 +1,33 @@ ## VersionMark -**Component**: DemaConsulting.VersionMark -**Role**: Captures tool version metadata and publishes a versions markdown document. -**Acceptance approach**: Automated build pipeline verification. - -VersionMark is maintained by DemaConsulting and is used as a build tool in the CI/CD -pipeline. Its integration is verified through the GitHub Actions workflow (`build.yaml`), -where "Run VersionMark self-validation" steps execute `dotnet versionmark --validate` -in three separate jobs: - -- The `quality-checks` job runs VersionMark self-validation, writing results to - `artifacts/versionmark-self-validation-quality.trx`. -- The `build` matrix job runs VersionMark self-validation on each operating system - (windows-latest, ubuntu-latest, macos-latest), writing results to - `artifacts/versionmark-self-validation-{os}.trx`. -- The `build-docs` job runs VersionMark self-validation, writing results to - `artifacts/versionmark-self-validation.trx`, and subsequently runs - `dotnet versionmark --publish` to generate the `docs/build_notes/generated/versions.md` - report from all collected `artifacts/**/versionmark-*.json` capture files. - -A non-zero exit from any self-validation step fails the CI build, providing -cross-platform evidence that VersionMark captured tool version information and generated -the versions markdown report correctly. - -### Test scenario coverage - -- **`VersionMark_CapturesVersions`** — VersionMark successfully captures version metadata - for each tool in the pipeline and writes a JSON capture file without error. - CI Evidence: "Run VersionMark self-validation" steps in the `quality-checks` job - (`artifacts/versionmark-self-validation-quality.trx`), the `build` matrix job - (`artifacts/versionmark-self-validation-{os}.trx`), and the `build-docs` job - (`artifacts/versionmark-self-validation.trx`) of `build.yaml`. -- **`VersionMark_GeneratesMarkdownReport`** — VersionMark aggregates captured version JSON - files and generates a markdown versions report from the pipeline metadata. - CI Evidence: "Run VersionMark self-validation" step in the `build-docs` job of - `build.yaml`, confirmed by the "Publish Tool Versions" step that generates - `docs/build_notes/generated/versions.md`. - -Both scenarios together confirm `ReviewMark-OTS-VersionMark`: VersionMark correctly -captures tool version information across all pipeline jobs and publishes it as a -human-readable markdown report included in the release artifacts. - -**Requirement coverage**: `ReviewMark-OTS-VersionMark` +### Verification Approach + +ReviewMark uses the `DemaConsulting.VersionMark` local tool at version 1.4.3, declared in +`.config/dotnet-tools.json`, to capture tool-version metadata and publish a consolidated versions +report for the build notes. The integration surface is the `dotnet versionmark --capture`, +`--validate`, and `--publish` commands used throughout `build.yaml`: the `quality-checks` job, +the cross-platform `build` matrix job, the `integration-test` matrix job, and the `build-docs` +job all capture JSON version records, while the `build-docs` job also runs self-validation and +publishes `docs/build_notes/generated/versions.md` from `artifacts/**/versionmark-*.json`. A +non-zero exit from any self-validation or publish step fails the pipeline, so successful CI runs +provide direct evidence that VersionMark is fit for this project's traceability workflow. No +project-specific issues have been observed in this validated capture-and-publish path. + +### Test Scenarios + +**VersionMarkCapture**: VersionMark captures version metadata for the tools used in each pipeline +stage and writes structured JSON records without interrupting the build. This scenario is tested by +`VersionMark_CapturesVersions`. + +**VersionMarkReportGeneration**: VersionMark aggregates the captured JSON records and publishes a +human-readable markdown report for the build notes document. This scenario is tested by +`VersionMark_GeneratesMarkdownReport`. + +### Requirements Coverage + +- **ReviewMark-OTS-VersionMark-Capture**: VersionMark shall capture tool version metadata. + - *VersionMarkCapture* + - `VersionMark_CapturesVersions` +- **ReviewMark-OTS-VersionMark-Report**: VersionMark shall generate a markdown versions report. + - *VersionMarkReportGeneration* + - `VersionMark_GeneratesMarkdownReport` diff --git a/docs/verification/ots/weasyprint.md b/docs/verification/ots/weasyprint.md index 0b258db..8e03180 100644 --- a/docs/verification/ots/weasyprint.md +++ b/docs/verification/ots/weasyprint.md @@ -1,61 +1,61 @@ ## WeasyPrint -**Component**: WeasyPrint (<https://weasyprint.org/>) -**Role**: Converts HTML documents to PDF as part of the documentation build pipeline. -**Acceptance approach**: FileAssert integration tests validating each PDF output. - -WeasyPrint is a widely adopted open-source HTML/CSS-to-PDF converter used in the build -pipeline. ReviewMark does not embed WeasyPrint; it is an external build dependency. -Correct WeasyPrint behaviour is confirmed by FileAssert integration tests in the GitHub -Actions CI workflow (`build.yaml`), which run within the `build-docs` job on the -`windows-latest` runner. Each document group has a dedicated WeasyPrint PDF generation -step followed by a FileAssert validation step that asserts the PDF file exists, contains -correct PDF metadata (Title, Author, Subject), has at least the minimum expected page -count, and contains expected document text content. - -FileAssert integration tests validate that each WeasyPrint invocation produced a -well-formed PDF with correct metadata, at least one page, and expected document content. - -### Test scenario coverage - -- **`WeasyPrint_BuildNotesPdf`** (Build Notes) — WeasyPrint generated - `"docs/generated/ReviewMark Build Notes.pdf"` with Title containing "ReviewMark", - Author "DEMA Consulting", Subject "Build notes", at least 1 page, and text containing - "Build Notes". CI Evidence: "Assert Build Notes Documents with FileAssert" step → - `artifacts/fileassert-build-notes.trx`. -- **`WeasyPrint_CodeQualityPdf`** (Code Quality) — WeasyPrint generated - `"docs/generated/ReviewMark Code Quality.pdf"` with Title containing "Code Quality", - Author "DEMA Consulting", Subject "Code Quality", at least 1 page, and text containing - "CodeQL". CI Evidence: "Assert Code Quality Documents with FileAssert" step → - `artifacts/fileassert-code-quality.trx`. -- **`WeasyPrint_ReviewPlanPdf`** (Review Plan) — WeasyPrint generated - `"docs/generated/ReviewMark Review Plan.pdf"` with Title containing "Review Plan", - Author "DEMA Consulting", Subject "Review Plan", at least 1 page, and text containing - "Review Plan". CI Evidence: "Assert Code Review Documents with FileAssert" step → - `artifacts/fileassert-code-review.trx`. -- **`WeasyPrint_ReviewReportPdf`** (Review Report) — WeasyPrint generated - `"docs/generated/ReviewMark Review Report.pdf"` with Title containing "Review Report", - Author "DEMA Consulting", Subject "Review Report", at least 1 page, and text containing - "Review Report". CI Evidence: "Assert Code Review Documents with FileAssert" step → - `artifacts/fileassert-code-review.trx`. -- **`WeasyPrint_DesignPdf`** (Design) — WeasyPrint generated - `"docs/generated/ReviewMark Software Design.pdf"` with Title containing "Design", - Author "DEMA Consulting", Subject "Design Document", at least 3 pages, and text - containing "Design". CI Evidence: "Assert Design Documents with FileAssert" step → - `artifacts/fileassert-design.trx`. -- **`WeasyPrint_VerificationPdf`** (Verification) — WeasyPrint generated - `"docs/generated/ReviewMark Software Verification Design.pdf"` with Title containing - "Verification", Author "DEMA Consulting", Subject "Verification design document", - at least 3 pages, and text containing "Verification". CI Evidence: "Assert - Verification Documents with FileAssert" step → `artifacts/fileassert-verification.trx`. -- **`WeasyPrint_UserGuidePdf`** (User Guide) — WeasyPrint generated - `"docs/generated/ReviewMark User Guide.pdf"` with Title containing "User Guide", - Author "DEMA Consulting", Subject "File-Review Evidence Management", at least 3 pages, - and text containing "User Guide". CI Evidence: "Assert User Guide Documents with - FileAssert" step → `artifacts/fileassert-user-guide.trx`. - -All seven scenarios together confirm `ReviewMark-OTS-WeasyPrint`: WeasyPrint correctly -converts HTML documents to well-formed, metadata-correct PDFs across all document types -in the release artifact set. - -**Requirement coverage**: `ReviewMark-OTS-WeasyPrint` +### Verification Approach + +ReviewMark uses the `DemaConsulting.WeasyPrintTool` local tool at version 68.1.0, declared in +`.config/dotnet-tools.json`, to render generated HTML documents to PDF/A-3u in the `build-docs` +job of `build.yaml`. The integration surface is the `dotnet weasyprint` command used for the Build +Notes, Code Quality, Review Plan, Review Report, Design, Verification, and User Guide document +collections. Fitness for intended use is verified by paired FileAssert checks defined in +`.fileassert.yaml` and executed immediately after each PDF generation step, confirming that each PDF +exists, contains the expected metadata, has at least the minimum page count, and includes expected +text content. The resulting TRX evidence is written to `artifacts/fileassert-*.trx`. No +project-specific issues have been observed in this validated document-rendering path. + +### Test Scenarios + +**WeasyPrintBuildNotesPdf**: The Build Notes HTML output is rendered to a valid PDF with the +expected title, author, subject, minimum page count, and visible "Build Notes" content. This +scenario is tested by `WeasyPrint_BuildNotesPdf`. + +**WeasyPrintCodeQualityPdf**: The Code Quality HTML output is rendered to a valid PDF with the +expected metadata and visible "CodeQL" content. This scenario is tested by +`WeasyPrint_CodeQualityPdf`. + +**WeasyPrintReviewPlanPdf**: The Review Plan HTML output is rendered to a valid PDF with the +expected metadata and visible "Review Plan" content. This scenario is tested by +`WeasyPrint_ReviewPlanPdf`. + +**WeasyPrintReviewReportPdf**: The Review Report HTML output is rendered to a valid PDF with the +expected metadata and visible "Review Report" content. This scenario is tested by +`WeasyPrint_ReviewReportPdf`. + +**WeasyPrintDesignPdf**: The Design HTML output is rendered to a valid PDF with the expected +metadata, at least three pages, and visible design content. This scenario is tested by +`WeasyPrint_DesignPdf`. + +**WeasyPrintVerificationPdf**: The Verification HTML output is rendered to a valid PDF with the +expected metadata, at least three pages, and visible verification content. This scenario is tested +by `WeasyPrint_VerificationPdf`. + +**WeasyPrintUserGuidePdf**: The User Guide HTML output is rendered to a valid PDF with the expected +metadata, at least three pages, and visible user guide content. This scenario is tested by +`WeasyPrint_UserGuidePdf`. + +### Requirements Coverage + +- **ReviewMark-OTS-WeasyPrint**: WeasyPrint shall convert HTML documents to valid PDF. + - *WeasyPrintBuildNotesPdf* + - `WeasyPrint_BuildNotesPdf` + - *WeasyPrintCodeQualityPdf* + - `WeasyPrint_CodeQualityPdf` + - *WeasyPrintReviewPlanPdf* + - `WeasyPrint_ReviewPlanPdf` + - *WeasyPrintReviewReportPdf* + - `WeasyPrint_ReviewReportPdf` + - *WeasyPrintDesignPdf* + - `WeasyPrint_DesignPdf` + - *WeasyPrintVerificationPdf* + - `WeasyPrint_VerificationPdf` + - *WeasyPrintUserGuidePdf* + - `WeasyPrint_UserGuidePdf` diff --git a/docs/verification/ots/xunit.md b/docs/verification/ots/xunit.md index f7975b2..20f59e6 100644 --- a/docs/verification/ots/xunit.md +++ b/docs/verification/ots/xunit.md @@ -1,50 +1,60 @@ ## xUnit -**Component**: xunit.v3 + xunit.runner.visualstudio (<https://xunit.net/>) -**Role**: Test framework for all ReviewMark unit and integration tests. -**Acceptance approach**: Established industry use and automated test coverage. - -xUnit.net v3 is a widely adopted open-source .NET testing framework with a large -active community, extensive documentation, and its own comprehensive test suite. It -is used by the .NET team and many major open-source projects. - -All ReviewMark unit and integration tests are written using xUnit.net v3. The test -suite is run as part of `build.ps1` and in the `build` matrix job of `build.yaml` -(`dotnet test … --logger "trx;LogFilePrefix={os}" --results-directory artifacts`). -A successful test run confirms that xUnit discovered, executed, and reported results -for all test methods. - -Because xUnit discovers and runs these tests, and produces TRX output consumed by the -requirements trace matrix, their successful completion constitutes self-validation of -the framework. - -### Test scenario coverage - -The following test methods, linked in `ReviewMark-OTS-xUnit-Execute` and -`ReviewMark-OTS-xUnit-Report`, provide evidence that xUnit discovers tests, runs them, -and reports results in TRX format. Any test passing through xUnit proves the framework -performs all three behaviours correctly. - -- **`Context_Create_NoArguments_ReturnsDefaultContext`** — Parsing an empty argument list - returns a default-initialized context. -- **`Context_Create_VersionFlag_SetsVersionTrue`** — Parsing `--version` sets the version - flag to true in the context. -- **`Context_Create_HelpFlag_SetsHelpTrue`** — Parsing `--help` sets the help flag to - true in the context. -- **`Context_Create_SilentFlag_SetsSilentTrue`** — Parsing `--silent` sets the silent - flag to true in the context. -- **`Context_Create_ValidateFlag_SetsValidateTrue`** — Parsing `--validate` sets the - validate flag to true in the context. -- **`Context_Create_ResultsFlag_SetsResultsFile`** — Parsing `--results <file>` captures - the results file path in the context. -- **`Context_Create_LogFlag_OpensLogFile`** — Parsing `--log <file>` opens the specified - log file in the context. -- **`Context_Create_UnknownArgument_ThrowsArgumentException`** — Parsing an unrecognised - argument raises an `ArgumentException`. -- **`Context_Create_ShortVersionFlag_SetsVersionTrue`** — Parsing `-v` (short form) sets - the version flag to true in the context. - -CI evidence source for all scenarios: `dotnet test` step in the `build` matrix job of -`build.yaml`, writing TRX result files to `artifacts/`. - -**Requirement coverage**: `ReviewMark-OTS-xUnit-Execute`, `ReviewMark-OTS-xUnit-Report` +### Verification Approach + +ReviewMark uses `xunit.v3` 3.2.2 and `xunit.runner.visualstudio` 3.1.5 in both test projects, +`test/DemaConsulting.ReviewMark.Tests` and `test/OtsSoftwareTests`, to discover, execute, and +report automated tests. Fitness for intended use is verified by the normal project test flow: +`build.ps1` runs `dotnet test`, and the `build` matrix job of `build.yaml` runs `dotnet test` +across Windows, Linux, and macOS with TRX logging enabled through +`--logger "trx;LogFilePrefix=${{ matrix.os }}" --results-directory artifacts`. Because xUnit is +the framework responsible for finding and running these tests, successful execution of the suite +constitutes direct self-validation of the integration, and the generated TRX files provide evidence +for downstream requirements tracing. No project-specific issues have been observed in this +validated execution and reporting path. + +### Test Scenarios + +**xUnitTestExecution**: xUnit discovers and executes representative ReviewMark tests that exercise +argument parsing, file output, and exception reporting, demonstrating that the framework runs the +project's normal unit and integration workload correctly. This scenario is tested by +`Context_Create_NoArguments_ReturnsDefaultContext`, +`Context_Create_VersionFlag_SetsVersionTrue`, `Context_Create_HelpFlag_SetsHelpTrue`, +`Context_Create_SilentFlag_SetsSilentTrue`, `Context_Create_ValidateFlag_SetsValidateTrue`, +`Context_Create_ResultsFlag_SetsResultsFile`, `Context_Create_LogFlag_OpensLogFile`, +`Context_Create_UnknownArgument_ThrowsArgumentException`, and +`Context_Create_ShortVersionFlag_SetsVersionTrue`. + +**xUnitTrxReporting**: The same executed tests are emitted as TRX results during CI runs so the +build pipeline and ReqStream can consume consistent machine-readable evidence without extra +reporting glue code. This scenario is tested by `Context_Create_NoArguments_ReturnsDefaultContext`, +`Context_Create_VersionFlag_SetsVersionTrue`, `Context_Create_HelpFlag_SetsHelpTrue`, +`Context_Create_SilentFlag_SetsSilentTrue`, `Context_Create_ValidateFlag_SetsValidateTrue`, +`Context_Create_ResultsFlag_SetsResultsFile`, `Context_Create_LogFlag_OpensLogFile`, +`Context_Create_UnknownArgument_ThrowsArgumentException`, and +`Context_Create_ShortVersionFlag_SetsVersionTrue`. + +### Requirements Coverage + +- **ReviewMark-OTS-xUnit-Execute**: xUnit shall execute unit tests. + - *xUnitTestExecution* + - `Context_Create_NoArguments_ReturnsDefaultContext` + - `Context_Create_VersionFlag_SetsVersionTrue` + - `Context_Create_HelpFlag_SetsHelpTrue` + - `Context_Create_SilentFlag_SetsSilentTrue` + - `Context_Create_ValidateFlag_SetsValidateTrue` + - `Context_Create_ResultsFlag_SetsResultsFile` + - `Context_Create_LogFlag_OpensLogFile` + - `Context_Create_UnknownArgument_ThrowsArgumentException` + - `Context_Create_ShortVersionFlag_SetsVersionTrue` +- **ReviewMark-OTS-xUnit-Report**: xUnit shall report test results in TRX format. + - *xUnitTrxReporting* + - `Context_Create_NoArguments_ReturnsDefaultContext` + - `Context_Create_VersionFlag_SetsVersionTrue` + - `Context_Create_HelpFlag_SetsHelpTrue` + - `Context_Create_SilentFlag_SetsSilentTrue` + - `Context_Create_ValidateFlag_SetsValidateTrue` + - `Context_Create_ResultsFlag_SetsResultsFile` + - `Context_Create_LogFlag_OpensLogFile` + - `Context_Create_UnknownArgument_ThrowsArgumentException` + - `Context_Create_ShortVersionFlag_SetsVersionTrue` diff --git a/docs/verification/ots/yamldotnet.md b/docs/verification/ots/yamldotnet.md new file mode 100644 index 0000000..94ee4ed --- /dev/null +++ b/docs/verification/ots/yamldotnet.md @@ -0,0 +1,57 @@ +## YamlDotNet + +### Verification Approach + +ReviewMark uses YamlDotNet 17.1.0, referenced from `DemaConsulting.ReviewMark.csproj`, to +deserialize `.reviewmark.yaml` content in the Configuration subsystem. The integration surface is +`ReviewMarkConfigurationHelpers.DeserializeRaw()`, which builds a deserializer with +`NullNamingConvention`, `IgnoreUnmatchedProperties()`, and `YamlMember` aliases so hyphenated YAML +keys such as `needs-review` and `evidence-source` bind to the raw model. Malformed YAML is caught +as `YamlException` and converted into user-visible parse diagnostics. Fitness for intended use is +verified by dedicated OTS tests in `test/OtsSoftwareTests/YamlDotNetTests.cs`, companion +configuration tests in `test/DemaConsulting.ReviewMark.Tests/Configuration/ReviewMarkConfigurationTests.cs`, +and the `dotnet test` step in the `build` matrix job of `build.yaml`, which publishes TRX evidence +to `artifacts/`. No project-specific issues have been observed in this validated integration +surface. + +### Test Scenarios + +**YamlDotNetDeserialization**: Well-formed `.reviewmark.yaml` content is deserialized into the raw +configuration model, including list values, nested objects, and aliased keys, so the Configuration +subsystem can build a usable `ReviewMarkConfiguration`. This scenario is tested by +`Deserializer_Deserialize_WellFormedYaml_MapsToTypedObject`, +`ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration`, +`ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly`, +`ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly`, +`ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly`, and +`ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly`. + +**YamlDotNetErrorHandling**: Malformed YAML is rejected with a `YamlException`, and ReviewMark +turns that failure into a deterministic configuration issue instead of accepting invalid input. +This scenario is tested by `Deserializer_Deserialize_MalformedYaml_ThrowsYamlException` and +`ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue`. + +**YamlDotNetUnknownKeys**: Unknown YAML keys are ignored when forward-compatible configuration +extensions are present, allowing older ReviewMark versions to continue parsing known fields +correctly. This scenario is tested by `Deserializer_Deserialize_UnknownKeys_DoesNotThrow`. + +### Requirements Coverage + +- **ReviewMark-OTS-YamlDotNet-Deserialize**: YamlDotNet shall deserialize `.reviewmark.yaml` + configuration files into typed C# objects. + - *YamlDotNetDeserialization* + - `Deserializer_Deserialize_WellFormedYaml_MapsToTypedObject` + - `ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration` + - `ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly` + - `ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly` + - `ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly` + - `ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly` +- **ReviewMark-OTS-YamlDotNet-ErrorHandling**: YamlDotNet shall raise a `YamlException` on + malformed YAML input. + - *YamlDotNetErrorHandling* + - `Deserializer_Deserialize_MalformedYaml_ThrowsYamlException` + - `ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue` +- **ReviewMark-OTS-YamlDotNet-UnknownKeys**: YamlDotNet shall silently ignore unrecognized YAML + keys without raising an error. + - *YamlDotNetUnknownKeys* + - `Deserializer_Deserialize_UnknownKeys_DoesNotThrow` diff --git a/docs/verification/review-mark.md b/docs/verification/review-mark.md index c79fcf6..a7b7527 100644 --- a/docs/verification/review-mark.md +++ b/docs/verification/review-mark.md @@ -1,191 +1,182 @@ # ReviewMark -## Verification Approach - -ReviewMark is verified at the system level through a set of integration tests in -`IntegrationTests.cs` that exercise the full CLI pipeline by launching the ReviewMark -DLL as a subprocess via `dotnet` and asserting on exit codes and console output. -The `Runner.cs` helper captures combined stdout/stderr from the subprocess, allowing -tests to assert on both normal output and error messages. - -The integration tests exercise all major system-level operations: version display, -help display, self-validation, silent mode, logging, review plan generation, review -report generation, enforce mode, index scanning, working directory override, review -set elaboration, lint mode, depth flags, results file generation, and error handling -for unknown arguments. - -## Dependencies - -| Mock / Stub | Reason | -| ---------------------- | --------------------------------------------------------------- | -| Temporary YAML files | Created in-process to provide controlled definition inputs | -| Temporary directories | Isolated filesystem state prevents test interference | -| `Runner.Run` | Runs DLL as subprocess; captures stdout/stderr for assertion | - -## Test Scenarios (System-Level) - -### ReviewMark_VersionFlag_Invoked_OutputsVersion - -**Scenario**: The tool is invoked with `--version`. - -**Expected**: Exit code is 0; output is non-empty; output does not contain "Error" or -"Copyright". - -**Requirement coverage**: `ReviewMark-System-Version` - -### ReviewMark_HelpFlag_Invoked_OutputsUsageInformation - -**Scenario**: The tool is invoked with `--help`. - -**Expected**: Exit code is 0; output contains "Usage:", "Options:", and "--version". - -**Requirement coverage**: `ReviewMark-System-Help` - -### ReviewMark_ValidateFlag_Invoked_RunsValidation - -**Scenario**: The tool is invoked with `--validate`. - -**Expected**: Exit code is 0; output contains "Total Tests:" and "Passed:". - -**Requirement coverage**: `ReviewMark-System-Validate` - -### ReviewMark_ValidateFlag_WithTrxResultsPath_GeneratesTrxFile - -**Scenario**: The tool is invoked with `--validate --results <file>.trx`. - -**Expected**: Exit code is 0; results file is created; file contains `<TestRun` and -`</TestRun>`. - -**Requirement coverage**: `ReviewMark-System-Results` - -### ReviewMark_ValidateFlag_WithXmlResultsPath_GeneratesJUnitFile - -**Scenario**: The tool is invoked with `--validate --results <file>.xml`. - -**Expected**: Exit code is 0; results file is created; file contains `<testsuites`. - -**Requirement coverage**: `ReviewMark-System-Results` - -### ReviewMark_SilentFlag_Invoked_SuppressesOutput - -**Scenario**: The tool is invoked with `--silent`. - -**Expected**: Exit code is 0; console output is empty. - -**Requirement coverage**: `ReviewMark-System-Silent` - -### ReviewMark_LogFlag_Invoked_WritesOutputToFile - -**Scenario**: The tool is invoked with `--log <file>`. - -**Expected**: Exit code is 0; log file is created; log file contains "ReviewMark version". - -**Requirement coverage**: `ReviewMark-System-Log` - -### ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError - -**Scenario**: The tool is invoked with `--unknown`. - -**Expected**: Exit code is non-zero; output contains "Error". - -**Requirement coverage**: `ReviewMark-System-InvalidArgs` - -### ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan - -**Scenario**: The tool is invoked with `--definition <file> --plan <planfile>` using -a temporary definition file with one review set. - -**Expected**: Exit code is 0; plan file is created; plan file contains the review set ID. - -**Requirement coverage**: `ReviewMark-System-ReviewPlan`, `ReviewMark-System-Definition` - -### ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport - -**Scenario**: The tool is invoked with `--definition <file> --report <reportfile>` using -a temporary definition file with one review set. - -**Expected**: Exit code is 0; report file is created; report file contains the review set ID. - -**Requirement coverage**: `ReviewMark-System-ReviewReport`, `ReviewMark-System-Definition` - -### ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero - -**Scenario**: The tool is invoked with `--definition <file> --report <reportfile> --enforce` -where the evidence source is `type: none`. - -**Expected**: Exit code is non-zero because no reviews are current against a `none` evidence source. - -**Requirement coverage**: `ReviewMark-System-Enforce` - -### ReviewMark_IndexFlag_OnEmptyDirectory_CreatesIndexJson - -**Scenario**: The tool is invoked with `--dir <tmpdir> --index <tmpdir>/**/*.pdf` against -an empty temporary directory. - -**Expected**: Exit code is 0; `index.json` is created in the temporary directory. - -**Requirement coverage**: `ReviewMark-System-IndexScan` - -### ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory - -**Scenario**: The tool is invoked with `--dir <tmpdir> --plan <planfile>` where `<tmpdir>` -contains a `.reviewmark.yaml` definition file. - -**Expected**: Exit code is 0; plan file is created; ReviewMark resolves the definition file -relative to the overridden working directory. - -**Requirement coverage**: `ReviewMark-System-WorkingDirectory`, `ReviewMark-System-ReviewPlan` - -### ReviewMark_ElaborateFlag_WithValidId_OutputsElaboration - -**Scenario**: The tool is invoked with `--definition <file> --elaborate Test-Review` where -the definition file defines a review set named `Test-Review`. - -**Expected**: Exit code is 0; output contains `Test-Review`. - -**Requirement coverage**: `ReviewMark-System-Elaborate` - -### ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth - -**Scenario**: The tool is invoked with `--definition <file> --plan <planfile> --report <reportfile> --depth 2`. - -**Expected**: Exit code is 0; plan file contains `## Review Coverage`; report file contains `## Review Status`. - -**Requirement coverage**: `ReviewMark-System-Depth` - -### ReviewMark_DepthFlag_WithValidate_SetsValidationHeadingDepth - -**Scenario**: The tool is invoked with `--validate --depth 2`. - -**Expected**: Exit code is 0; output contains `## DEMA Consulting ReviewMark`. - -**Requirement coverage**: `ReviewMark-System-Depth` - -### ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput - -**Scenario**: The tool is invoked with `--definition <file> --lint` using a valid definition file. - -**Expected**: Exit code is 0; output is empty (no issues, no banner in lint mode). - -**Requirement coverage**: `ReviewMark-System-LintValidation`, `ReviewMark-System-LintSilenceOnSuccess` +## Verification Strategy + +ReviewMark is verified primarily through system integration tests in +`test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs`. These tests launch the built +`DemaConsulting.ReviewMark.dll` through `dotnet`, exercise the public CLI end to end, and assert +on exit codes, generated files, and console output. Supporting unit tests are also used where a +system requirement depends on a specific internal contract, such as authenticated URL evidence +source configuration. + +System-boundary test doubles are limited to temporary YAML definition files, temporary directories, +and transient output files. No external services or persistent evidence stores are required. The +same verification suite is also executed in the GitHub Actions build matrix across Windows, Linux, +and macOS and across .NET 8, .NET 9, and .NET 10. + +## Test Environment + +ReviewMark system verification runs under xUnit with Release builds of +`DemaConsulting.ReviewMark.dll`. Tests require the .NET SDK/runtime, local file-system access, and +write access to temporary directories for definitions, logs, reports, results files, and index +files. No network connectivity or external service dependencies are required for the mapped +scenarios. + +Test parallelization is disabled in `test/DemaConsulting.ReviewMark.Tests/AssemblyInfo.cs` +because multiple scenarios launch `dotnet` subprocesses and manipulate transient files in the same +test run. + +## Acceptance Criteria + +- All mapped automated tests pass with zero failures. +- Every `ReviewMark-System-*` requirement is mapped to at least one named scenario and one passing + test method. +- Valid CLI invocations return exit code 0, while invalid input and enforcement failures return a + non-zero exit code. +- Generated plan, report, results, log, and index artifacts are created with the expected content + when their corresponding options are used. +- Cross-platform verification completes successfully in CI across Windows, Linux, and macOS and + across .NET 8, .NET 9, and .NET 10. + +## System-Level Test Scenarios + +**VersionDisplay**: ReviewMark writes a version string when invoked with `--version`, proving a +valid execution context can be created and the CLI can dispatch an immediate success path. This +scenario is tested by `ReviewMark_VersionFlag_Invoked_OutputsVersion`. + +**HelpDisplay**: ReviewMark writes usage and option information when invoked with `--help`, +proving the CLI exposes operator guidance without requiring a definition file. This scenario is +tested by `ReviewMark_HelpFlag_Invoked_OutputsUsageInformation`. + +**SelfValidationExecution**: ReviewMark runs its built-in self-test suite when invoked with +`--validate`, emits a validation summary, and exits successfully when all checks pass. This +scenario is tested by `ReviewMark_ValidateFlag_Invoked_RunsValidation`. + +**ValidationResultsExport**: ReviewMark writes validation results in both TRX and JUnit XML when +`--results` is supplied, proving compatibility with downstream CI and traceability tooling. This +scenario is tested by `ReviewMark_ValidateFlag_WithTrxResultsPath_GeneratesTrxFile` and +`ReviewMark_ValidateFlag_WithXmlResultsPath_GeneratesJUnitFile`. + +**SilentAndLoggedOutput**: ReviewMark can suppress console output with `--silent` and duplicate +normal output to a persistent log file with `--log`, preserving execution semantics while changing +output channels. This scenario is tested by `ReviewMark_SilentFlag_Invoked_SuppressesOutput` and +`ReviewMark_LogFlag_Invoked_WritesOutputToFile`. + +**InvalidArgumentHandling**: ReviewMark rejects unknown command-line arguments with an error +message and a non-zero exit code, proving invalid invocations do not proceed with undefined +behavior. This scenario is tested by `ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError`. + +**ReviewPlanGeneration**: ReviewMark loads a definition file, resolves review-sets, and writes a +review plan containing the expected review-set identifiers. This scenario is tested by +`ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan`. + +**ReviewReportGeneration**: ReviewMark loads a definition file, evaluates evidence status, and +writes a review report containing the expected review-set identifiers. This scenario is tested by +`ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport`. + +**EnforcementFailureOnNonCurrentReview**: ReviewMark exits non-zero when `--enforce` is used and +no current evidence exists, proving review compliance can gate automated workflows. This scenario +is tested by `ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero`. + +**IndexGeneration**: ReviewMark scans configured PDF glob paths and writes `index.json`, even when +no evidence PDFs are found, proving the index-generation workflow completes deterministically. This +scenario is tested by `ReviewMark_IndexFlag_OnEmptyDirectory_CreatesIndexJson` and +`ReviewMark_IndexFlag_WithRepeat_ScansAllPaths`. + +**WorkingDirectoryOverride**: ReviewMark resolves its default definition file relative to the +`--dir` argument, allowing scripted execution against repositories outside the current process +working directory. This scenario is tested by `ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory`. + +**ReviewSetElaboration**: ReviewMark prints a Markdown elaboration for a named review-set, +including the selected review-set identifier. This scenario is tested by +`ReviewMark_ElaborateFlag_WithValidId_OutputsElaboration`. + +**HeadingDepthControl**: ReviewMark applies `--depth`, `--plan-depth`, and `--report-depth` to +Markdown output so plan, report, and validation output can be embedded at the required outline +level. This scenario is tested by `ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth`, +`ReviewMark_PlanDepthFlag_Invoked_OverridesPlanHeadingOnly`, +`ReviewMark_ReportDepthFlag_Invoked_OverridesReportHeadingOnly`, and +`ReviewMark_DepthFlag_WithValidate_SetsValidationHeadingDepth`. + +**LintValidation**: ReviewMark validates the definition file in lint mode, stays silent on +success, reports only issue messages on failure, and suppresses the normal version banner in both +cases. This scenario is tested by `ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput` and +`ReviewMark_LintFlag_WithInvalidConfig_ReportsOnlyIssueMessages`. + +**AuthenticatedUrlEvidenceSourceConfiguration**: ReviewMark accepts `username-env` and +`password-env` entries in the `evidence-source.credentials` block so authenticated URL evidence +sources can be configured without storing secrets in YAML. This scenario is tested by +`ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly`. + +**ExecutionContextManagement**: ReviewMark parses arguments into a stable execution context and +maintains consistent exit-code and output behavior from startup through error handling. This +scenario is tested by `ReviewMark_VersionFlag_Invoked_OutputsVersion` and +`ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError`. + +**CrossPlatformRuntimeMatrix**: ReviewMark verification is repeated in the GitHub Actions +`integration-test` job across Windows, Linux, and macOS and across .NET 8, .NET 9, and .NET 10, +proving the packaged tool remains operational across supported platforms and runtimes. This +scenario is tested by `ReviewMark_VersionFlag_Invoked_OutputsVersion` and +`ReviewMark_ValidateFlag_Invoked_RunsValidation` in the CI matrix defined in +`.github/workflows/build.yaml`. + +**InvalidLogPathHandling**: ReviewMark returns a non-zero exit code and surfaces an error when +`--log` points to a path that cannot be written, proving output-channel setup failures are handled +deterministically. This scenario is tested by `ReviewMark_LogFlag_WithInvalidPath_ReturnsNonZero`. ## Requirements Coverage -- **ReviewMark-System-Version**: ReviewMark_VersionFlag_Invoked_OutputsVersion -- **ReviewMark-System-Help**: ReviewMark_HelpFlag_Invoked_OutputsUsageInformation -- **ReviewMark-System-Validate**: ReviewMark_ValidateFlag_Invoked_RunsValidation -- **ReviewMark-System-Results**: ReviewMark_ValidateFlag_WithTrxResultsPath_GeneratesTrxFile, - ReviewMark_ValidateFlag_WithXmlResultsPath_GeneratesJUnitFile -- **ReviewMark-System-Silent**: ReviewMark_SilentFlag_Invoked_SuppressesOutput -- **ReviewMark-System-Log**: ReviewMark_LogFlag_Invoked_WritesOutputToFile -- **ReviewMark-System-InvalidArgs**: ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError -- **ReviewMark-System-ReviewPlan**: ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan, ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory -- **ReviewMark-System-ReviewReport**: ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport -- **ReviewMark-System-Enforce**: ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero -- **ReviewMark-System-IndexScan**: ReviewMark_IndexFlag_OnEmptyDirectory_CreatesIndexJson -- **ReviewMark-System-WorkingDirectory**: ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory -- **ReviewMark-System-Elaborate**: ReviewMark_ElaborateFlag_WithValidId_OutputsElaboration -- **ReviewMark-System-Depth**: ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth, ReviewMark_DepthFlag_WithValidate_SetsValidationHeadingDepth -- **ReviewMark-System-LintValidation**: ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput -- **ReviewMark-System-LintSilenceOnSuccess**: ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput -- **ReviewMark-System-Definition**: ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan, ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport +- **ReviewMark-System-ReviewPlan**: *ReviewPlanGeneration* - verified by + `ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan`; *WorkingDirectoryOverride* - + verified by `ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory`. +- **ReviewMark-System-ReviewReport**: *ReviewReportGeneration* - verified by + `ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport`. +- **ReviewMark-System-Enforce**: *EnforcementFailureOnNonCurrentReview* - verified by + `ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero`. +- **ReviewMark-System-Credentials**: *AuthenticatedUrlEvidenceSourceConfiguration* - verified by + `ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly`. +- **ReviewMark-System-IndexScan**: *IndexGeneration* - verified by + `ReviewMark_IndexFlag_OnEmptyDirectory_CreatesIndexJson`. +- **ReviewMark-System-IndexScan-Repeat**: *IndexGeneration* - verified by + `ReviewMark_IndexFlag_WithRepeat_ScansAllPaths`. +- **ReviewMark-System-Validate**: *SelfValidationExecution* - verified by + `ReviewMark_ValidateFlag_Invoked_RunsValidation`. +- **ReviewMark-System-Version**: *VersionDisplay* - verified by + `ReviewMark_VersionFlag_Invoked_OutputsVersion`. +- **ReviewMark-System-Help**: *HelpDisplay* - verified by + `ReviewMark_HelpFlag_Invoked_OutputsUsageInformation`. +- **ReviewMark-System-WorkingDirectory**: *WorkingDirectoryOverride* - verified by + `ReviewMark_DirFlag_Invoked_OverridesWorkingDirectory`. +- **ReviewMark-System-Elaborate**: *ReviewSetElaboration* - verified by + `ReviewMark_ElaborateFlag_WithValidId_OutputsElaboration`. +- **ReviewMark-System-LintValidation**: *LintValidation* - verified by + `ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput` and + `ReviewMark_LintFlag_WithInvalidConfig_ReportsOnlyIssueMessages`. +- **ReviewMark-System-LintSilenceOnSuccess**: *LintValidation* - verified by + `ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput`. +- **ReviewMark-System-Silent**: *SilentAndLoggedOutput* - verified by + `ReviewMark_SilentFlag_Invoked_SuppressesOutput`. +- **ReviewMark-System-Log**: *SilentAndLoggedOutput* - verified by + `ReviewMark_LogFlag_Invoked_WritesOutputToFile`; *InvalidLogPathHandling* - verified by + `ReviewMark_LogFlag_WithInvalidPath_ReturnsNonZero`. +- **ReviewMark-System-Depth**: *HeadingDepthControl* - verified by + `ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth` and + `ReviewMark_DepthFlag_WithValidate_SetsValidationHeadingDepth`. +- **ReviewMark-System-PlanDepth**: *HeadingDepthControl* - verified by + `ReviewMark_PlanDepthFlag_Invoked_OverridesPlanHeadingOnly`. +- **ReviewMark-System-ReportDepth**: *HeadingDepthControl* - verified by + `ReviewMark_ReportDepthFlag_Invoked_OverridesReportHeadingOnly`. +- **ReviewMark-System-InvalidArgs**: *InvalidArgumentHandling* - verified by + `ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError`. +- **ReviewMark-System-Results**: *ValidationResultsExport* - verified by + `ReviewMark_ValidateFlag_WithTrxResultsPath_GeneratesTrxFile` and + `ReviewMark_ValidateFlag_WithXmlResultsPath_GeneratesJUnitFile`. +- **ReviewMark-System-Definition**: *ReviewPlanGeneration* - verified by + `ReviewMark_PlanFlag_WithDefinitionFile_GeneratesReviewPlan`; *ReviewReportGeneration* - + verified by `ReviewMark_ReportFlag_WithDefinitionFile_GeneratesReviewReport`. +- **ReviewMark-System-CrossPlatform**: *CrossPlatformRuntimeMatrix* - verified by + `ReviewMark_VersionFlag_Invoked_OutputsVersion` and + `ReviewMark_ValidateFlag_Invoked_RunsValidation` in the CI integration-test matrix. +- **ReviewMark-System-ExecutionContext**: *ExecutionContextManagement* - verified by + `ReviewMark_VersionFlag_Invoked_OutputsVersion` and + `ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError`. diff --git a/docs/verification/review-mark/cli.md b/docs/verification/review-mark/cli.md index 56958b8..3a7dd81 100644 --- a/docs/verification/review-mark/cli.md +++ b/docs/verification/review-mark/cli.md @@ -2,215 +2,75 @@ ### Verification Approach -The Cli subsystem is verified through `CliTests.cs`, which exercises the `Context` -class and `Program.Run` together with controlled argument arrays and output capture. -Each test targets a specific flag or argument combination and validates the correct -end-to-end behavior including parsing, dispatching, output, and exit code. +Cli subsystem verification uses `CliTests.cs` to exercise `Context.Create`, `Program.Run`, and, for invalid-argument behavior, reflection-based calls to the internal `Program.Main` entry point. The tests use real configuration loading, review plan and report generation, index creation, captured console streams, and temporary files and directories so the subsystem boundary is verified end to end rather than through mocks. -### Dependencies +### Test Environment -| Mock / Stub | Reason | -| --------------- | ------------------------------------------------------------------- | -| `StringWriter` | Captures context output for assertion without console side effects | -| Temporary files | Provide controlled configuration inputs for plan/report operations | +Tests run under xUnit on .NET 8, 9, and 10 across Windows, Linux, and macOS. `StringWriter` instances capture stdout and stderr in-process, temporary YAML files and output files are created for definition-based workflows, and no external services or network access are required. -### Test Scenarios - -#### Cli_VersionFlag_FlagSupplied_OutputsVersionOnly - -**Scenario**: CLI is invoked via `Context.Create(["--version"])` and `Program.Run`. - -**Expected**: Output equals the version string only; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Version` - -#### Cli_HelpFlag_FlagSupplied_OutputsUsageInformation - -**Scenario**: CLI is invoked with `--help`. - -**Expected**: Output contains "Usage:", "Options:", "--version"; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Help` - -#### Cli_SilentFlag_FlagSupplied_SuppressesOutput - -**Scenario**: CLI is invoked with `--silent`. - -**Expected**: Console output is empty; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Silent` - -#### Cli_ValidateFlag_FlagSupplied_RunsValidation - -**Scenario**: CLI is invoked with `--validate`. - -**Expected**: Output contains validation summary; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Validate` - -#### Cli_ResultsFlag_FlagSupplied_GeneratesTrxFile - -**Scenario**: CLI is invoked with `--validate --results <file>.trx`. - -**Expected**: TRX results file is created; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Results` - -#### Cli_LogFlag_FlagSupplied_WritesOutputToFile - -**Scenario**: CLI is invoked with `--log <file>`. - -**Expected**: Log file is created; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Log` - -#### Cli_DepthFlag_FlagSupplied_SetsDefaultHeadingDepth - -**Scenario**: CLI is invoked with `--depth 2 --plan <file>`. - -**Expected**: Generated plan uses level-2 headings; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Depth` - -#### Cli_DepthFlag_BelowMinimum_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--depth", "0"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Depth value below minimum of 1. - -**Requirement coverage**: `ReviewMark-Cmd-Depth`, `ReviewMark-Cmd-PlanDepth`, `ReviewMark-Cmd-ReportDepth` - -#### Cli_DepthFlag_AboveMaximum_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--depth", "6"]`. - -**Expected**: `ArgumentException` is thrown. +### Acceptance Criteria -**Boundary / error path**: Depth value above maximum of 5. +- All Cli subsystem integration tests pass with zero failures. +- Each `ReviewMark-Cmd-*` requirement is traced to at least one scenario and test method. +- Normal, boundary, and error-path invocations produce the expected output, generated artifacts, and exit codes. -**Requirement coverage**: `ReviewMark-Cmd-Depth`, `ReviewMark-Cmd-PlanDepth`, `ReviewMark-Cmd-ReportDepth` - -#### Cli_ErrorOutput_UnknownArg_WritesToStderr - -**Scenario**: CLI is invoked with `--unknown-arg-xyz`. - -**Expected**: Error message appears on stderr; exit code is non-zero. - -**Requirement coverage**: `ReviewMark-Cmd-ErrorOutput` - -#### Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode - -**Scenario**: CLI is invoked with an unknown argument. - -**Expected**: Exit code is non-zero. - -**Requirement coverage**: `ReviewMark-Cmd-InvalidArgs` - -#### Cli_DefinitionFlag_FlagSupplied_LoadsSpecifiedFile - -**Scenario**: CLI is invoked with `--definition <file> --plan <file>`. - -**Expected**: Plan file is created using the specified definition; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Definition` - -#### Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan - -**Scenario**: CLI is invoked with `--definition <file> --plan <file>`. - -**Expected**: Plan file exists and contains review-set ID; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Plan` - -#### Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth - -**Scenario**: CLI is invoked with `--plan-depth 2` along with `--plan <file>`. - -**Expected**: Plan file contains `## Review Coverage` (depth 2 heading); exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-PlanDepth` - -#### Cli_ReportFlag_FlagSupplied_GeneratesReviewReport - -**Scenario**: CLI is invoked with `--definition <file> --report <file>`. - -**Expected**: Report file exists and contains review-set ID; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Report` - -#### Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth - -**Scenario**: CLI is invoked with `--report-depth 2` along with `--report <file>`. - -**Expected**: Report file contains `## Review Status` (depth 2 heading); exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-ReportDepth` - -#### Cli_IndexFlag_FlagSupplied_CreatesIndexJson - -**Scenario**: CLI is invoked with `--dir <tmpDir> --index <glob>` where tmpDir contains a valid config. - -**Expected**: `index.json` is created in the directory; exit code is 0. - -**Requirement coverage**: `ReviewMark-Cmd-Index` +### Test Scenarios -#### Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent +**Cli_VersionFlag_FlagSupplied_OutputsVersionOnly**: CLI is invoked via `Context.Create(["--version"])` and `Program.Run`. Expected outcome: Output equals the version string only; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Version`. This scenario is tested by `Cli_VersionFlag_FlagSupplied_OutputsVersionOnly`. -**Scenario**: CLI is invoked with `--enforce` and the evidence source is `none`. +**Cli_HelpFlag_FlagSupplied_OutputsUsageInformation**: CLI is invoked with `--help`. Expected outcome: Output contains "Usage:", "Options:", "--version"; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Help`. This scenario is tested by `Cli_HelpFlag_FlagSupplied_OutputsUsageInformation`. -**Expected**: Exit code is non-zero because reviews are in Missing state. +**Cli_SilentFlag_FlagSupplied_SuppressesOutput**: CLI is invoked with `--silent`. Expected outcome: Console output is empty; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Silent`. This scenario is tested by `Cli_SilentFlag_FlagSupplied_SuppressesOutput`. -**Requirement coverage**: `ReviewMark-Cmd-Enforce` +**Cli_ValidateFlag_FlagSupplied_RunsValidation**: CLI is invoked with `--validate`. Expected outcome: Output contains validation summary; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Validate`. This scenario is tested by `Cli_ValidateFlag_FlagSupplied_RunsValidation`. -#### Cli_DirFlag_FlagSupplied_SetsWorkingDirectory +**Cli_ResultsFlag_FlagSupplied_GeneratesTrxFile**: CLI is invoked with `--validate --results <file>.trx`. Expected outcome: TRX results file is created; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Results`. This scenario is tested by `Cli_ResultsFlag_FlagSupplied_GeneratesTrxFile`. -**Scenario**: CLI is invoked with `--dir <tmpDir>` where tmpDir contains `.reviewmark.yaml`, plus `--plan <file>`. +**Cli_LogFlag_FlagSupplied_WritesOutputToFile**: CLI is invoked with `--log <file>`. Expected outcome: Log file is created; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Log`. This scenario is tested by `Cli_LogFlag_FlagSupplied_WritesOutputToFile`. -**Expected**: Plan is created from directory-relative config; exit code is 0. +**Cli_DepthFlag_FlagSupplied_SetsDefaultHeadingDepth**: CLI is invoked with `--depth 2 --plan <file>`. Expected outcome: Generated plan uses level-2 headings; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Depth`. This scenario is tested by `Cli_DepthFlag_FlagSupplied_SetsDefaultHeadingDepth`. -**Requirement coverage**: `ReviewMark-Cmd-Dir` +**Cli_DepthFlag_BelowMinimum_ThrowsArgumentException**: `Context.Create` is called with `["--depth", "0"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Depth value below minimum of 1. Requirement coverage: `ReviewMark-Cmd-Depth`. This scenario is tested by `Cli_DepthFlag_BelowMinimum_ThrowsArgumentException`. -#### Cli_ElaborateFlag_ValidId_OutputsElaboration +**Cli_DepthFlag_AboveMaximum_ThrowsArgumentException**: `Context.Create` is called with `["--depth", "6"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Depth value above maximum of 5. Requirement coverage: `ReviewMark-Cmd-Depth`. This scenario is tested by `Cli_DepthFlag_AboveMaximum_ThrowsArgumentException`. -**Scenario**: CLI is invoked with `--elaborate <review-set-id>`. +**Cli_PlanDepthFlag_BelowMinimum_ThrowsArgumentException**: `Context.Create` is called with `["--plan-depth", "0"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Plan-depth value below minimum of 1. Requirement coverage: `ReviewMark-Cmd-PlanDepth`. This scenario is tested by `Cli_PlanDepthFlag_BelowMinimum_ThrowsArgumentException`. -**Expected**: Output contains the review-set ID; exit code is 0. +**Cli_PlanDepthFlag_AboveMaximum_ThrowsArgumentException**: `Context.Create` is called with `["--plan-depth", "6"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Plan-depth value above maximum of 5. Requirement coverage: `ReviewMark-Cmd-PlanDepth`. This scenario is tested by `Cli_PlanDepthFlag_AboveMaximum_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Cmd-Elaborate` +**Cli_ReportDepthFlag_BelowMinimum_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth", "0"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Report-depth value below minimum of 1. Requirement coverage: `ReviewMark-Cmd-ReportDepth`. This scenario is tested by `Cli_ReportDepthFlag_BelowMinimum_ThrowsArgumentException`. -#### Cli_LintFlag_ValidConfig_ReportsSuccess +**Cli_ReportDepthFlag_AboveMaximum_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth", "6"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Report-depth value above maximum of 5. Requirement coverage: `ReviewMark-Cmd-ReportDepth`. This scenario is tested by `Cli_ReportDepthFlag_AboveMaximum_ThrowsArgumentException`. -**Scenario**: CLI is invoked with `--lint` on a valid definition file. +**Cli_ErrorOutput_UnknownArg_WritesToStderr**: CLI is invoked with `--unknown-arg-xyz`. Expected outcome: Error message appears on stderr; exit code is non-zero. Requirement coverage: `ReviewMark-Cmd-ErrorOutput`. Note: `Program.Main` is invoked via reflection because it is the only code path that catches `ArgumentException` and writes the error message to stderr. This test and `Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode` share the same `UnknownArgArray` input but test distinct observable behaviors: stderr output vs exit code. This scenario is tested by `Cli_ErrorOutput_UnknownArg_WritesToStderr`. -**Expected**: No output (silence on success); exit code is 0. +**Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode**: CLI is invoked with an unknown argument. Expected outcome: Exit code is non-zero. Requirement coverage: `ReviewMark-Cmd-InvalidArgs`. Note: `Program.Main` is invoked via reflection because it is the only code path that catches `ArgumentException` and returns the non-zero exit code. This test shares the same `UnknownArgArray` input as `Cli_ErrorOutput_UnknownArg_WritesToStderr` but tests the distinct observable behavior of exit code rather than stderr output. This scenario is tested by `Cli_InvalidArgs_UnknownArgSupplied_ReturnsNonZeroExitCode`. -**Requirement coverage**: `ReviewMark-Cmd-Lint` +**Cli_DefinitionFlag_FlagSupplied_LoadsSpecifiedFile**: CLI is invoked with `--definition <file> --plan <file>`. Expected outcome: Plan file is created using the specified definition; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Definition`. This scenario is tested by `Cli_DefinitionFlag_FlagSupplied_LoadsSpecifiedFile`. -#### Cli_LintFlag_InvalidConfig_ReportsIssueMessages +**Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan**: CLI is invoked with `--definition <file> --plan <file>`. Expected outcome: Plan file exists and contains review-set ID; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Plan`. This scenario is tested by `Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan`. -**Scenario**: CLI is invoked with `--lint` on a definition file missing `evidence-source`. +**Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth**: CLI is invoked with `--plan-depth 2` along with `--plan <file>`. Expected outcome: Plan file contains `## Review Coverage` (depth 2 heading); exit code is 0. Requirement coverage: `ReviewMark-Cmd-PlanDepth`. This scenario is tested by `Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth`. -**Expected**: Issue messages appear in error output; exit code is non-zero. +**Cli_ReportFlag_FlagSupplied_GeneratesReviewReport**: CLI is invoked with `--definition <file> --report <file>`. Expected outcome: Report file exists and contains review-set ID; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Report`. This scenario is tested by `Cli_ReportFlag_FlagSupplied_GeneratesReviewReport`. -**Requirement coverage**: `ReviewMark-Cmd-Lint` +**Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth**: CLI is invoked with `--report-depth 2` along with `--report <file>`. Expected outcome: Report file contains `## Review Status` (depth 2 heading); exit code is 0. Requirement coverage: `ReviewMark-Cmd-ReportDepth`. This scenario is tested by `Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth`. -#### Cli_Context_NoArgs_Parsed +**Cli_IndexFlag_FlagSupplied_CreatesIndexJson**: CLI is invoked with `--dir <tmpDir> --index <glob>` where tmpDir contains a valid config. Expected outcome: `index.json` is created in the directory; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Index`. This scenario is tested by `Cli_IndexFlag_FlagSupplied_CreatesIndexJson`. -**Scenario**: Context is created with no arguments (default values). +**Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent**: CLI is invoked with `--enforce` and the evidence source is `none`. Expected outcome: Exit code is non-zero because reviews are in Missing state. Requirement coverage: `ReviewMark-Cmd-Enforce`. This scenario is tested by `Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent`. -**Expected**: All default values are set correctly (Help=false, Silent=false, etc.). +**Cli_DirFlag_FlagSupplied_SetsWorkingDirectory**: CLI is invoked with `--dir <tmpDir>` where tmpDir contains `.reviewmark.yaml`, plus `--plan <file>`. Expected outcome: Plan is created from directory-relative config; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Dir`. This scenario is tested by `Cli_DirFlag_FlagSupplied_SetsWorkingDirectory`. -**Requirement coverage**: `ReviewMark-Cmd-Context` +**Cli_ElaborateFlag_ValidId_OutputsElaboration**: CLI is invoked with `--elaborate <review-set-id>`. Expected outcome: Output contains the review-set ID; exit code is 0. Requirement coverage: `ReviewMark-Cmd-Elaborate`. This scenario is tested by `Cli_ElaborateFlag_ValidId_OutputsElaboration`. -#### Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode +**Cli_LintFlag_ValidConfig_ReportsSuccess**: CLI is invoked with `--lint` on a valid definition file. Expected outcome: No output (silence on success); exit code is 0. Requirement coverage: `ReviewMark-Cmd-LintSilence`. This scenario is tested by `Cli_LintFlag_ValidConfig_ReportsSuccess`. -**Scenario**: CLI is invoked with an invalid argument. +**Cli_LintFlag_InvalidConfig_ReportsIssueMessages**: CLI is invoked with `--lint` on a definition file missing `evidence-source`. Expected outcome: Issue messages appear in error output; exit code is non-zero. Requirement coverage: `ReviewMark-Cmd-Lint`. This scenario is tested by `Cli_LintFlag_InvalidConfig_ReportsIssueMessages`. -**Expected**: Exit code is 1. +**Cli_Context_NoArgs_Parsed**: Context is created with no arguments (default values). Expected outcome: All default values are set correctly (Help=false, Silent=false, etc.). Requirement coverage: `ReviewMark-Cmd-Context`. This scenario is tested by `Cli_Context_NoArgs_Parsed`. -**Requirement coverage**: `ReviewMark-Cmd-ExitCode` +**Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode**: A Context is created with no arguments; WriteError() is called directly. Expected: context.ExitCode is non-zero. Expected outcome: Exit code is 1. Requirement coverage: `ReviewMark-Cmd-ExitCode`. This scenario is tested by `Cli_ExitCode_ErrorReported_ReturnsNonZeroExitCode`. ### Requirements Coverage @@ -233,17 +93,16 @@ end-to-end behavior including parsing, dispatching, output, and exit code. - **`ReviewMark-Cmd-Plan`**: `Cli_PlanFlag_FlagSupplied_GeneratesReviewPlan` - **`ReviewMark-Cmd-PlanDepth`**: `Cli_PlanDepthFlag_FlagSupplied_SetsHeadingDepth`, - `Cli_DepthFlag_BelowMinimum_ThrowsArgumentException`, - `Cli_DepthFlag_AboveMaximum_ThrowsArgumentException` + `Cli_PlanDepthFlag_BelowMinimum_ThrowsArgumentException`, + `Cli_PlanDepthFlag_AboveMaximum_ThrowsArgumentException` - **`ReviewMark-Cmd-Report`**: `Cli_ReportFlag_FlagSupplied_GeneratesReviewReport` - **`ReviewMark-Cmd-ReportDepth`**: `Cli_ReportDepthFlag_FlagSupplied_SetsHeadingDepth`, - `Cli_DepthFlag_BelowMinimum_ThrowsArgumentException`, - `Cli_DepthFlag_AboveMaximum_ThrowsArgumentException` + `Cli_ReportDepthFlag_BelowMinimum_ThrowsArgumentException`, + `Cli_ReportDepthFlag_AboveMaximum_ThrowsArgumentException` - **`ReviewMark-Cmd-Index`**: `Cli_IndexFlag_FlagSupplied_CreatesIndexJson` - **`ReviewMark-Cmd-Enforce`**: `Cli_EnforceFlag_FlagSupplied_ExitsNonZeroWhenNotCurrent` - **`ReviewMark-Cmd-Dir`**: `Cli_DirFlag_FlagSupplied_SetsWorkingDirectory` - **`ReviewMark-Cmd-Elaborate`**: `Cli_ElaborateFlag_ValidId_OutputsElaboration` -- **`ReviewMark-Cmd-Lint`**: - `Cli_LintFlag_ValidConfig_ReportsSuccess`, - `Cli_LintFlag_InvalidConfig_ReportsIssueMessages` +- **`ReviewMark-Cmd-Lint`**: `Cli_LintFlag_InvalidConfig_ReportsIssueMessages` +- **`ReviewMark-Cmd-LintSilence`**: `Cli_LintFlag_ValidConfig_ReportsSuccess` diff --git a/docs/verification/review-mark/cli/context.md b/docs/verification/review-mark/cli/context.md index e82ee17..6bed4dd 100644 --- a/docs/verification/review-mark/cli/context.md +++ b/docs/verification/review-mark/cli/context.md @@ -1,545 +1,140 @@ -### Context Verification - -This document describes the unit-level verification design for the `Context` unit. It -defines the test scenarios, dependency usage, and requirement coverage for `Cli/Context.cs`. +### Context #### Verification Approach -`Context` is verified with unit tests in `ContextTests.cs`. Because `Context` depends -only on .NET base class library types (`Console`, `StreamWriter`, `Path`), no mocking or -test doubles are required. Tests call `Context.Create` with controlled argument arrays, -inspect the resulting properties and exit codes, and verify output written to captured streams. - -#### Dependencies - -`Context` has no dependencies on other tool units. All dependencies are real .NET BCL -types; no mocking is needed at this level. - -#### Test Scenarios - -##### Context_Create_NoArguments_ReturnsDefaultContext - -**Scenario**: `Context.Create` is called with an empty argument array. - -**Expected**: All boolean flags are false; exit code is 0. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_VersionFlag_SetsVersionTrue - -**Scenario**: `Context.Create` is called with `["--version"]`. - -**Expected**: `Version` property is true; `Help` is false; exit code is 0. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ShortVersionFlag_SetsVersionTrue - -**Scenario**: `Context.Create` is called with `["-v"]`. - -**Expected**: `Version` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_HelpFlag_SetsHelpTrue - -**Scenario**: `Context.Create` is called with `["--help"]`. - -**Expected**: `Help` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_SilentFlag_SetsSilentTrue - -**Scenario**: `Context.Create` is called with `["--silent"]`. - -**Expected**: `Silent` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ValidateFlag_SetsValidateTrue - -**Scenario**: `Context.Create` is called with `["--validate"]`. - -**Expected**: `Validate` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_UnknownArgument_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--unknown"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Unknown argument rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_WriteLine_NotSilent_WritesToConsole - -**Scenario**: A non-silent `Context` calls `WriteLine`. - -**Expected**: The message appears on standard output. - -**Requirement coverage**: `ReviewMark-Context-Output` - -##### Context_WriteLine_Silent_DoesNotWriteToConsole - -**Scenario**: A silent `Context` calls `WriteLine`. - -**Expected**: Standard output receives nothing. - -**Requirement coverage**: `ReviewMark-Context-Output` - -##### Context_WriteError_NotSilent_WritesToConsole - -**Scenario**: A non-silent `Context` calls `WriteError`. - -**Expected**: The message appears on standard error. - -**Requirement coverage**: `ReviewMark-Context-Output` - -##### Context_WriteError_SetsErrorExitCode - -**Scenario**: A `Context` calls `WriteError`. - -**Expected**: `ExitCode` is 1 after the call. - -**Requirement coverage**: `ReviewMark-Context-Output` - -##### Context_Create_ShortHelpFlag_H_SetsHelpTrue - -**Scenario**: `Context.Create` is called with `["-h"]`. - -**Expected**: `Help` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ShortHelpFlag_Question_SetsHelpTrue - -**Scenario**: `Context.Create` is called with `["-?"]`. - -**Expected**: `Help` property is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ResultsFlag_SetsResultsFile - -**Scenario**: `Context.Create` is called with `["--results", "test.trx"]`. - -**Expected**: `ResultsFile` is set to `"test.trx"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_LogFlag_OpensLogFile - -**Scenario**: `Context.Create` is called with `["--log", "<file>"]` and `WriteLine` is called. - -**Expected**: Log file exists and contains the written message. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_LogFlag_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--log"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--log"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--results"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--results"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ResultAlias_SetsResultsFile - -**Scenario**: `Context.Create` is called with `["--result", "test.trx"]`. - -**Expected**: `ResultsFile` is set to `"test.trx"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ResultAlias_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--result"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--result"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_DefinitionFlag_SetsDefinitionFile - -**Scenario**: `Context.Create` is called with `["--definition", "spec.yaml"]`. - -**Expected**: `DefinitionFile` is set to `"spec.yaml"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_DefinitionFlag_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--definition"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--definition"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanFlag_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--plan"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--plan"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportFlag_WithoutValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--report"]` (no value). - -**Expected**: `ArgumentException` is thrown with message containing `"--report"`. +Context unit verification uses `ContextTests.cs` to construct real contexts from controlled argument arrays and inspect parsed properties, exit-code behavior, console output, and log-file output. Dependencies remain real .NET BCL types, so no subsystem mocks are required; the tests validate the unit directly through `Context.Create`, `WriteLine`, and `WriteError`. -**Boundary / error path**: Missing value rejection. +#### Test Environment -**Requirement coverage**: `ReviewMark-Context-Parsing` +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10, capture console streams with `StringWriter`, and create temporary log files only for the logging scenarios. -##### Context_Create_IndexFlag_WithoutValue_ThrowsArgumentException +#### Acceptance Criteria -**Scenario**: `Context.Create` is called with `["--index"]` (no value). +- All Context unit tests pass with zero failures. +- Each `ReviewMark-Context-*` requirement is traced to at least one scenario and test method. +- Flag parsing, value validation, output routing, and log-file error handling all produce the documented state changes and exceptions. -**Expected**: `ArgumentException` is thrown with message containing `"--index"`. - -**Boundary / error path**: Missing value rejection. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanFlag_SetsPlanFile - -**Scenario**: `Context.Create` is called with `["--plan", "plan.yaml"]`. - -**Expected**: `PlanFile` is set to `"plan.yaml"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanDepthFlag_SetsPlanDepth - -**Scenario**: `Context.Create` is called with `["--plan-depth", "3"]`. - -**Expected**: `PlanDepth` is 3. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanDepthFlag_WithInvalidValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--plan-depth", "not-a-number"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Non-numeric depth value. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanDepthFlag_WithZeroValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--plan-depth", "0"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Zero depth value (must be >= 1). - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportFlag_SetsReportFile - -**Scenario**: `Context.Create` is called with `["--report", "report.md"]`. - -**Expected**: `ReportFile` is set to `"report.md"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportDepthFlag_SetsReportDepth - -**Scenario**: `Context.Create` is called with `["--report-depth", "2"]`. - -**Expected**: `ReportDepth` is 2. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportDepthFlag_NonNumeric_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--report-depth", "abc"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Non-numeric depth value. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportDepthFlag_Zero_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--report-depth", "0"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Zero depth value. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportDepthFlag_MissingValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--report-depth"]` (no value). - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Missing value. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_IndexFlag_AddsIndexPath - -**Scenario**: `Context.Create` is called with `["--index", "*.pdf"]`. - -**Expected**: `IndexPaths` contains `"*.pdf"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_IndexFlag_MultipleTimes_AddsAllPaths - -**Scenario**: `Context.Create` is called with two `--index` flags. - -**Expected**: `IndexPaths` contains both patterns. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_IndexPathsEmpty - -**Scenario**: `Context.Create` is called with no arguments. - -**Expected**: `IndexPaths` is empty. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_PlanDepthDefaultsToOne - -**Scenario**: `Context.Create` is called with no arguments. - -**Expected**: `PlanDepth` is 1. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_ReportDepthDefaultsToOne - -**Scenario**: `Context.Create` is called with no arguments. - -**Expected**: `ReportDepth` is 1. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_EnforceFlag_SetsEnforceTrue - -**Scenario**: `Context.Create` is called with `["--enforce"]`. - -**Expected**: `Enforce` is true. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_EnforceFalse - -**Scenario**: `Context.Create` is called with no arguments. - -**Expected**: `Enforce` is false. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_PlanDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--plan-depth", "6"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Depth exceeds maximum of 5. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ReportDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--report-depth", "6"]`. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Depth exceeds maximum of 5. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_DirFlag_SetsWorkingDirectory - -**Scenario**: `Context.Create` is called with `["--dir", "/evidence"]`. - -**Expected**: `WorkingDirectory` is `"/evidence"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_WorkingDirectoryIsNull - -**Scenario**: `Context.Create` is called with no arguments. - -**Expected**: `WorkingDirectory` is null. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_DirFlag_MissingValue_ThrowsArgumentException - -**Scenario**: `Context.Create` is called with `["--dir"]` (no value). - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Missing value. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_ElaborateFlag_SetsElaborateId - -**Scenario**: `Context.Create` is called with `["--elaborate", "Core-Logic"]`. - -**Expected**: `ElaborateId` is `"Core-Logic"`. - -**Requirement coverage**: `ReviewMark-Context-Parsing` - -##### Context_Create_NoArguments_ElaborateIdIsNull - -**Scenario**: `Context.Create` is called with no arguments. +#### Test Scenarios -**Expected**: `ElaborateId` is null. +**Context_Create_NoArguments_ReturnsDefaultContext**: `Context.Create` is called with an empty argument array. Expected outcome: All boolean flags are false; exit code is 0. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_ReturnsDefaultContext`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_VersionFlag_SetsVersionTrue**: `Context.Create` is called with `["--version"]`. Expected outcome: `Version` property is true; `Help` is false; exit code is 0. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_VersionFlag_SetsVersionTrue`. -##### Context_Create_ElaborateFlag_WithoutValue_ThrowsArgumentException +**Context_Create_ShortVersionFlag_SetsVersionTrue**: `Context.Create` is called with `["-v"]`. Expected outcome: `Version` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ShortVersionFlag_SetsVersionTrue`. -**Scenario**: `Context.Create` is called with `["--elaborate"]` (no value). +**Context_Create_HelpFlag_SetsHelpTrue**: `Context.Create` is called with `["--help"]`. Expected outcome: `Help` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_HelpFlag_SetsHelpTrue`. -**Expected**: `ArgumentException` is thrown. +**Context_Create_SilentFlag_SetsSilentTrue**: `Context.Create` is called with `["--silent"]`. Expected outcome: `Silent` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_SilentFlag_SetsSilentTrue`. -**Boundary / error path**: Missing value. +**Context_Create_ValidateFlag_SetsValidateTrue**: `Context.Create` is called with `["--validate"]`. Expected outcome: `Validate` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ValidateFlag_SetsValidateTrue`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_UnknownArgument_ThrowsArgumentException**: `Context.Create` is called with `["--unknown"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Unknown argument rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_UnknownArgument_ThrowsArgumentException`. -##### Context_Create_LintFlag_SetsLintTrue +**Context_WriteLine_NotSilent_WritesToConsole**: A non-silent `Context` calls `WriteLine`. Expected outcome: The message appears on standard output. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteLine_NotSilent_WritesToConsole`. -**Scenario**: `Context.Create` is called with `["--lint"]`. +**Context_WriteLine_Silent_DoesNotWriteToConsole**: A silent `Context` calls `WriteLine`. Expected outcome: Standard output receives nothing. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteLine_Silent_DoesNotWriteToConsole`. -**Expected**: `Lint` is true; `Version` and `Help` are false. +**Context_WriteError_NotSilent_WritesToConsole**: A non-silent `Context` calls `WriteError`. Expected outcome: The message appears on standard error. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteError_NotSilent_WritesToConsole`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_WriteError_SetsErrorExitCode**: A `Context` calls `WriteError`. Expected outcome: `ExitCode` is 1 after the call. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteError_SetsErrorExitCode`. -##### Context_Create_NoArguments_LintIsFalse +**Context_Create_ShortHelpFlag_H_SetsHelpTrue**: `Context.Create` is called with `["-h"]`. Expected outcome: `Help` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ShortHelpFlag_H_SetsHelpTrue`. -**Scenario**: `Context.Create` is called with no arguments. +**Context_Create_ShortHelpFlag_Question_SetsHelpTrue**: `Context.Create` is called with `["-?"]`. Expected outcome: `Help` property is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ShortHelpFlag_Question_SetsHelpTrue`. -**Expected**: `Lint` is false. +**Context_Create_ResultsFlag_SetsResultsFile**: `Context.Create` is called with `["--results", "test.trx"]`. Expected outcome: `ResultsFile` is set to `"test.trx"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ResultsFlag_SetsResultsFile`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_LogFlag_OpensLogFile**: `Context.Create` is called with `["--log", "<file>"]` and `WriteLine` is called. Expected outcome: Log file exists and contains the written message. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_LogFlag_OpensLogFile`. -##### Context_Create_DepthFlag_SetsDepth +**Context_Create_LogFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--log"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--log"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_LogFlag_WithoutValue_ThrowsArgumentException`. -**Scenario**: `Context.Create` is called with `["--depth", "3"]`. +**Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--results"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--results"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException`. -**Expected**: `Depth`, `PlanDepth`, and `ReportDepth` are all 3. +**Context_Create_ResultAlias_SetsResultsFile**: `Context.Create` is called with `["--result", "test.trx"]`. Expected outcome: `ResultsFile` is set to `"test.trx"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ResultAlias_SetsResultsFile`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_ResultAlias_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--result"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--result"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ResultAlias_WithoutValue_ThrowsArgumentException`. -##### Context_Create_DepthFlag_PlanDepthOverride +**Context_Create_DefinitionFlag_SetsDefinitionFile**: `Context.Create` is called with `["--definition", "spec.yaml"]`. Expected outcome: `DefinitionFile` is set to `"spec.yaml"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DefinitionFlag_SetsDefinitionFile`. -**Scenario**: `Context.Create` is called with `["--depth", "2", "--plan-depth", "4"]`. +**Context_Create_DefinitionFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--definition"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--definition"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DefinitionFlag_WithoutValue_ThrowsArgumentException`. -**Expected**: `Depth` is 2, `PlanDepth` is 4, `ReportDepth` is 2. +**Context_Create_PlanFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--plan"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--plan"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanFlag_WithoutValue_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_ReportFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--report"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--report"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportFlag_WithoutValue_ThrowsArgumentException`. -##### Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException +**Context_Create_IndexFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--index"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--index"`. Boundary or error path: Missing value rejection. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_IndexFlag_WithoutValue_ThrowsArgumentException`. -**Scenario**: `Context.Create` is called with `["--depth", "not-a-number"]`. +**Context_Create_PlanFlag_SetsPlanFile**: `Context.Create` is called with `["--plan", "plan.yaml"]`. Expected outcome: `PlanFile` is set to `"plan.yaml"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanFlag_SetsPlanFile`. -**Expected**: `ArgumentException` is thrown with message containing `"--depth"`. +**Context_Create_PlanDepthFlag_SetsPlanDepth**: `Context.Create` is called with `["--plan-depth", "3"]`. Expected outcome: `PlanDepth` is 3. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanDepthFlag_SetsPlanDepth`. -**Boundary / error path**: Non-numeric depth. +**Context_Create_PlanDepthFlag_WithInvalidValue_ThrowsArgumentException**: `Context.Create` is called with `["--plan-depth", "not-a-number"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Non-numeric depth value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanDepthFlag_WithInvalidValue_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_PlanDepthFlag_WithZeroValue_ThrowsArgumentException**: `Context.Create` is called with `["--plan-depth", "0"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Zero depth value (must be >= 1). Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanDepthFlag_WithZeroValue_ThrowsArgumentException`. -##### Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException +**Context_Create_ReportFlag_SetsReportFile**: `Context.Create` is called with `["--report", "report.md"]`. Expected outcome: `ReportFile` is set to `"report.md"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportFlag_SetsReportFile`. -**Scenario**: `Context.Create` is called with `["--depth", "0"]`. +**Context_Create_ReportDepthFlag_SetsReportDepth**: `Context.Create` is called with `["--report-depth", "2"]`. Expected outcome: `ReportDepth` is 2. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportDepthFlag_SetsReportDepth`. -**Expected**: `ArgumentException` is thrown with message containing `"--depth"`. +**Context_Create_ReportDepthFlag_NonNumeric_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth", "abc"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Non-numeric depth value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportDepthFlag_NonNumeric_ThrowsArgumentException`. -**Boundary / error path**: Zero depth. +**Context_Create_ReportDepthFlag_Zero_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth", "0"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Zero depth value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportDepthFlag_Zero_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_ReportDepthFlag_MissingValue_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth"]` (no value). Expected outcome: `ArgumentException` is thrown. Boundary or error path: Missing value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportDepthFlag_MissingValue_ThrowsArgumentException`. -##### Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException +**Context_Create_IndexFlag_AddsIndexPath**: `Context.Create` is called with `["--index", "*.pdf"]`. Expected outcome: `IndexPaths` contains `"*.pdf"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_IndexFlag_AddsIndexPath`. -**Scenario**: `Context.Create` is called with `["--depth", "6"]`. +**Context_Create_IndexFlag_MultipleTimes_AddsAllPaths**: `Context.Create` is called with two `--index` flags. Expected outcome: `IndexPaths` contains both patterns. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_IndexFlag_MultipleTimes_AddsAllPaths`. -**Expected**: `ArgumentException` is thrown with message containing `"--depth"`. +**Context_Create_NoArguments_IndexPathsEmpty**: `Context.Create` is called with no arguments. Expected outcome: `IndexPaths` is empty. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_IndexPathsEmpty`. -**Boundary / error path**: Depth exceeds maximum of 5. +**Context_Create_NoArguments_PlanDepthDefaultsToOne**: `Context.Create` is called with no arguments. Expected outcome: `PlanDepth` is 1. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_PlanDepthDefaultsToOne`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_NoArguments_ReportDepthDefaultsToOne**: `Context.Create` is called with no arguments. Expected outcome: `ReportDepth` is 1. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_ReportDepthDefaultsToOne`. -##### Context_Create_DepthFlag_MissingValue_ThrowsArgumentException +**Context_Create_EnforceFlag_SetsEnforceTrue**: `Context.Create` is called with `["--enforce"]`. Expected outcome: `Enforce` is true. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_EnforceFlag_SetsEnforceTrue`. -**Scenario**: `Context.Create` is called with `["--depth"]` (no value). +**Context_Create_NoArguments_EnforceFalse**: `Context.Create` is called with no arguments. Expected outcome: `Enforce` is false. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_EnforceFalse`. -**Expected**: `ArgumentException` is thrown with message containing `"--depth"`. +**Context_Create_PlanDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException**: `Context.Create` is called with `["--plan-depth", "6"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Depth exceeds maximum of 5. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_PlanDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException`. -**Boundary / error path**: Missing value. +**Context_Create_ReportDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException**: `Context.Create` is called with `["--report-depth", "6"]`. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Depth exceeds maximum of 5. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ReportDepthFlag_WithValueGreaterThanFive_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_DirFlag_SetsWorkingDirectory**: `Context.Create` is called with `["--dir", "/evidence"]`. Expected outcome: `WorkingDirectory` is `"/evidence"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DirFlag_SetsWorkingDirectory`. -##### Context_Create_DepthFlag_ReportDepthOverride +**Context_Create_NoArguments_WorkingDirectoryIsNull**: `Context.Create` is called with no arguments. Expected outcome: `WorkingDirectory` is null. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_WorkingDirectoryIsNull`. -**Scenario**: `Context.Create` is called with `["--depth", "2", "--report-depth", "4"]`. +**Context_Create_DirFlag_MissingValue_ThrowsArgumentException**: `Context.Create` is called with `["--dir"]` (no value). Expected outcome: `ArgumentException` is thrown. Boundary or error path: Missing value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DirFlag_MissingValue_ThrowsArgumentException`. -**Expected**: `Depth` is 2, `PlanDepth` is 2, `ReportDepth` is 4. +**Context_Create_ElaborateFlag_SetsElaborateId**: `Context.Create` is called with `["--elaborate", "Core-Logic"]`. Expected outcome: `ElaborateId` is `"Core-Logic"`. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ElaborateFlag_SetsElaborateId`. -**Requirement coverage**: `ReviewMark-Context-Parsing` +**Context_Create_NoArguments_ElaborateIdIsNull**: `Context.Create` is called with no arguments. Expected outcome: `ElaborateId` is null. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_ElaborateIdIsNull`. -##### Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException +**Context_Create_ElaborateFlag_WithoutValue_ThrowsArgumentException**: `Context.Create` is called with `["--elaborate"]` (no value). Expected outcome: `ArgumentException` is thrown. Boundary or error path: Missing value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_ElaborateFlag_WithoutValue_ThrowsArgumentException`. -**Scenario**: `Context.Create` is called with `["--log", "<path-with-nonexistent-parent-dir>"]`. +**Context_Create_LintFlag_SetsLintTrue**: `Context.Create` is called with `["--lint"]`. Expected outcome: `Lint` is true; `Version` and `Help` are false. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_LintFlag_SetsLintTrue`. -**Expected**: `InvalidOperationException` is thrown. +**Context_Create_NoArguments_LintIsFalse**: `Context.Create` is called with no arguments. Expected outcome: `Lint` is false. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_NoArguments_LintIsFalse`. -**Boundary / error path**: Log file path whose parent directory does not exist. +**Context_Create_DepthFlag_SetsDepth**: `Context.Create` is called with `["--depth", "3"]`. Expected outcome: `Depth`, `PlanDepth`, and `ReportDepth` are all 3. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_SetsDepth`. -**Requirement coverage**: `ReviewMark-Context-LogFileError` +**Context_Create_DepthFlag_PlanDepthOverride**: `Context.Create` is called with `["--depth", "2", "--plan-depth", "4"]`. Expected outcome: `Depth` is 2, `PlanDepth` is 4, `ReportDepth` is 2. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_PlanDepthOverride`. -##### Context_WriteError_Silent_DoesNotWriteToConsole +**Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException**: `Context.Create` is called with `["--depth", "not-a-number"]`. Expected outcome: `ArgumentException` is thrown with message containing `"--depth"`. Boundary or error path: Non-numeric depth. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException`. -**Scenario**: A silent `Context` calls `WriteError`. +**Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException**: `Context.Create` is called with `["--depth", "0"]`. Expected outcome: `ArgumentException` is thrown with message containing `"--depth"`. Boundary or error path: Zero depth. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException`. -**Expected**: Standard error receives nothing. +**Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException**: `Context.Create` is called with `["--depth", "6"]`. Expected outcome: `ArgumentException` is thrown with message containing `"--depth"`. Boundary or error path: Depth exceeds maximum of 5. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Context-Output` +**Context_Create_DepthFlag_MissingValue_ThrowsArgumentException**: `Context.Create` is called with `["--depth"]` (no value). Expected outcome: `ArgumentException` is thrown with message containing `"--depth"`. Boundary or error path: Missing value. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_MissingValue_ThrowsArgumentException`. -##### Context_WriteError_WritesToLogFile +**Context_Create_DepthFlag_ReportDepthOverride**: `Context.Create` is called with `["--depth", "2", "--report-depth", "4"]`. Expected outcome: `Depth` is 2, `PlanDepth` is 2, `ReportDepth` is 4. Requirement coverage: `ReviewMark-Context-Parsing`. This scenario is tested by `Context_Create_DepthFlag_ReportDepthOverride`. -**Scenario**: A `Context` with `--silent --log <file>` calls `WriteError`. +**Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException**: `Context.Create` is called with `["--log", "<path-with-nonexistent-parent-dir>"]`. Expected outcome: `InvalidOperationException` is thrown. Boundary or error path: Log file path whose parent directory does not exist. Requirement coverage: `ReviewMark-Context-LogFileError`. This scenario is tested by `Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException`. -**Expected**: The error message appears in the log file. +**Context_WriteError_Silent_DoesNotWriteToConsole**: A silent `Context` calls `WriteError`. Expected outcome: Standard error receives nothing. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteError_Silent_DoesNotWriteToConsole`. -**Requirement coverage**: `ReviewMark-Context-Output` +**Context_WriteError_WritesToLogFile**: A `Context` with `--silent --log <file>` calls `WriteError`. Expected outcome: The error message appears in the log file. Requirement coverage: `ReviewMark-Context-Output`. This scenario is tested by `Context_WriteError_WritesToLogFile`. #### Requirements Coverage diff --git a/docs/verification/review-mark/configuration.md b/docs/verification/review-mark/configuration.md index 91afb24..0ccb262 100644 --- a/docs/verification/review-mark/configuration.md +++ b/docs/verification/review-mark/configuration.md @@ -2,91 +2,35 @@ ### Verification Approach -The Configuration subsystem is verified through `ConfigurationTests.cs`, which exercises -`ReviewMarkConfiguration` and `GlobMatcher` working together with actual temporary file -systems. Each test creates a fresh temporary directory with controlled definition files -and source files, loads the configuration, and asserts on the resulting state. +Configuration subsystem verification uses `ConfigurationTests.cs` to exercise `ReviewMarkConfiguration` and `GlobMatcher` together against temporary directories, real YAML definition files, and real file sets. These tests verify file discovery, fingerprinting, plan and report generation, elaboration, and malformed-YAML handling across the subsystem boundary; `ReviewIndex` is used only where report generation needs realistic evidence input. -The constructor initializes the temporary directory; `Dispose` deletes it, ensuring each -test operates in a clean environment. +### Test Environment -### Dependencies +Tests run under xUnit on .NET 8, 9, and 10 across Windows, Linux, and macOS. Each test creates a fresh temporary directory, writes controlled `.reviewmark.yaml` and source files, and removes the directory during cleanup. No external services or network access are required. -| Mock / Stub | Reason | -| ------------------- | ------------------------------------------------------------- | -| Temporary directory | Isolated filesystem prevents test interference | -| Temporary YAML file | Controlled definition file with known configuration content | +### Acceptance Criteria -### Test Scenarios - -#### Configuration_NeedsReview_ValidConfig_ResolvesFiles - -**Scenario**: A configuration with `needs-review: ["src/**/*.cs"]` is loaded; two `.cs` -files exist in `src/`. - -**Expected**: `GetNeedsReviewFiles` returns exactly two files. - -**Requirement coverage**: `ReviewMark-Configuration-NeedsReview` - -#### Configuration_Fingerprinting_ContentModified_FingerprintDiffers - -**Scenario**: A configuration is loaded before and after modifying a source file. - -**Expected**: The fingerprints differ after the content change. - -**Requirement coverage**: `ReviewMark-Configuration-Fingerprinting` - -#### Configuration_PlanGeneration_ValidConfig_Succeeds - -**Scenario**: A valid configuration is loaded and `PublishReviewPlan` is called. - -**Expected**: The returned markdown contains the review set ID. - -**Requirement coverage**: `ReviewMark-Configuration-PlanGeneration` - -#### Configuration_ReportGeneration_ValidConfig_Succeeds +- All Configuration subsystem integration tests pass with zero failures. +- Each `ReviewMark-Configuration-*` requirement is traced to at least one scenario and test method. +- Valid configurations generate deterministic fingerprints and Markdown outputs, while malformed inputs produce the documented diagnostics. -**Scenario**: A valid configuration is loaded and `PublishReviewReport` is called. - -**Expected**: The returned markdown contains the review set ID. - -**Requirement coverage**: `ReviewMark-Configuration-ReportGeneration` - -#### Configuration_Elaboration_ValidId_Succeeds - -**Scenario**: A valid configuration file is loaded and `ElaborateReviewSet` is called with a known review-set ID. - -**Expected**: The returned elaboration markdown contains the review-set ID, fingerprint, and file paths. - -**Requirement coverage**: `ReviewMark-Configuration-Elaboration` - -#### Configuration_LoadConfig_ElaborateUnknownId_ThrowsArgumentException - -**Scenario**: A valid configuration file is loaded and `ElaborateReviewSet` is called with an ID that does not exist. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Unknown review-set ID validation at subsystem level. - -**Requirement coverage**: `ReviewMark-Configuration-ElaborateUnknownId` - -#### Configuration_LoadConfig_MalformedYaml_ReturnsIssues +### Test Scenarios -**Scenario**: A configuration file with invalid/malformed YAML is loaded. +**Configuration_NeedsReview_ValidConfig_ResolvesFiles**: A configuration with `needs-review: ["src/**/*.cs"]` is loaded; two `.cs` files exist in `src/`. Expected outcome: `GetNeedsReviewFiles` returns exactly two files. Requirement coverage: `ReviewMark-Configuration-NeedsReview`. This scenario is tested by `Configuration_NeedsReview_ValidConfig_ResolvesFiles`. -**Expected**: The configuration is null and the issues list is non-empty. +**Configuration_Fingerprinting_ContentModified_FingerprintDiffers**: A configuration is loaded before and after modifying a source file. Expected outcome: The fingerprints differ after the content change. Requirement coverage: `ReviewMark-Configuration-Fingerprinting`. This scenario is tested by `Configuration_Fingerprinting_ContentModified_FingerprintDiffers`. -**Boundary / error path**: Malformed YAML input. +**Configuration_PlanGeneration_ValidConfig_Succeeds**: A valid configuration is loaded and `PublishReviewPlan` is called. Expected outcome: The returned markdown contains the review set ID. Requirement coverage: `ReviewMark-Configuration-PlanGeneration`. This scenario is tested by `Configuration_PlanGeneration_ValidConfig_Succeeds`. -**Requirement coverage**: `ReviewMark-Configuration-MalformedYaml` +**Configuration_ReportGeneration_ValidConfig_Succeeds**: A valid configuration is loaded and `PublishReviewReport` is called. Expected outcome: The returned markdown contains the review set ID. Requirement coverage: `ReviewMark-Configuration-ReportGeneration`. This scenario is tested by `Configuration_ReportGeneration_ValidConfig_Succeeds`. -#### Configuration_Fingerprinting_FileRenamed_FingerprintUnchanged +**Configuration_Elaboration_ValidId_Succeeds**: A valid configuration file is loaded and `ElaborateReviewSet` is called with a known review-set ID. Expected outcome: The returned elaboration markdown contains the review-set ID, fingerprint, and file paths. Requirement coverage: `ReviewMark-Configuration-Elaboration`. This scenario is tested by `Configuration_Elaboration_ValidId_Succeeds`. -**Scenario**: A review-set fingerprint is computed before and after renaming one of its source files. +**Configuration_ElaborateReviewSet_UnknownId_ThrowsArgumentException**: A valid configuration file is loaded and `ElaborateReviewSet` is called with an ID that does not exist. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Unknown review-set ID validation at subsystem level. Requirement coverage: `ReviewMark-Configuration-ElaborateUnknownId`. This scenario is tested by `Configuration_ElaborateReviewSet_UnknownId_ThrowsArgumentException`. -**Expected**: The fingerprint is identical before and after renaming (content-based, not path-based). +**Configuration_LoadConfig_MalformedYaml_ReturnsIssues**: A configuration file with invalid/malformed YAML is loaded. Expected outcome: The configuration is null and the issues list is non-empty. Boundary or error path: Malformed YAML input. Requirement coverage: `ReviewMark-Configuration-MalformedYaml`. This scenario is tested by `Configuration_LoadConfig_MalformedYaml_ReturnsIssues`. -**Requirement coverage**: `ReviewMark-Configuration-Fingerprinting` +**Configuration_Fingerprinting_FileRenamed_FingerprintUnchanged**: A review-set fingerprint is computed before and after renaming one of its source files. Expected outcome: The fingerprint is identical before and after renaming (content-based, not path-based). Requirement coverage: `ReviewMark-Configuration-Fingerprinting`. This scenario is tested by `Configuration_Fingerprinting_FileRenamed_FingerprintUnchanged`. ### Requirements Coverage @@ -95,5 +39,5 @@ files exist in `src/`. - **ReviewMark-Configuration-PlanGeneration**: Configuration_PlanGeneration_ValidConfig_Succeeds - **ReviewMark-Configuration-ReportGeneration**: Configuration_ReportGeneration_ValidConfig_Succeeds - **ReviewMark-Configuration-Elaboration**: Configuration_Elaboration_ValidId_Succeeds -- **ReviewMark-Configuration-ElaborateUnknownId**: Configuration_LoadConfig_ElaborateUnknownId_ThrowsArgumentException +- **ReviewMark-Configuration-ElaborateUnknownId**: Configuration_ElaborateReviewSet_UnknownId_ThrowsArgumentException - **ReviewMark-Configuration-MalformedYaml**: Configuration_LoadConfig_MalformedYaml_ReturnsIssues diff --git a/docs/verification/review-mark/configuration/glob-matcher.md b/docs/verification/review-mark/configuration/glob-matcher.md index 81dc55c..88d18a6 100644 --- a/docs/verification/review-mark/configuration/glob-matcher.md +++ b/docs/verification/review-mark/configuration/glob-matcher.md @@ -1,129 +1,44 @@ -### GlobMatcher Verification - -This document describes the unit-level verification design for the `GlobMatcher` unit. -It defines the test scenarios, dependency usage, and requirement coverage for -`Configuration/GlobMatcher.cs`. +### GlobMatcher #### Verification Approach -`GlobMatcher` is verified with unit tests in `GlobMatcherTests.cs`. Tests create temporary -directories with controlled file layouts, call `GlobMatcher.GetMatchingFiles` with various -pattern combinations, and assert on the returned file lists. - -#### Dependencies - -`GlobMatcher` has no dependencies on other tool units. All file system operations use -real temporary directories; no mocking is required. - -#### Test Scenarios - -##### GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles - -**Scenario**: `GetMatchingFiles` is called with a single include pattern that matches -several files. - -**Expected**: All matching files are returned. - -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` - -##### GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles - -**Scenario**: `GetMatchingFiles` is called with an include pattern followed by an exclude -pattern. - -**Expected**: Files matching the exclude pattern are absent from the result. - -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` - -##### GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList - -**Scenario**: `GetMatchingFiles` is called with a pattern that matches nothing. - -**Expected**: Returns an empty list. - -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` - -##### GlobMatcher_GetMatchingFiles_NullBaseDirectory_ThrowsArgumentNullException - -**Scenario**: `GetMatchingFiles` is called with null as the base directory. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null input rejection. +GlobMatcher unit verification uses `GlobMatcherTests.cs` to create temporary directory layouts, execute ordered include and exclude pattern sets, and inspect the returned relative file lists. The tests use real file-system state and no mocks so declaration-order semantics, empty results, validation behavior, and path normalization are verified directly. -**Requirement coverage**: `ReviewMark-GlobMatcher-NullBaseDirectoryRejection` +#### Test Environment -##### GlobMatcher_GetMatchingFiles_EmptyBaseDirectory_ThrowsArgumentException +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10 and create temporary directories and files in-process without any external services. -**Scenario**: `GetMatchingFiles` is called with an empty string as the base directory. +#### Acceptance Criteria -**Expected**: `ArgumentException` is thrown. +- All GlobMatcher unit tests pass with zero failures. +- Each `ReviewMark-GlobMatcher-*` requirement is traced to at least one scenario and test method. +- Ordered include and exclude semantics, argument validation, and forward-slash path normalization all behave as documented. -**Boundary / error path**: Empty input rejection. - -**Requirement coverage**: `ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection` - -##### GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator - -**Scenario**: `GetMatchingFiles` returns a file from a subdirectory. - -**Expected**: The returned path uses forward slashes regardless of OS. - -**Requirement coverage**: `ReviewMark-GlobMatcher-PathNormalization` - -##### GlobMatcher_GetMatchingFiles_NullPatterns_ThrowsArgumentNullException - -**Scenario**: `GetMatchingFiles` is called with null as the patterns list. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null patterns rejection. - -**Requirement coverage**: `ReviewMark-GlobMatcher-NullPatternsRejection` - -##### GlobMatcher_GetMatchingFiles_WhitespaceBaseDirectory_ThrowsArgumentException - -**Scenario**: `GetMatchingFiles` is called with a whitespace-only string as the base directory. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Whitespace base directory rejection. - -**Requirement coverage**: `ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection` - -##### GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles - -**Scenario**: `GetMatchingFiles` is called with patterns that include all `.cs` files, -exclude a subdirectory, then re-include a specific file in that subdirectory. - -**Expected**: The re-included file and all other non-excluded files are in the result; the other excluded files are absent. - -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` +#### Test Scenarios -##### GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles +**GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles**: `GetMatchingFiles` is called with a single include pattern that matches several files. Expected outcome: All matching files are returned. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_SingleIncludePattern_ReturnsMatchingFiles`. -**Scenario**: `GetMatchingFiles` is called with an include pattern for all `.cs` files -and an exclude pattern for the `obj/` directory. +**GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles**: `GetMatchingFiles` is called with an include pattern followed by an exclude pattern. Expected outcome: Files matching the exclude pattern are absent from the result. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_ExcludePattern_ExcludesMatchingFiles`. -**Expected**: Only files outside the excluded directory are returned. +**GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList**: `GetMatchingFiles` is called with a pattern that matches nothing. Expected outcome: Returns an empty list. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_NoMatchingFiles_ReturnsEmptyList`. -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` +**GlobMatcher_GetMatchingFiles_NullBaseDirectory_ThrowsArgumentNullException**: `GetMatchingFiles` is called with null as the base directory. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null input rejection. Requirement coverage: `ReviewMark-GlobMatcher-NullBaseDirectoryRejection`. This scenario is tested by `GlobMatcher_GetMatchingFiles_NullBaseDirectory_ThrowsArgumentNullException`. -##### GlobMatcher_GetMatchingFiles_EmptyPatterns_ReturnsEmptyList +**GlobMatcher_GetMatchingFiles_EmptyBaseDirectory_ThrowsArgumentException**: `GetMatchingFiles` is called with an empty string as the base directory. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Empty input rejection. Requirement coverage: `ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection`. This scenario is tested by `GlobMatcher_GetMatchingFiles_EmptyBaseDirectory_ThrowsArgumentException`. -**Scenario**: `GetMatchingFiles` is called with an empty patterns list. +**GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator**: `GetMatchingFiles` returns a file from a subdirectory. Expected outcome: The returned path uses forward slashes regardless of OS. Requirement coverage: `ReviewMark-GlobMatcher-PathNormalization`. This scenario is tested by `GlobMatcher_GetMatchingFiles_FileInSubdirectory_UsesForwardSlashSeparator`. -**Expected**: An empty list is returned. +**GlobMatcher_GetMatchingFiles_NullPatterns_ThrowsArgumentNullException**: `GetMatchingFiles` is called with null as the patterns list. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null patterns rejection. Requirement coverage: `ReviewMark-GlobMatcher-NullPatternsRejection`. This scenario is tested by `GlobMatcher_GetMatchingFiles_NullPatterns_ThrowsArgumentNullException`. -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` +**GlobMatcher_GetMatchingFiles_WhitespaceBaseDirectory_ThrowsArgumentException**: `GetMatchingFiles` is called with a whitespace-only string as the base directory. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Whitespace base directory rejection. Requirement coverage: `ReviewMark-GlobMatcher-EmptyBaseDirectoryRejection`. This scenario is tested by `GlobMatcher_GetMatchingFiles_WhitespaceBaseDirectory_ThrowsArgumentException`. -##### GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching +**GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles**: `GetMatchingFiles` is called with patterns that include all `.cs` files, exclude a subdirectory, then re-include a specific file in that subdirectory. Expected outcome: The re-included file and all other non-excluded files are in the result; the other excluded files are absent. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_ReIncludeAfterExclude_ReturnsReIncludedFiles`. -**Scenario**: `GetMatchingFiles` is called with two include patterns (e.g., `**/*.cs` and `**/*.yaml`). +**GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles**: `GetMatchingFiles` is called with an include pattern for all `.cs` files and an exclude pattern for the `obj/` directory. Expected outcome: Only files outside the excluded directory are returned. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_IncludeAndExclude_ReturnsFilteredFiles`. -**Expected**: Files matching either pattern are returned; files matching neither are absent. +**GlobMatcher_GetMatchingFiles_EmptyPatterns_ReturnsEmptyList**: `GetMatchingFiles` is called with an empty patterns list. Expected outcome: An empty list is returned. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_EmptyPatterns_ReturnsEmptyList`. -**Requirement coverage**: `ReviewMark-GlobMatcher-IncludeExclude` +**GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching**: `GetMatchingFiles` is called with two include patterns (e.g., `**/*.cs` and `**/*.yaml`). Expected outcome: Files matching either pattern are returned; files matching neither are absent. Requirement coverage: `ReviewMark-GlobMatcher-IncludeExclude`. This scenario is tested by `GlobMatcher_GetMatchingFiles_MultipleIncludePatterns_ReturnsAllMatching`. #### Requirements Coverage diff --git a/docs/verification/review-mark/configuration/review-mark-configuration.md b/docs/verification/review-mark/configuration/review-mark-configuration.md index 3de9369..8aba002 100644 --- a/docs/verification/review-mark/configuration/review-mark-configuration.md +++ b/docs/verification/review-mark/configuration/review-mark-configuration.md @@ -1,347 +1,96 @@ -### ReviewMarkConfiguration Verification - -This document describes the unit-level verification design for the `ReviewMarkConfiguration` -unit. It defines the test scenarios, dependency usage, and requirement coverage for -`Configuration/ReviewMarkConfiguration.cs`. +### ReviewMarkConfiguration #### Verification Approach -`ReviewMarkConfiguration` is verified with unit tests in `ReviewMarkConfigurationTests.cs`. -Tests parse inline YAML strings or load from temporary files, then assert on the resulting -configuration model properties and generated Markdown output. - -#### Dependencies - -`ReviewMarkConfiguration` depends on `GlobMatcher` for file resolution, but these -unit tests exercise the full stack with real temporary files rather than mocks, because -the integration is simple and deterministic. - -#### Test Scenarios - -##### ReviewMarkConfiguration_Parse_NullYaml_ThrowsArgumentNullException - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with null. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null input rejection. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with valid YAML. - -**Expected**: Returns a non-null configuration object. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a valid definition file. - -**Expected**: Returns a result with non-null configuration and no issues. - -**Requirement coverage**: `ReviewMark-Config-Loading` - -##### ReviewMarkConfiguration_Load_NonExistentFile_ReturnsNullConfigWithErrorIssue - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a path that does not exist. - -**Expected**: Result has null configuration and at least one error-level issue. - -**Boundary / error path**: Missing file handling. - -**Requirement coverage**: `ReviewMark-Config-LoadingNullOnError` - -##### ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a YAML missing -`evidence-source`. - -**Expected**: Result has null configuration and at least one error-level issue. - -**Requirement coverage**: `ReviewMark-Config-Loading`, `ReviewMark-Config-LoadingNullOnError` - -##### ReviewMarkConfiguration_PublishReviewPlan_AllCovered_NoIssues - -**Scenario**: `PublishReviewPlan` is called when all needs-review files are covered. - -**Expected**: Returned markdown contains plan content; no issues. - -**Requirement coverage**: `ReviewMark-Config-PlanGeneration` - -##### ReviewMarkConfiguration_PublishReviewReport_CurrentReview_NoIssues - -**Scenario**: `PublishReviewReport` is called with a current review in the index. - -**Expected**: Report markdown shows "Current" status; no issues. - -**Requirement coverage**: `ReviewMark-Config-ReportGeneration` - -##### ReviewMarkConfiguration_ElaborateReviewSet_ValidId_ReturnsElaboration - -**Scenario**: `ElaborateReviewSet` is called with a valid review set ID. - -**Expected**: Returns markdown containing the ID, fingerprint, and file list. - -**Requirement coverage**: `ReviewMark-Config-Elaboration` - -##### ReviewMarkConfiguration_ElaborateReviewSet_UnknownId_ThrowsArgumentException - -**Scenario**: `ElaborateReviewSet` is called with an ID not in the configuration. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Unknown review set ID. - -**Requirement coverage**: `ReviewMark-Config-ElaborationUnknownIdRejection` - -##### ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing three `needs-review` patterns. - -**Expected**: `NeedsReviewPatterns` contains all three patterns in order. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing a `url` evidence source. - -**Expected**: `EvidenceSource.Type` is `"url"`, `Location` is set, credentials are null. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing one review. - -**Expected**: `Reviews` has one entry with expected `Id`, `Title`, and `Paths`. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing credential environment variable names. - -**Expected**: `EvidenceSource.UsernameEnv` and `PasswordEnv` are set correctly. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_GetNeedsReviewFiles_ReturnsMatchingFiles - -**Scenario**: `GetNeedsReviewFiles` is called on a configuration with a `.cs` pattern; one `.cs` and one `.txt` file exist. +ReviewMarkConfiguration unit verification uses `ReviewMarkConfigurationTests.cs` with inline YAML strings, temporary configuration files, real `GlobMatcher` file resolution, and `ReviewIndex` fixtures where report generation requires evidence. The tests validate parsing, integrated linting, fingerprinting, Markdown generation, elaboration, and issue reporting through the unit's public surface rather than through mocks. -**Expected**: Only the `.cs` file is returned. +#### Test Environment -**Requirement coverage**: `ReviewMark-Config-Reading` +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10, create temporary YAML and source files in-process, and require no external services or network access. -##### ReviewSet_GetFingerprint_SameContent_ReturnsSameFingerprint +#### Acceptance Criteria -**Scenario**: Two directories with identical file content; `GetFingerprint` called on each. +- All ReviewMarkConfiguration unit tests pass with zero failures. +- Each `ReviewMark-Config-*` requirement is traced to at least one scenario and test method. +- Configuration loading returns deterministic diagnostics, fingerprints remain content based, and generated Markdown honors the requested heading depth constraints. -**Expected**: Both fingerprints are equal. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewSet_GetFingerprint_DifferentContent_ReturnsDifferentFingerprint - -**Scenario**: Two directories with different file content; `GetFingerprint` called on each. - -**Expected**: The fingerprints differ. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewSet_GetFingerprint_RenameFile_ReturnsSameFingerprint - -**Scenario**: Two directories where one file differs only in name but has identical -content; `GetFingerprint` called on each. - -**Expected**: Both fingerprints are equal (content-based, not path-based). - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a file containing invalid YAML syntax. - -**Expected**: Result has null configuration and one error issue naming the file and line. - -**Boundary / error path**: Invalid YAML syntax. - -**Requirement coverage**: `ReviewMark-Config-Loading`, `ReviewMark-Config-LoadingNullOnError` - -##### ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a file missing -`evidence-source` AND containing duplicate review IDs. - -**Expected**: Result has null configuration and both errors are reported (does not stop at first). - -**Requirement coverage**: `ReviewMark-Config-Loading` - -##### ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a config having a relative `fileshare` location. - -**Expected**: The `EvidenceSource.Location` is resolved to an absolute path under the config file's directory. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a config having `evidence-source: type: none`. - -**Expected**: No issues; configuration is non-null. - -**Requirement coverage**: `ReviewMark-Config-Loading` - -##### ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext - -**Scenario**: A `ReviewMarkLoadResult` with one warning and one error calls `ReportIssues` on a context. - -**Expected**: Exit code is 1; both messages appear in the log. - -**Requirement coverage**: `ReviewMark-Config-Loading` - -##### ReviewMarkConfiguration_Load_WhitespaceOnlyPaths_ReturnsLintError - -**Scenario**: `ReviewMarkConfiguration.Load` is called with a config whose review set paths list contains only whitespace. - -**Expected**: Null configuration with a lint error referencing `"paths"`. - -**Requirement coverage**: `ReviewMark-Config-Loading` - -##### ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing `evidence-source: type: none`. - -**Expected**: `EvidenceSource.Type` is `"none"` and `Location` is empty. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_Parse_NoneEvidenceSource_NoLocationRequired - -**Scenario**: `ReviewMarkConfiguration.Parse` is called with YAML containing a `none` source and no `location` field. - -**Expected**: Parsing succeeds without throwing; `EvidenceSource.Type` is `"none"`. - -**Requirement coverage**: `ReviewMark-Config-Reading` - -##### ReviewMarkConfiguration_PublishReviewPlan_UncoveredFiles_HasIssues - -**Scenario**: `PublishReviewPlan` is called when at least one needs-review file is not covered by any review set. - -**Expected**: `HasIssues` is true; the uncovered file appears in the Markdown. - -**Requirement coverage**: `ReviewMark-Config-PlanGeneration` - -##### ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepth_UsedForHeadings - -**Scenario**: `PublishReviewPlan` is called with `markdownDepth: 2`. - -**Expected**: Main heading is at level 2; subheading at level 3. - -**Requirement coverage**: `ReviewMark-Config-PlanMarkdownDepth` - -##### ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepthAbove5_Throws - -**Scenario**: `PublishReviewPlan` is called with `markdownDepth: 6`. - -**Expected**: `ArgumentOutOfRangeException` is thrown. - -**Boundary / error path**: Depth exceeds maximum. - -**Requirement coverage**: `ReviewMark-Config-PlanMarkdownDepthValidation` - -##### ReviewMarkConfiguration_PublishReviewReport_StaleReview_HasIssues - -**Scenario**: `PublishReviewReport` is called with an index having an outdated fingerprint. - -**Expected**: `HasIssues` is true; Markdown shows "Stale". - -**Requirement coverage**: `ReviewMark-Config-ReportGeneration` - -##### ReviewMarkConfiguration_PublishReviewReport_FailedReview_HasIssues - -**Scenario**: `PublishReviewReport` is called with an index having a matching fingerprint but a failing result. +#### Test Scenarios -**Expected**: `HasIssues` is true; Markdown shows "Failed". +**ReviewMarkConfiguration_Parse_NullYaml_ThrowsArgumentNullException**: `ReviewMarkConfiguration.Parse` is called with null. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null input rejection. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_NullYaml_ThrowsArgumentNullException`. -**Requirement coverage**: `ReviewMark-Config-ReportGeneration` +**ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration**: `ReviewMarkConfiguration.Parse` is called with valid YAML. Expected outcome: Returns a non-null configuration object. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_ValidYaml_ReturnsConfiguration`. -##### ReviewMarkConfiguration_PublishReviewReport_MissingReview_HasIssues +**ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues**: `ReviewMarkConfiguration.Load` is called with a valid definition file. Expected outcome: Returns a result with non-null configuration and no issues. Requirement coverage: `ReviewMark-Config-Loading`. This scenario is tested by `ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues`. -**Scenario**: `PublishReviewReport` is called with an empty index. +**ReviewMarkConfiguration_Load_NonExistentFile_ReturnsNullConfigWithErrorIssue**: `ReviewMarkConfiguration.Load` is called with a path that does not exist. Expected outcome: Result has null configuration and at least one error-level issue. Boundary or error path: Missing file handling. Requirement coverage: `ReviewMark-Config-LoadingNullOnError`. This scenario is tested by `ReviewMarkConfiguration_Load_NonExistentFile_ReturnsNullConfigWithErrorIssue`. -**Expected**: `HasIssues` is true; Markdown shows "Missing". +**ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue**: `ReviewMarkConfiguration.Load` is called with a YAML missing `evidence-source`. Expected outcome: Result has null configuration and at least one error-level issue. Requirement coverage: `ReviewMark-Config-Loading`, `ReviewMark-Config-LoadingNullOnError`. This scenario is tested by `ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue`. -**Requirement coverage**: `ReviewMark-Config-ReportGeneration` +**ReviewMarkConfiguration_PublishReviewPlan_AllCovered_NoIssues**: `PublishReviewPlan` is called when all needs-review files are covered. Expected outcome: Returned markdown contains plan content; no issues. Requirement coverage: `ReviewMark-Config-PlanGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewPlan_AllCovered_NoIssues`. -##### ReviewMarkConfiguration_PublishReviewReport_MarkdownDepth_UsedForHeadings +**ReviewMarkConfiguration_PublishReviewReport_CurrentReview_NoIssues**: `PublishReviewReport` is called with a current review in the index. Expected outcome: Report markdown shows "Current" status; no issues. Requirement coverage: `ReviewMark-Config-ReportGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_CurrentReview_NoIssues`. -**Scenario**: `PublishReviewReport` is called with `markdownDepth: 2`. +**ReviewMarkConfiguration_ElaborateReviewSet_ValidId_ReturnsElaboration**: `ElaborateReviewSet` is called with a valid review set ID. Expected outcome: Returns markdown containing the ID, fingerprint, and file list. Requirement coverage: `ReviewMark-Config-Elaboration`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_ValidId_ReturnsElaboration`. -**Expected**: Main heading starts with `"## Review Status"`. +**ReviewMarkConfiguration_ElaborateReviewSet_UnknownId_ThrowsArgumentException**: `ElaborateReviewSet` is called with an ID not in the configuration. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Unknown review set ID. Requirement coverage: `ReviewMark-Config-ElaborationUnknownIdRejection`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_UnknownId_ThrowsArgumentException`. -**Requirement coverage**: `ReviewMark-Config-ReportMarkdownDepth` +**ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly**: `ReviewMarkConfiguration.Parse` is called with YAML containing three `needs-review` patterns. Expected outcome: `NeedsReviewPatterns` contains all three patterns in order. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_NeedsReviewPatterns_ParsedCorrectly`. -##### ReviewMarkConfiguration_PublishReviewReport_MarkdownDepthAbove5_Throws +**ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly**: `ReviewMarkConfiguration.Parse` is called with YAML containing a `url` evidence source. Expected outcome: `EvidenceSource.Type` is `"url"`, `Location` is set, credentials are null. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_EvidenceSource_ParsedCorrectly`. -**Scenario**: `PublishReviewReport` is called with `markdownDepth: 6`. +**ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly**: `ReviewMarkConfiguration.Parse` is called with YAML containing one review. Expected outcome: `Reviews` has one entry with expected `Id`, `Title`, and `Paths`. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_Reviews_ParsedCorrectly`. -**Expected**: `ArgumentOutOfRangeException` is thrown. +**ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly**: `ReviewMarkConfiguration.Parse` is called with YAML containing credential environment variable names. Expected outcome: `EvidenceSource.UsernameEnv` and `PasswordEnv` are set correctly. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_EvidenceSourceWithCredentials_ParsedCorrectly`. -**Boundary / error path**: Depth exceeds maximum. +**ReviewMarkConfiguration_GetNeedsReviewFiles_ReturnsMatchingFiles**: `GetNeedsReviewFiles` is called on a configuration with a `.cs` pattern; one `.cs` and one `.txt` file exist. Expected outcome: Only the `.cs` file is returned. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_GetNeedsReviewFiles_ReturnsMatchingFiles`. -**Requirement coverage**: `ReviewMark-Config-ReportMarkdownDepthValidation` +**ReviewSet_GetFingerprint_SameContent_ReturnsSameFingerprint**: Two directories with identical file content; `GetFingerprint` called on each. Expected outcome: Both fingerprints are equal. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewSet_GetFingerprint_SameContent_ReturnsSameFingerprint`. -##### ReviewMarkConfiguration_ElaborateReviewSet_NullId_ThrowsArgumentNullException +**ReviewSet_GetFingerprint_DifferentContent_ReturnsDifferentFingerprint**: Two directories with different file content; `GetFingerprint` called on each. Expected outcome: The fingerprints differ. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewSet_GetFingerprint_DifferentContent_ReturnsDifferentFingerprint`. -**Scenario**: `ElaborateReviewSet` is called with null as the ID. +**ReviewSet_GetFingerprint_RenameFile_ReturnsSameFingerprint**: Two directories where one file differs only in name but has identical content; `GetFingerprint` called on each. Expected outcome: Both fingerprints are equal (content-based, not path-based). Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewSet_GetFingerprint_RenameFile_ReturnsSameFingerprint`. -**Expected**: `ArgumentNullException` is thrown. +**ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue**: `ReviewMarkConfiguration.Load` is called with a file containing invalid YAML syntax. Expected outcome: Result has null configuration and one error issue naming the file and line. Boundary or error path: Invalid YAML syntax. Requirement coverage: `ReviewMark-Config-Loading`, `ReviewMark-Config-LoadingNullOnError`. This scenario is tested by `ReviewMarkConfiguration_Load_InvalidYaml_ReturnsNullConfigWithErrorIssue`. -**Boundary / error path**: Null ID rejection. +**ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues**: `ReviewMarkConfiguration.Load` is called with a file missing `evidence-source` AND containing duplicate review IDs. Expected outcome: Result has null configuration and both errors are reported (does not stop at first). Requirement coverage: `ReviewMark-Config-Loading`. This scenario is tested by `ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues`. -**Requirement coverage**: `ReviewMark-Config-ElaborationNullRejection` +**ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath**: `ReviewMarkConfiguration.Load` is called with a config having a relative `fileshare` location. Expected outcome: The `EvidenceSource.Location` is resolved to an absolute path under the config file's directory. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath`. -##### ReviewMarkConfiguration_ElaborateReviewSet_WhitespaceId_ThrowsArgumentException +**ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues**: `ReviewMarkConfiguration.Load` is called with a config having `evidence-source: type: none`. Expected outcome: No issues; configuration is non-null. Requirement coverage: `ReviewMark-Config-Loading`. This scenario is tested by `ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues`. -**Scenario**: `ElaborateReviewSet` is called with a whitespace-only string as the ID. +**ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext**: A `ReviewMarkLoadResult` with one warning and one error calls `ReportIssues` on a context. Expected outcome: Exit code is 1; both messages appear in the log. Requirement coverage: `ReviewMark-Config-Loading`. This scenario is tested by `ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext`. -**Expected**: `ArgumentException` is thrown. +**ReviewMarkConfiguration_Load_WhitespaceOnlyPaths_ReturnsLintError**: `ReviewMarkConfiguration.Load` is called with a config whose review set paths list contains only whitespace. Expected outcome: Null configuration with a lint error referencing `"paths"`. Requirement coverage: `ReviewMark-Config-Loading`. This scenario is tested by `ReviewMarkConfiguration_Load_WhitespaceOnlyPaths_ReturnsLintError`. -**Boundary / error path**: Whitespace/empty ID rejection. +**ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly**: `ReviewMarkConfiguration.Parse` is called with YAML containing `evidence-source: type: none`. Expected outcome: `EvidenceSource.Type` is `"none"` and `Location` is empty. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_NoneEvidenceSource_ParsedCorrectly`. -**Requirement coverage**: `ReviewMark-Config-ElaborationNullRejection` +**ReviewMarkConfiguration_Parse_NoneEvidenceSource_NoLocationRequired**: `ReviewMarkConfiguration.Parse` is called with YAML containing a `none` source and no `location` field. Expected outcome: Parsing succeeds without throwing; `EvidenceSource.Type` is `"none"`. Requirement coverage: `ReviewMark-Config-Reading`. This scenario is tested by `ReviewMarkConfiguration_Parse_NoneEvidenceSource_NoLocationRequired`. -##### ReviewMarkConfiguration_ElaborateReviewSet_ContainsFullFingerprint +**ReviewMarkConfiguration_PublishReviewPlan_UncoveredFiles_HasIssues**: `PublishReviewPlan` is called when at least one needs-review file is not covered by any review set. Expected outcome: `HasIssues` is true; the uncovered file appears in the Markdown. Requirement coverage: `ReviewMark-Config-PlanGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewPlan_UncoveredFiles_HasIssues`. -**Scenario**: `ElaborateReviewSet` is called with a valid ID and a source file present. +**ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepth_UsedForHeadings**: `PublishReviewPlan` is called with `markdownDepth: 2`. Expected outcome: Main heading is at level 2; subheading at level 3. Requirement coverage: `ReviewMark-Config-PlanMarkdownDepth`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepth_UsedForHeadings`. -**Expected**: The full 64-character hex fingerprint appears in the Markdown. +**ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepthAbove5_Throws**: `PublishReviewPlan` is called with `markdownDepth: 6`. Expected outcome: `ArgumentOutOfRangeException` is thrown. Boundary or error path: Depth exceeds maximum. Requirement coverage: `ReviewMark-Config-PlanMarkdownDepthValidation`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewPlan_MarkdownDepthAbove5_Throws`. -**Requirement coverage**: `ReviewMark-Config-Elaboration` +**ReviewMarkConfiguration_PublishReviewReport_StaleReview_HasIssues**: `PublishReviewReport` is called with an index having an outdated fingerprint. Expected outcome: `HasIssues` is true; Markdown shows "Stale". Requirement coverage: `ReviewMark-Config-ReportGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_StaleReview_HasIssues`. -##### ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepth_UsedForHeadings +**ReviewMarkConfiguration_PublishReviewReport_FailedReview_HasIssues**: `PublishReviewReport` is called with an index having a matching fingerprint but a failing result. Expected outcome: `HasIssues` is true; Markdown shows "Failed". Requirement coverage: `ReviewMark-Config-ReportGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_FailedReview_HasIssues`. -**Scenario**: `ElaborateReviewSet` is called with `markdownDepth: 2`. +**ReviewMarkConfiguration_PublishReviewReport_MissingReview_HasIssues**: `PublishReviewReport` is called with an empty index. Expected outcome: `HasIssues` is true; Markdown shows "Missing". Requirement coverage: `ReviewMark-Config-ReportGeneration`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_MissingReview_HasIssues`. -**Expected**: Main heading starts with `"## Core-Logic"`; Files subheading at level 3. +**ReviewMarkConfiguration_PublishReviewReport_MarkdownDepth_UsedForHeadings**: `PublishReviewReport` is called with `markdownDepth: 2`. Expected outcome: Main heading starts with `" ## Review Status"`. Requirement coverage: `ReviewMark-Config-ReportMarkdownDepth`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_MarkdownDepth_UsedForHeadings`. -**Requirement coverage**: `ReviewMark-Config-ElaborationMarkdownDepth` +**ReviewMarkConfiguration_PublishReviewReport_MarkdownDepthAbove5_Throws**: `PublishReviewReport` is called with `markdownDepth: 6`. Expected outcome: `ArgumentOutOfRangeException` is thrown. Boundary or error path: Depth exceeds maximum. Requirement coverage: `ReviewMark-Config-ReportMarkdownDepthValidation`. This scenario is tested by `ReviewMarkConfiguration_PublishReviewReport_MarkdownDepthAbove5_Throws`. -##### ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepthAbove5_Throws +**ReviewMarkConfiguration_ElaborateReviewSet_NullId_ThrowsArgumentNullException**: `ElaborateReviewSet` is called with null as the ID. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null ID rejection. Requirement coverage: `ReviewMark-Config-ElaborationNullRejection`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_NullId_ThrowsArgumentNullException`. -**Scenario**: `ElaborateReviewSet` is called with `markdownDepth: 6`. +**ReviewMarkConfiguration_ElaborateReviewSet_WhitespaceId_ThrowsArgumentException**: `ElaborateReviewSet` is called with a whitespace-only string as the ID. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Whitespace/empty ID rejection. Requirement coverage: `ReviewMark-Config-ElaborationNullRejection`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_WhitespaceId_ThrowsArgumentException`. -**Expected**: `ArgumentOutOfRangeException` is thrown. +**ReviewMarkConfiguration_ElaborateReviewSet_ContainsFullFingerprint**: `ElaborateReviewSet` is called with a valid ID and a source file present. Expected outcome: The full 64-character hex fingerprint appears in the Markdown. Requirement coverage: `ReviewMark-Config-Elaboration`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_ContainsFullFingerprint`. -**Boundary / error path**: Depth exceeds maximum. +**ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepth_UsedForHeadings**: `ElaborateReviewSet` is called with `markdownDepth: 2`. Expected outcome: Main heading starts with `" ## Core-Logic"`; Files subheading at level 3. Requirement coverage: `ReviewMark-Config-ElaborationMarkdownDepth`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepth_UsedForHeadings`. -**Requirement coverage**: `ReviewMark-Config-ElaborationMarkdownDepthValidation` +**ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepthAbove5_Throws**: `ElaborateReviewSet` is called with `markdownDepth: 6`. Expected outcome: `ArgumentOutOfRangeException` is thrown. Boundary or error path: Depth exceeds maximum. Requirement coverage: `ReviewMark-Config-ElaborationMarkdownDepthValidation`. This scenario is tested by `ReviewMarkConfiguration_ElaborateReviewSet_MarkdownDepthAbove5_Throws`. #### Requirements Coverage @@ -361,6 +110,7 @@ content; `GetFingerprint` called on each. ReviewMarkConfiguration_Load_FileshareRelativeLocation_ResolvesToAbsolutePath - **ReviewMark-Config-Loading**: ReviewMarkConfiguration_Load_ValidFile_ReturnsConfigurationAndNoIssues, + ReviewMarkConfiguration_Load_MissingEvidenceSource_ReturnsNullConfigWithErrorIssue, ReviewMarkConfiguration_Load_MultipleErrors_ReturnsAllIssues, ReviewMarkConfiguration_Load_NoneEvidenceSource_NoIssues, ReviewMarkLoadResult_ReportIssues_RoutesIssuesToContext, diff --git a/docs/verification/review-mark/indexing.md b/docs/verification/review-mark/indexing.md index 8c6a6df..a65dfad 100644 --- a/docs/verification/review-mark/indexing.md +++ b/docs/verification/review-mark/indexing.md @@ -2,93 +2,45 @@ ### Verification Approach -The Indexing subsystem is verified through `IndexingTests.cs`, which exercises -`ReviewIndex` and `PathHelpers` working together with actual temporary directories. -Each test creates a fresh isolated directory with controlled index JSON or PDF files, -exercises the subsystem operations, and asserts on the resulting index state. +Indexing subsystem verification uses `IndexingTests.cs` to exercise `ReviewIndex` and `PathHelpers` together with temporary directories, JSON fixtures, minimal PDF evidence files, and a fake in-process HTTP handler. This verifies fileshare, URL, and none-source loading, PDF scan behavior, save and reload round-trips, and safe path handling at the subsystem boundary without requiring real network access. -The constructor initializes the temporary directory; `Dispose` deletes it, ensuring -clean isolation between tests. +### Test Environment -### Dependencies +Tests run under xUnit on .NET 8, 9, and 10 across Windows, Linux, and macOS. Each test creates an isolated temporary directory, URL-source scenarios inject a fake `HttpMessageHandler`, and PDF-scan scenarios use real minimal PDF fixtures. -| Mock / Stub | Reason | -| ------------------------ | -------------------------------------------------------------- | -| Temporary directory | Isolated filesystem prevents test interference | -| Fake JSON index file | Provides controlled evidence index without real PDF evidence | -| `FakeHttpMessageHandler` | Returns fixed JSON payload for URL-source tests | +### Acceptance Criteria -### Test Scenarios - -#### Indexing_SafePathCombine_WithIndexPath_LoadsIndex - -**Scenario**: A subdirectory index JSON is loaded using a path constructed with -`PathHelpers.SafePathCombine`. - -**Expected**: The index contains the entries from the JSON file. - -**Requirement coverage**: `ReviewMark-Indexing-LoadEvidence`, `ReviewMark-Indexing-SafePathCombine` - -#### Indexing_ReviewIndex_SaveAndLoad_RoundTrip - -**Scenario**: A populated index is loaded from JSON, saved to a new file, then reloaded. - -**Expected**: All entries survive the round-trip. - -**Requirement coverage**: `ReviewMark-Indexing-Save`, `ReviewMark-Indexing-LoadEvidence` - -#### Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex - -**Scenario**: `ReviewIndex.Load` is called with a `none`-type evidence source. +- All Indexing subsystem integration tests pass with zero failures. +- Each `ReviewMark-Indexing-*` requirement is traced to at least one scenario and test method. +- Evidence loading, index creation, persistence, and path-traversal rejection all behave as documented for both normal and error paths. -**Expected**: Returns an empty index immediately; no file system access occurs. - -**Requirement coverage**: `ReviewMark-Indexing-LoadEvidence` - -#### Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex - -**Scenario**: `ReviewIndex.Load` is called with a `url`-type source and a fake HTTP client -returning a fixed JSON payload. - -**Expected**: The index contains the entry from the JSON payload. - -**Requirement coverage**: `ReviewMark-Indexing-LoadEvidence` - -#### Indexing_SafePathCombine_WithTraversalInputs_Throws - -**Scenario**: `PathHelpers.SafePathCombine` is called with path traversal inputs — first -with a `..`-based relative path (`../../etc/sensitive`) and then with an absolute path. - -**Expected**: `ArgumentException` is thrown in both cases; directory traversal and -absolute-path injection are rejected. - -**Boundary / error path**: Path traversal prevention. +### Test Scenarios -**Requirement coverage**: `ReviewMark-Indexing-SafePathCombine` +**Indexing_SafePathCombine_WithIndexPath_LoadsIndex**: A subdirectory index JSON is loaded using a path constructed with `PathHelpers.SafePathCombine`. Expected outcome: The index contains the entries from the JSON file. Requirement coverage: `ReviewMark-Indexing-LoadEvidence`, `ReviewMark-Indexing-SafePathCombine`. This scenario is tested by `Indexing_SafePathCombine_WithIndexPath_LoadsIndex`. -#### Indexing_ReviewIndex_Scan_WithNoPdfs_ReturnsEmptyIndex +**Indexing_ReviewIndex_SaveAndLoad_RoundTrip**: A populated index is loaded from JSON, saved to a new file, then reloaded. Expected outcome: All entries survive the round-trip. Requirement coverage: `ReviewMark-Indexing-Save`, `ReviewMark-Indexing-LoadEvidence`. This scenario is tested by `Indexing_ReviewIndex_SaveAndLoad_RoundTrip`. -**Scenario**: `ReviewIndex.Scan` is called against a directory that contains no PDF files -(only a plain text file). +**Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex**: `ReviewIndex.Load` is called with a `none`-type evidence source. Expected outcome: Returns an empty index immediately; no file system access occurs. Requirement coverage: `ReviewMark-Indexing-CreateEvidence`. This scenario is tested by `Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex`. -**Expected**: Returns an empty index with no entries. +**Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex**: `ReviewIndex.Load` is called with a `url`-type source and a fake HTTP client returning a fixed JSON payload. Expected outcome: The index contains the entry from the JSON payload. Requirement coverage: `ReviewMark-Indexing-LoadEvidence`. This scenario is tested by `Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex`. -**Requirement coverage**: `ReviewMark-Indexing-ScanPdfEvidence` +**Indexing_SafePathCombine_WithTraversalInputs_Throws**: `PathHelpers.SafePathCombine` is called with path traversal inputs — first with a `..`-based relative path (`../../etc/sensitive`) and then with an absolute path. Expected outcome: `ArgumentException` is thrown in both cases; directory traversal and absolute-path injection are rejected. Boundary or error path: Path traversal prevention. Requirement coverage: `ReviewMark-Indexing-SafePathCombine`. This scenario is tested by `Indexing_SafePathCombine_WithTraversalInputs_Throws`. -#### Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex +**Indexing_ReviewIndex_Scan_WithNoPdfs_ReturnsEmptyIndex**: `ReviewIndex.Scan` is called against a directory that contains no PDF files (only a plain text file). Expected outcome: Returns an empty index with no entries. Requirement coverage: `ReviewMark-Indexing-ScanPdfEvidence`. This scenario is tested by `Indexing_ReviewIndex_Scan_WithNoPdfs_ReturnsEmptyIndex`. -**Scenario**: `ReviewIndex.Scan` is called against a directory containing a single PDF -with all required keyword metadata fields (`id`, `fingerprint`, `date`, `result`). +**Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex**: `ReviewIndex.Scan` is called against a directory containing a single PDF with all required keyword metadata fields (`id`, `fingerprint`, `date`, `result`). Expected outcome: Returns an index populated with the evidence entry extracted from the PDF. Requirement coverage: `ReviewMark-Indexing-ScanPdfEvidence`. This scenario is tested by `Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex`. -**Expected**: Returns an index populated with the evidence entry extracted from the PDF. +**Indexing_ReviewIndex_Load_MissingFile_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a `fileshare` source whose file path does not exist on disk. Expected outcome: `InvalidOperationException` is thrown with a message identifying the missing path. Boundary or error path: Missing evidence file. Requirement coverage: `ReviewMark-Indexing-LoadEvidence`. This scenario is tested by `Indexing_ReviewIndex_Load_MissingFile_ThrowsInvalidOperationException`. -**Requirement coverage**: `ReviewMark-Indexing-ScanPdfEvidence` +**Indexing_ReviewIndex_Load_MalformedJson_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a `fileshare` source pointing to a file that contains malformed (non-JSON) content. Expected outcome: `InvalidOperationException` is thrown describing the parse failure. Boundary or error path: Malformed JSON content. Requirement coverage: `ReviewMark-Indexing-LoadEvidence`. This scenario is tested by `Indexing_ReviewIndex_Load_MalformedJson_ThrowsInvalidOperationException`. ### Requirements Coverage - **ReviewMark-Indexing-LoadEvidence**: Indexing_SafePathCombine_WithIndexPath_LoadsIndex, - Indexing_ReviewIndex_SaveAndLoad_RoundTrip, Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex, - Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex + Indexing_ReviewIndex_Load_WithUrlSource_ReturnsPopulatedIndex, + Indexing_ReviewIndex_Load_MissingFile_ThrowsInvalidOperationException, + Indexing_ReviewIndex_Load_MalformedJson_ThrowsInvalidOperationException +- **ReviewMark-Indexing-CreateEvidence**: Indexing_ReviewIndex_Load_WithNoneSource_ReturnsEmptyIndex - **ReviewMark-Indexing-Save**: Indexing_ReviewIndex_SaveAndLoad_RoundTrip - **ReviewMark-Indexing-SafePathCombine**: Indexing_SafePathCombine_WithIndexPath_LoadsIndex, Indexing_SafePathCombine_WithTraversalInputs_Throws diff --git a/docs/verification/review-mark/indexing/path-helpers.md b/docs/verification/review-mark/indexing/path-helpers.md index d058595..e6ebcf5 100644 --- a/docs/verification/review-mark/indexing/path-helpers.md +++ b/docs/verification/review-mark/indexing/path-helpers.md @@ -1,114 +1,50 @@ -### PathHelpers Verification - -This document describes the unit-level verification design for the `PathHelpers` unit. -It defines the test scenarios, dependency usage, and requirement coverage for -`Indexing/PathHelpers.cs`. +### PathHelpers #### Verification Approach -`PathHelpers` is verified with unit tests in `PathHelpersTests.cs`. All methods are -pure functions, so tests pass string arguments directly and assert on return values. -No file system access or mocking is required. - -#### Dependencies - -`PathHelpers` has no runtime dependencies on other tool units and no I/O operations. - -#### Test Scenarios - -##### PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly - -**Scenario**: `SafePathCombine("/home/user/project", "subfolder/file.txt")` is called. - -**Expected**: Returns the result of `Path.Combine(basePath, relativePath)`. - -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` - -##### PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly - -**Scenario**: `SafePathCombine` is called with a multi-level relative path -(`"level1/level2/level3/file.txt"`). - -**Expected**: Returns the correctly combined nested path. - -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` - -##### PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly - -**Scenario**: `SafePathCombine` is called with a relative path that begins with `./`. - -**Expected**: Returns the combined path with the current-directory prefix preserved. +PathHelpers unit verification uses `PathHelpersTests.cs` to call the pure `SafePathCombine` function directly with valid paths, traversal attempts, absolute-path injections, and null values. The tests use no mocks or file-system setup because the unit's behavior is completely determined by its string inputs. -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` +#### Test Environment -##### PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10 and require no external services, file fixtures, or environment setup beyond the test runner. -**Scenario**: `SafePathCombine` is called with an empty relative path (`""`). +#### Acceptance Criteria -**Expected**: Returns the base path unchanged. +- All PathHelpers unit tests pass with zero failures. +- Each `ReviewMark-PathHelpers-*` requirement is traced to at least one scenario and test method. +- Valid combinations succeed, traversal attempts are rejected, and null inputs raise the documented exceptions. -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` - -##### PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException - -**Scenario**: `SafePathCombine("/home/user/project", "../etc/passwd")` is called. - -**Expected**: `ArgumentException` is thrown with a message containing "Invalid path component". - -**Boundary / error path**: Path traversal via leading `..` segment. - -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` - -##### PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException - -**Scenario**: `SafePathCombine` is called with a relative path containing `..` embedded -in the middle (e.g. `"subfolder/../../../etc/passwd"`). - -**Expected**: `ArgumentException` is thrown with a message containing "Invalid path component". - -**Boundary / error path**: Path traversal via embedded `..` segments. - -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` - -##### PathHelpers_SafePathCombine_AbsolutePath_ThrowsArgumentException - -**Scenario**: `SafePathCombine` is called where the relative path is an absolute path -(Unix: `/etc/passwd`; Windows: `C:\Windows\file.txt`). - -**Expected**: `ArgumentException` is thrown with a message containing "Invalid path component". - -**Boundary / error path**: Absolute path injection. - -**Requirement coverage**: `ReviewMark-PathHelpers-SafeCombine` +#### Test Scenarios -##### PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException +**PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly**: `SafePathCombine("/home/user/project", "subfolder/file.txt")` is called. Expected outcome: Returns the result of `Path.Combine(basePath, relativePath)`. Requirement coverage: `ReviewMark-PathHelpers-SafeCombine`. This scenario is tested by `PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly`. -**Scenario**: `SafePathCombine(null, "relative")` is called. +**PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly**: `SafePathCombine` is called with a multi-level relative path (`"level1/level2/level3/file.txt"`). Expected outcome: Returns the correctly combined nested path. Requirement coverage: `ReviewMark-PathHelpers-SafeCombine`. This scenario is tested by `PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly`. -**Expected**: `ArgumentNullException` is thrown. +**PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly**: `SafePathCombine` is called with a relative path that begins with `./`. Expected outcome: Returns the correctly combined path equivalent to `Path.Combine(basePath, relativePath)`, where `./` is consumed by the combination. Requirement coverage: `ReviewMark-PathHelpers-SafeCombine`. This scenario is tested by `PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly`. -**Boundary / error path**: Null base path rejection. +**PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath**: `SafePathCombine` is called with an empty relative path (`""`). Expected outcome: Returns the base path unchanged. Requirement coverage: `ReviewMark-PathHelpers-SafeCombine`. This scenario is tested by `PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath`. -**Requirement coverage**: `ReviewMark-PathHelpers-NullRejection` +**PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException**: `SafePathCombine("/home/user/project", "../etc/passwd")` is called. Expected outcome: `ArgumentException` is thrown with a message containing "Invalid path component". Boundary or error path: Path traversal via leading `..` segment. Requirement coverage: `ReviewMark-PathHelpers-TraversalRejection`. This scenario is tested by `PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException`. -##### PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException +**PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException**: `SafePathCombine` is called with a relative path containing `..` embedded in the middle (e.g. `"subfolder/../../../etc/passwd"`). Expected outcome: `ArgumentException` is thrown with a message containing "Invalid path component". Boundary or error path: Path traversal via embedded `..` segments. Requirement coverage: `ReviewMark-PathHelpers-TraversalRejection`. This scenario is tested by `PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException`. -**Scenario**: `SafePathCombine("base", null)` is called. +**PathHelpers_SafePathCombine_AbsoluteUnixPath_ThrowsArgumentException**: `SafePathCombine` is called where the relative path is a Unix absolute path (`/etc/passwd`). Runs on all platforms. Expected outcome: `ArgumentException` is thrown with a message containing "Invalid path component". Boundary or error path: Unix absolute path injection. Requirement coverage: `ReviewMark-PathHelpers-TraversalRejection`. This scenario is tested by `PathHelpers_SafePathCombine_AbsoluteUnixPath_ThrowsArgumentException`. -**Expected**: `ArgumentNullException` is thrown. +**PathHelpers_SafePathCombine_AbsoluteWindowsPath_ThrowsArgumentException**: `SafePathCombine` is called where the relative path is a Windows absolute path (`C:\Windows\System32\file.txt`). Runs on Windows only. Expected outcome: `ArgumentException` is thrown with a message containing "Invalid path component". Boundary or error path: Windows absolute path injection. Requirement coverage: `ReviewMark-PathHelpers-TraversalRejection`. This scenario is tested by `PathHelpers_SafePathCombine_AbsoluteWindowsPath_ThrowsArgumentException`. -**Boundary / error path**: Null relative path rejection. +**PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException**: `SafePathCombine(null, "relative")` is called. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null base path rejection. Requirement coverage: `ReviewMark-PathHelpers-NullRejection`. This scenario is tested by `PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException`. -**Requirement coverage**: `ReviewMark-PathHelpers-NullRejection` +**PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException**: `SafePathCombine("base", null)` is called. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null relative path rejection. Requirement coverage: `ReviewMark-PathHelpers-NullRejection`. This scenario is tested by `PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException`. #### Requirements Coverage - **ReviewMark-PathHelpers-SafeCombine**: PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly, PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly, PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly, - PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath, - PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException, + PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath +- **ReviewMark-PathHelpers-TraversalRejection**: PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException, PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException, - PathHelpers_SafePathCombine_AbsolutePath_ThrowsArgumentException + PathHelpers_SafePathCombine_AbsoluteUnixPath_ThrowsArgumentException, + PathHelpers_SafePathCombine_AbsoluteWindowsPath_ThrowsArgumentException (Windows only) - **ReviewMark-PathHelpers-NullRejection**: PathHelpers_SafePathCombine_NullBasePath_ThrowsArgumentNullException, PathHelpers_SafePathCombine_NullRelativePath_ThrowsArgumentNullException diff --git a/docs/verification/review-mark/indexing/review-index.md b/docs/verification/review-mark/indexing/review-index.md index 8c166bf..5b0ca25 100644 --- a/docs/verification/review-mark/indexing/review-index.md +++ b/docs/verification/review-mark/indexing/review-index.md @@ -1,382 +1,106 @@ -### ReviewIndex Verification - -This document describes the unit-level verification design for the `ReviewIndex` unit. -It defines the test scenarios, dependency usage, and requirement coverage for -`Indexing/ReviewIndex.cs`. +### ReviewIndex #### Verification Approach -`ReviewIndex` is verified with unit tests in `IndexTests.cs`. Tests exercise all source -types (none, fileshare, url), JSON round-trip serialization, PDF metadata extraction -(via the `Scan` method), and query operations (`GetEvidence`, `HasId`, `GetAllForId`). - -#### Dependencies - -| Mock / Stub | Reason | -| ----------------------- | ---------------------------------------------------------- | -| Temporary JSON files | Controlled fileshare evidence without real review PDFs | -| `FakeHttpMessageHandler`| Returns fixed JSON for URL source tests | -| Temporary PDF files | Real minimal PDF fixtures used for Scan metadata tests | - -#### Test Scenarios - -##### ReviewIndex_Empty_ReturnsEmptyIndex - -**Scenario**: `ReviewIndex.Empty` is called. - -**Expected**: Returns an index with no entries; all query methods report empty/no results. - -**Requirement coverage**: `ReviewMark-Index-Empty` - -##### ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException - -**Scenario**: `ReviewIndex.Load` is called with a `null` evidence source. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null input rejection. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException - -**Scenario**: `ReviewIndex.Load` is called with an evidence source whose type is not -recognised (e.g. `"unknown-type"`). - -**Expected**: `InvalidOperationException` is thrown. - -**Boundary / error path**: Unsupported source type. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex - -**Scenario**: `ReviewIndex.Load` is called with `EvidenceSource` type `none`. - -**Expected**: Returns an empty index with no entries. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource`, `ReviewMark-EvidenceSource-None` - -##### ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex - -**Scenario**: `ReviewIndex.Load(EvidenceSource, HttpClient)` is called with a `none`-type -source and a fake HTTP client that would fail if actually contacted. - -**Expected**: Returns an empty index without making any HTTP request. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource`, `ReviewMark-EvidenceSource-None` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile - -**Scenario**: `ReviewIndex.Load` is called with a fileshare source pointing to a valid -index JSON file written to a temporary path. - -**Expected**: Returns an index containing the entry from the JSON file. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex - -**Scenario**: `ReviewIndex.Load` is called with a fileshare source pointing to a valid -index JSON file containing two distinct review evidence entries. - -**Expected**: Returns a populated index with both entries; all fields match the JSON. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException - -**Scenario**: `ReviewIndex.Load` is called with a fileshare path that does not exist. - -**Expected**: `InvalidOperationException` is thrown. - -**Boundary / error path**: Missing file handling. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException - -**Scenario**: `ReviewIndex.Load` is called with a fileshare source pointing to a file -containing invalid JSON content. - -**Expected**: `InvalidOperationException` is thrown. - -**Boundary / error path**: Malformed JSON content. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex - -**Scenario**: `ReviewIndex.Load` is called with a fileshare source pointing to a JSON -file whose `reviews` array is empty. - -**Expected**: Returns an empty index with no entries. - -**Boundary / error path**: Empty reviews array. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries - -**Scenario**: `ReviewIndex.Load` is called with a JSON file containing three entries: -one missing `id`, one missing `fingerprint`, and one fully valid. - -**Expected**: Only the valid entry is present in the resulting index; the two -incomplete entries are silently skipped. - -**Boundary / error path**: Partial / incomplete entry handling. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex - -**Scenario**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns -a 200 OK with valid index JSON. - -**Expected**: Returns a populated index. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException - -**Scenario**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns -HTTP 404. - -**Expected**: `InvalidOperationException` is thrown identifying the failed URL. - -**Boundary / error path**: HTTP error response. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException +ReviewIndex unit verification uses `IndexTests.cs` to exercise all evidence-source loaders, save and reload round-trips, PDF scan behavior, and query operations. The tests use temporary JSON and PDF fixtures, a fake `HttpMessageHandler` for URL scenarios, and real `PathHelpers` and `GlobMatcher` interactions where the production unit depends on them. -**Scenario**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns -200 OK with malformed JSON. +#### Test Environment -**Expected**: `InvalidOperationException` is thrown describing the parse failure. +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10, create temporary JSON and PDF fixtures in-process, and inject a fake HTTP client so no real network access is required. -**Boundary / error path**: Malformed HTTP response body. +#### Acceptance Criteria -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` +- All ReviewIndex unit tests pass with zero failures. +- Each `ReviewMark-Index-*` requirement is traced to at least one scenario and test method. +- Loader selection, scan behavior, persistence, and evidence-query APIs all return the documented results for normal, boundary, and failure paths. -##### ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException - -**Scenario**: `ReviewIndex.Load(EvidenceSource, HttpClient)` is called with a null -`HttpClient`. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null HTTP client rejection. - -**Requirement coverage**: `ReviewMark-Index-EvidenceSource` - -##### ReviewIndex_Save_Stream_NullStream_ThrowsArgumentNullException - -**Scenario**: `ReviewIndex.Save(Stream)` is called with a null stream. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null stream rejection. - -**Requirement coverage**: `ReviewMark-Index-Save` - -##### ReviewIndex_Save_File_EmptyPath_ThrowsArgumentException - -**Scenario**: `ReviewIndex.Save(string)` is called with an empty string path. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Empty path rejection. - -**Requirement coverage**: `ReviewMark-Index-Save` - -##### ReviewIndex_Save_File_NullPath_ThrowsArgumentException - -**Scenario**: `ReviewIndex.Save(string)` is called with a `null` path. - -**Expected**: `ArgumentException` is thrown. - -**Boundary / error path**: Null path rejection. - -**Requirement coverage**: `ReviewMark-Index-Save` - -##### ReviewIndex_Save_RoundTrip_PreservesAllEntries - -**Scenario**: A populated index is saved to a stream and reloaded. - -**Expected**: All entries are preserved after the round-trip. - -**Requirement coverage**: `ReviewMark-Index-Save` - -##### ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty - -**Scenario**: `ReviewIndex.Scan` is called on an empty directory; no PDFs are present. - -**Expected**: Returns an empty index with no entries. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex - -**Scenario**: `ReviewIndex.Scan` is called on a directory containing a PDF with all four -required keyword metadata fields (`id`, `fingerprint`, `date`, `result`). - -**Expected**: Returns an index with one entry whose fields match the PDF keywords. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning - -**Scenario**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `id` entry. - -**Expected**: The PDF is skipped; the warning callback is invoked; the index remains empty. - -**Boundary / error path**: Missing required `id` field. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning - -**Scenario**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `fingerprint` entry. - -**Expected**: The PDF is skipped; the warning callback is invoked; the index remains empty. - -**Boundary / error path**: Missing required `fingerprint` field. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithMissingDate_SkipsWithWarning - -**Scenario**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `date` entry. - -**Expected**: The PDF is skipped; the warning callback is invoked; the index remains empty. - -**Boundary / error path**: Missing required `date` field. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithMissingResult_SkipsWithWarning - -**Scenario**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `result` entry. - -**Expected**: The PDF is skipped; the warning callback is invoked; the index remains empty. - -**Boundary / error path**: Missing required `result` field. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning - -**Scenario**: `ReviewIndex.Scan` processes a PDF with an empty Keywords field. - -**Expected**: The PDF is skipped; the warning callback is invoked; the index remains empty. - -**Boundary / error path**: Empty Keywords field. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries - -**Scenario**: `ReviewIndex.Scan` is called on a directory containing two PDFs, each with -distinct IDs and fingerprints. - -**Expected**: Returns an index with both entries; each entry's fields match its PDF's keywords. - -**Requirement coverage**: `ReviewMark-Index-PdfParsing` - -##### ReviewIndex_Scan_ClearsExistingEntries - -**Scenario**: An existing loaded index contains an entry; `ReviewIndex.Scan` is then called -on an empty directory. +#### Test Scenarios -**Expected**: The scan returns a fresh index that does not contain any entries from the -separately loaded index. +**ReviewIndex_Empty_ReturnsEmptyIndex**: `ReviewIndex.Empty` is called. Expected outcome: Returns an index with no entries; all query methods report empty/no results. Requirement coverage: `ReviewMark-Index-Empty`. This scenario is tested by `ReviewIndex_Empty_ReturnsEmptyIndex`. -**Boundary / error path**: Freshness — scan creates a new independent index. +**ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException**: `ReviewIndex.Load` is called with a `null` evidence source. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null input rejection. Requirement coverage: `ReviewMark-Index-EvidenceSource`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException`. -**Requirement coverage**: `ReviewMark-Index-Freshness` +**ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with an evidence source whose type is not recognized (e.g. `"unknown-type"`). Expected outcome: `InvalidOperationException` is thrown. Boundary or error path: Unsupported source type. Requirement coverage: `ReviewMark-Index-EvidenceSource`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException`. -##### ReviewIndex_GetEvidence_ExistingEntry_ReturnsEvidence +**ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex**: `ReviewIndex.Load` is called with `EvidenceSource` type `none`. Expected outcome: Returns an empty index with no entries. Requirement coverage: `ReviewMark-Index-EvidenceSource`, `ReviewMark-Index-EvidenceSource-None`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex`. -**Scenario**: `GetEvidence` is called with an ID and fingerprint that exist in the index. +**ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex**: `ReviewIndex.Load(EvidenceSource, HttpClient)` is called with a `none`-type source and a fake HTTP client that would fail if actually contacted. Expected outcome: Returns an empty index without making any HTTP request. Requirement coverage: `ReviewMark-Index-EvidenceSource`, `ReviewMark-Index-EvidenceSource-None`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex`. -**Expected**: Returns the matching evidence record. +**ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile**: `ReviewIndex.Load` is called with a fileshare source pointing to a valid index JSON file written to a temporary path. Expected outcome: Returns an index containing the entry from the JSON file. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile`. -**Requirement coverage**: `ReviewMark-Index-GetEvidence` +**ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex**: `ReviewIndex.Load` is called with a fileshare source pointing to a valid index JSON file containing two distinct review evidence entries. Expected outcome: Returns a populated index with both entries; all fields match the JSON. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex`. -##### ReviewIndex_GetEvidence_WrongFingerprint_ReturnsNull +**ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a fileshare path that does not exist. Expected outcome: `InvalidOperationException` is thrown. Boundary or error path: Missing file handling. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException`. -**Scenario**: `GetEvidence` is called with a known ID but wrong fingerprint. +**ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a fileshare source pointing to a file containing invalid JSON content. Expected outcome: `InvalidOperationException` is thrown. Boundary or error path: Malformed JSON content. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException`. -**Expected**: Returns null. +**ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex**: `ReviewIndex.Load` is called with a fileshare source pointing to a JSON file whose `reviews` array is empty. Expected outcome: Returns an empty index with no entries. Boundary or error path: Empty reviews array. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex`. -**Boundary / error path**: Fingerprint mismatch. +**ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries**: `ReviewIndex.Load` is called with a JSON file containing three entries: one missing `id`, one missing `fingerprint`, and one fully valid. Expected outcome: Only the valid entry is present in the resulting index; the two incomplete entries are silently skipped. Boundary or error path: Partial / incomplete entry handling. Requirement coverage: `ReviewMark-Index-LoadFromFile`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries`. -**Requirement coverage**: `ReviewMark-Index-GetEvidence` +**ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns a 200 OK with valid index JSON. Expected outcome: Returns a populated index. Requirement coverage: `ReviewMark-Index-LoadFromStream`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex`. -##### ReviewIndex_GetEvidence_UnknownId_ReturnsNull +**ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns HTTP 404. Expected outcome: `InvalidOperationException` is thrown identifying the failed URL. Boundary or error path: HTTP error response. Requirement coverage: `ReviewMark-Index-LoadFromStream`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException`. -**Scenario**: `GetEvidence` is called with an ID that does not exist in the index. +**ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException**: `ReviewIndex.Load` is called with a url source; the fake HTTP client returns 200 OK with malformed JSON. Expected outcome: `InvalidOperationException` is thrown describing the parse failure. Boundary or error path: Malformed HTTP response body. Requirement coverage: `ReviewMark-Index-LoadFromStream`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException`. -**Expected**: Returns null. +**ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException**: `ReviewIndex.Load(EvidenceSource, HttpClient)` is called with a null `HttpClient`. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null HTTP client rejection. Requirement coverage: `ReviewMark-Index-EvidenceSource`. This scenario is tested by `ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException`. -**Boundary / error path**: Unknown ID lookup. +**ReviewIndex_Save_Stream_NullStream_ThrowsArgumentNullException**: `ReviewIndex.Save(Stream)` is called with a null stream. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null stream rejection. Requirement coverage: `ReviewMark-Index-Save`. This scenario is tested by `ReviewIndex_Save_Stream_NullStream_ThrowsArgumentNullException`. -**Requirement coverage**: `ReviewMark-Index-GetEvidence` +**ReviewIndex_Save_File_EmptyPath_ThrowsArgumentException**: `ReviewIndex.Save(string)` is called with an empty string path. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Empty path rejection. Requirement coverage: `ReviewMark-Index-Save`. This scenario is tested by `ReviewIndex_Save_File_EmptyPath_ThrowsArgumentException`. -##### ReviewIndex_HasId_ExistingId_ReturnsTrue +**ReviewIndex_Save_File_NullPath_ThrowsArgumentException**: `ReviewIndex.Save(string)` is called with a `null` path. Expected outcome: `ArgumentException` is thrown. Boundary or error path: Null path rejection. Requirement coverage: `ReviewMark-Index-Save`. This scenario is tested by `ReviewIndex_Save_File_NullPath_ThrowsArgumentException`. -**Scenario**: `HasId` is called with an ID that exists in the index. +**ReviewIndex_Save_RoundTrip_PreservesAllEntries**: A populated index is saved to a stream and reloaded. Expected outcome: All entries are preserved after the round-trip. Requirement coverage: `ReviewMark-Index-Save`. This scenario is tested by `ReviewIndex_Save_RoundTrip_PreservesAllEntries`. -**Expected**: Returns true. +**ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty**: `ReviewIndex.Scan` is called on an empty directory; no PDFs are present. Expected outcome: Returns an empty index with no entries. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty`. -**Requirement coverage**: `ReviewMark-Index-HasId` +**ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex**: `ReviewIndex.Scan` is called on a directory containing a PDF with all four required keyword metadata fields (`id`, `fingerprint`, `date`, `result`). Expected outcome: Returns an index with one entry whose fields match the PDF keywords. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithValidMetadata_PopulatesIndex`. -##### ReviewIndex_HasId_UnknownId_ReturnsFalse +**ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `id` entry. Expected outcome: The PDF is skipped; the warning callback is invoked; the index remains empty. Boundary or error path: Missing required `id` field. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithMissingId_SkipsWithWarning`. -**Scenario**: `HasId` is called with an ID that does not exist. +**ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `fingerprint` entry. Expected outcome: The PDF is skipped; the warning callback is invoked; the index remains empty. Boundary or error path: Missing required `fingerprint` field. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithMissingFingerprint_SkipsWithWarning`. -**Expected**: Returns false. +**ReviewIndex_Scan_PdfWithMissingDate_SkipsWithWarning**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `date` entry. Expected outcome: The PDF is skipped; the warning callback is invoked; the index remains empty. Boundary or error path: Missing required `date` field. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithMissingDate_SkipsWithWarning`. -**Boundary / error path**: Unknown ID lookup. +**ReviewIndex_Scan_PdfWithMissingResult_SkipsWithWarning**: `ReviewIndex.Scan` processes a PDF whose Keywords field has no `result` entry. Expected outcome: The PDF is skipped; the warning callback is invoked; the index remains empty. Boundary or error path: Missing required `result` field. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithMissingResult_SkipsWithWarning`. -**Requirement coverage**: `ReviewMark-Index-HasId` +**ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning**: `ReviewIndex.Scan` processes a PDF with an empty Keywords field. Expected outcome: The PDF is skipped; the warning callback is invoked; the index remains empty. Boundary or error path: Empty Keywords field. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_PdfWithNoKeywords_SkipsWithWarning`. -##### ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries +**ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries**: `ReviewIndex.Scan` is called on a directory containing two PDFs, each with distinct IDs and fingerprints. Expected outcome: Returns an index with both entries; each entry's fields match its PDF's keywords. Requirement coverage: `ReviewMark-Index-PdfParsing`. This scenario is tested by `ReviewIndex_Scan_MultiplePdfs_PopulatesAllEntries`. -**Scenario**: `GetAllForId` is called with an ID that has two entries (different fingerprints). +**ReviewIndex_Scan_ClearsExistingEntries**: An existing loaded index contains an entry; `ReviewIndex.Scan` is then called on an empty directory. Expected outcome: The scan returns a fresh index that does not contain any entries from the separately loaded index. Boundary or error path: Freshness — scan creates a new independent index. Requirement coverage: `ReviewMark-Index-Freshness`. This scenario is tested by `ReviewIndex_Scan_ClearsExistingEntries`. -**Expected**: Returns a collection containing both entries. +**ReviewIndex_GetEvidence_ExistingEntry_ReturnsEvidence**: `GetEvidence` is called with an ID and fingerprint that exist in the index. Expected outcome: Returns the matching evidence record. Requirement coverage: `ReviewMark-Index-GetEvidence`. This scenario is tested by `ReviewIndex_GetEvidence_ExistingEntry_ReturnsEvidence`. -**Requirement coverage**: `ReviewMark-Index-GetAllForId` +**ReviewIndex_GetEvidence_WrongFingerprint_ReturnsNull**: `GetEvidence` is called with a known ID but wrong fingerprint. Expected outcome: Returns null. Boundary or error path: Fingerprint mismatch. Requirement coverage: `ReviewMark-Index-GetEvidence`. This scenario is tested by `ReviewIndex_GetEvidence_WrongFingerprint_ReturnsNull`. -##### ReviewIndex_GetAllForId_UnknownId_ReturnsEmptyList +**ReviewIndex_GetEvidence_UnknownId_ReturnsNull**: `GetEvidence` is called with an ID that does not exist in the index. Expected outcome: Returns null. Boundary or error path: Unknown ID lookup. Requirement coverage: `ReviewMark-Index-GetEvidence`. This scenario is tested by `ReviewIndex_GetEvidence_UnknownId_ReturnsNull`. -**Scenario**: `GetAllForId` is called with an ID that does not exist in the index. +**ReviewIndex_HasId_ExistingId_ReturnsTrue**: `HasId` is called with an ID that exists in the index. Expected outcome: Returns true. Requirement coverage: `ReviewMark-Index-HasId`. This scenario is tested by `ReviewIndex_HasId_ExistingId_ReturnsTrue`. -**Expected**: Returns an empty collection (not null). +**ReviewIndex_HasId_UnknownId_ReturnsFalse**: `HasId` is called with an ID that does not exist. Expected outcome: Returns false. Boundary or error path: Unknown ID lookup. Requirement coverage: `ReviewMark-Index-HasId`. This scenario is tested by `ReviewIndex_HasId_UnknownId_ReturnsFalse`. -**Boundary / error path**: Unknown ID — empty collection returned. +**ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries**: `GetAllForId` is called with an ID that has two entries (different fingerprints). Expected outcome: Returns a collection containing both entries. Requirement coverage: `ReviewMark-Index-GetAllForId`. This scenario is tested by `ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries`. -**Requirement coverage**: `ReviewMark-Index-GetAllForId` +**ReviewIndex_GetAllForId_UnknownId_ReturnsEmptyList**: `GetAllForId` is called with an ID that does not exist in the index. Expected outcome: Returns an empty collection (not null). Boundary or error path: Unknown ID — empty collection returned. Requirement coverage: `ReviewMark-Index-GetAllForId`. This scenario is tested by `ReviewIndex_GetAllForId_UnknownId_ReturnsEmptyList`. #### Requirements Coverage - **ReviewMark-Index-EvidenceSource**: ReviewIndex_Load_EvidenceSource_NullSource_ThrowsArgumentNullException, ReviewIndex_Load_EvidenceSource_UnknownType_ThrowsInvalidOperationException, - ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex, - ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex, - ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile, + ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException +- **ReviewMark-Index-LoadFromFile**: ReviewIndex_Load_EvidenceSource_Fileshare_LoadsFromFile, ReviewIndex_Load_EvidenceSource_Fileshare_ValidJson_ReturnsPopulatedIndex, ReviewIndex_Load_EvidenceSource_Fileshare_NonExistentFile_ThrowsInvalidOperationException, ReviewIndex_Load_EvidenceSource_Fileshare_InvalidJson_ThrowsInvalidOperationException, ReviewIndex_Load_EvidenceSource_Fileshare_EmptyReviews_ReturnsEmptyIndex, - ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries, - ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex, + ReviewIndex_Load_EvidenceSource_Fileshare_MissingRequiredFields_SkipsInvalidEntries +- **ReviewMark-Index-LoadFromStream**: ReviewIndex_Load_EvidenceSource_Url_SuccessResponse_LoadsIndex, ReviewIndex_Load_EvidenceSource_Url_NotFoundResponse_ThrowsInvalidOperationException, - ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException, - ReviewIndex_Load_EvidenceSource_NullHttpClient_ThrowsArgumentNullException -- **ReviewMark-EvidenceSource-None**: ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex, + ReviewIndex_Load_EvidenceSource_Url_InvalidJson_ThrowsInvalidOperationException +- **ReviewMark-Index-EvidenceSource-None**: ReviewIndex_Load_EvidenceSource_None_ReturnsEmptyIndex, ReviewIndex_Load_EvidenceSource_None_HttpClientOverload_ReturnsEmptyIndex - **ReviewMark-Index-Empty**: ReviewIndex_Empty_ReturnsEmptyIndex - **ReviewMark-Index-PdfParsing**: ReviewIndex_Scan_NoMatchingFiles_LeavesIndexEmpty, diff --git a/docs/verification/review-mark/program.md b/docs/verification/review-mark/program.md index 38820a4..d1d07bf 100644 --- a/docs/verification/review-mark/program.md +++ b/docs/verification/review-mark/program.md @@ -2,193 +2,69 @@ ### Verification Approach -`Program` unit tests are in `ProgramTests.cs`. Each test constructs a `Context` object -with controlled arguments, redirects `Console.Out` or `Console.Error` to a `StringWriter` -for output capture, calls `Program.Run`, and then asserts on captured output and exit code. +Program unit verification uses `ProgramTests.cs` to call `Program.Run` with real `Context` instances, captured console streams, and temporary definition, evidence, and output fixtures. The tests verify dispatch priority, banner and help behavior, lint-mode silence, elaboration, and enforce and warning handling without mocking downstream units. -### Dependencies +### Test Environment -| Mock / Stub | Reason | -| ----------------- | -------------------------------------------------------------- | -| `Context` | Constructed with controlled arguments and output capture | -| `StringWriter` | Replaces `Console.Out`/`Console.Error` for assertion | +Tests run under xUnit on .NET 8, 9, and 10 across Windows, Linux, and macOS. Console streams are redirected with `StringWriter`, temporary YAML and Markdown files are created as needed, and no external services or network access are required. -### Test Scenarios - -#### Program_Run_WithVersionFlag_DisplaysVersionOnly - -**Scenario**: `Program.Run` is called with `["--version"]`. - -**Expected**: Output equals the trimmed version string; "Copyright" and "ReviewMark version" -are absent; exit code is 0. - -**Requirement coverage**: `ReviewMark-Program-EntryPoint`, `ReviewMark-Program-Dispatch` - -#### Program_Version_ReturnsNonEmptyString - -**Scenario**: `Program.Version` property is accessed directly. - -**Expected**: Returns a non-null, non-empty, non-whitespace string. - -**Requirement coverage**: `ReviewMark-Program-EntryPoint` - -#### Program_Run_WithHelpFlag_DisplaysUsageInformation - -**Scenario**: `Program.Run` is called with `["--help"]`. - -**Expected**: Output contains "Usage:", "Options:", "--version", and "--help"; exit code is 0. - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_WithValidateFlag_RunsValidation - -**Scenario**: `Program.Run` is called with `["--validate"]`. - -**Expected**: Output contains "Total Tests:"; exit code is 0. - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_NoArguments_DisplaysDefaultBehavior - -**Scenario**: `Program.Run` is called with `[]`. - -**Expected**: Output contains "ReviewMark version" and "Copyright". - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_WithHelpFlag_IncludesElaborateOption - -**Scenario**: `Program.Run` is called with `["--help"]`. - -**Expected**: Help text includes "--elaborate". - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_WithHelpFlag_IncludesLintOption - -**Scenario**: `Program.Run` is called with `["--help"]`. - -**Expected**: Help text includes "--lint". - -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithElaborateFlag_OutputsElaboration +### Acceptance Criteria -**Scenario**: `Program.Run` is called with `--definition`, `--dir`, and `--elaborate Core-Logic`. +- All Program unit tests pass with zero failures. +- Each `ReviewMark-Program-*` requirement is traced to at least one scenario and test method. +- Dispatch, lint behavior, elaboration, and issue handling yield the documented output and exit-code behavior for both success and failure paths. -**Expected**: Output contains "Core-Logic", "Fingerprint", and "Files"; exit code is 0. - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_WithElaborateFlag_UnknownId_ReportsError - -**Scenario**: `Program.Run` is called with `--elaborate Unknown-Id` against a definition -that does not contain that ID. - -**Expected**: Exit code is 1. - -**Requirement coverage**: `ReviewMark-Program-Dispatch` - -#### Program_Run_WithLintFlag_ValidConfig_ReportsSuccess - -**Scenario**: `Program.Run` is called with `--lint --definition <valid-file>`. - -**Expected**: Exit code is 0; log file contains no error text. - -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithLintFlag_ValidConfig_SuppressesBanner - -**Scenario**: `Program.Run` is called with `--lint --definition <valid-file>`. - -**Expected**: Console output is empty; exit code is 0. The banner is suppressed because -lint mode itself suppresses the application banner, not because of a `--silent` flag. - -**Requirement coverage**: `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithLintFlag_MissingConfig_ReportsError - -**Scenario**: `Program.Run` is called with `--lint --definition <nonexistent-file>`. - -**Expected**: Exit code is 1; log output contains "error:" and the name of the missing file. - -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithLintFlag_DuplicateIds_ReportsError - -**Scenario**: `Program.Run` is called with `--lint --definition <file>` where the definition -contains two review sets with the same ID `Core-Logic`. - -**Expected**: Exit code is 1; log output contains "error:", "duplicate ID", and "Core-Logic". - -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithLintFlag_UnknownSourceType_ReportsError - -**Scenario**: `Program.Run` is called with `--lint --definition <file>` where the definition -has `evidence-source.type: ftp`. - -**Expected**: Exit code is 1; log output contains "error:", "ftp", and "not supported". - -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` - -#### Program_Run_WithLintFlag_CorruptedYaml_ReportsError - -**Scenario**: `Program.Run` is called with `--lint --definition <file>` where the definition -file contains invalid YAML syntax. - -**Expected**: Exit code is 1; log output contains "error:" and the definition file name with a line number. +### Test Scenarios -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` +**Program_Run_WithVersionFlag_DisplaysVersionOnly**: `Program.Run` is called with `["--version"]`. Expected outcome: Output equals the trimmed version string; "Copyright" and "ReviewMark version" are absent; exit code is 0. Requirement coverage: `ReviewMark-Program-ParseArguments`, `ReviewMark-Program-ExecuteOperation`, `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithVersionFlag_DisplaysVersionOnly`. -#### Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError +**Program_Version_ReturnsNonEmptyString**: `Program.Version` property is accessed directly. Expected outcome: Returns a non-null, non-empty, non-whitespace string. Requirement coverage: `ReviewMark-Program-ParseArguments`. This scenario is tested by `Program_Version_ReturnsNonEmptyString`. -**Scenario**: `Program.Run` is called with `--lint --definition <file>` where the definition -has no `evidence-source` block. +**Program_Run_WithHelpFlag_DisplaysUsageInformation**: `Program.Run` is called with `["--help"]`. Expected outcome: Output contains "Usage:", "Options:", "--version", and "--help"; exit code is 0. Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithHelpFlag_DisplaysUsageInformation`. -**Expected**: Exit code is 1; log output contains "error:", the definition file name, and "evidence-source". +**Program_Run_WithValidateFlag_RunsValidation**: `Program.Run` is called with `["--validate"]`. Expected outcome: Output contains "Total Tests:"; exit code is 0. Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithValidateFlag_RunsValidation`. -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` +**Program_Run_NoArguments_DisplaysDefaultBehavior**: `Program.Run` is called with `[]`. Expected outcome: Output contains "ReviewMark version" and "Copyright". Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_NoArguments_DisplaysDefaultBehavior`. -#### Program_Run_WithLintFlag_MultipleErrors_ReportsAll +**Program_Run_WithHelpFlag_IncludesElaborateOption**: `Program.Run` is called with `["--help"]`. Expected outcome: Help text includes "--elaborate". Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithHelpFlag_IncludesElaborateOption`. -**Scenario**: `Program.Run` is called with `--lint --definition <file>` where the definition -is missing `evidence-source` AND has duplicate review-set IDs. +**Program_Run_WithHelpFlag_IncludesLintOption**: `Program.Run` is called with `["--help"]`. Expected outcome: Help text includes "--lint". Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithHelpFlag_IncludesLintOption`. -**Expected**: Exit code is 1; log output contains BOTH "evidence-source" AND "duplicate ID", -proving all errors are accumulated in a single pass. +**Program_Run_WithElaborateFlag_OutputsElaboration**: `Program.Run` is called with `--definition`, `--dir`, and `--elaborate Core-Logic`. Expected outcome: Output contains "Core-Logic", "Fingerprint", and "Files"; exit code is 0. Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithElaborateFlag_OutputsElaboration`. -**Requirement coverage**: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity` +**Program_Run_WithElaborateFlag_UnknownId_ReportsError**: `Program.Run` is called with `--elaborate Unknown-Id` against a definition that does not contain that ID. Expected outcome: Exit code is 1. Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithElaborateFlag_UnknownId_ReportsError`. -#### Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError +**Program_Run_WithLintFlag_ValidConfig_ReportsSuccess**: `Program.Run` is called with `--lint --definition <valid-file>`. Expected outcome: Exit code is 0; log file contains no error text. Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_ValidConfig_ReportsSuccess`. -**Scenario**: `Program.Run` is called with `--definition <invalid-file> --plan <planfile>` where -the definition is missing `evidence-source`. +**Program_Run_WithLintFlag_ValidConfig_SuppressesBanner**: `Program.Run` is called with `--lint --definition <valid-file>`. Expected outcome: Console output is empty; exit code is 0. The banner is suppressed because lint mode itself suppresses the application banner, not because of a `--silent` flag. Requirement coverage: `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_ValidConfig_SuppressesBanner`. -**Expected**: Exit code is 1; log output contains "error:" and "evidence-source". +**Program_Run_WithLintFlag_MissingConfig_ReportsError**: `Program.Run` is called with `--lint --definition <nonexistent-file>`. Expected outcome: Exit code is 1; log output contains "error:" and the name of the missing file. Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_MissingConfig_ReportsError`. -**Requirement coverage**: `ReviewMark-Program-Dispatch` +**Program_Run_WithLintFlag_DuplicateIds_ReportsError**: `Program.Run` is called with `--lint --definition <file>` where the definition contains two review sets with the same ID `Core-Logic`. Expected outcome: Exit code is 1; log output contains "error:", "duplicate ID", and "Core-Logic". Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_DuplicateIds_ReportsError`. -#### Program_HandleIssues_WithEnforce_SetsExitCode1 +**Program_Run_WithLintFlag_UnknownSourceType_ReportsError**: `Program.Run` is called with `--lint --definition <file>` where the definition has `evidence-source.type: ftp`. Expected outcome: Exit code is 1; log output contains "error:", "ftp", and "not supported". Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_UnknownSourceType_ReportsError`. -**Scenario**: `Program.HandleIssues` is called with enforce=true and a non-empty issue list. +**Program_Run_WithLintFlag_CorruptedYaml_ReportsError**: `Program.Run` is called with `--lint --definition <file>` where the definition file contains invalid YAML syntax. Expected outcome: Exit code is 1; log output contains "error:" and the definition file name with a line number. Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_CorruptedYaml_ReportsError`. -**Expected**: Context exit code is set to 1. +**Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError**: `Program.Run` is called with `--lint --definition <file>` where the definition has no `evidence-source` block. Expected outcome: Exit code is 1; log output contains "error:", the definition file name, and "evidence-source". Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError`. -**Requirement coverage**: `ReviewMark-Program-HandleIssues` +**Program_Run_WithLintFlag_MultipleErrors_ReportsAll**: `Program.Run` is called with `--lint --definition <file>` where the definition is missing `evidence-source` AND has duplicate review-set IDs. Expected outcome: Exit code is 1; log output contains BOTH "evidence-source" AND "duplicate ID", proving all errors are accumulated in a single pass. Requirement coverage: `ReviewMark-Program-Dispatch`, `ReviewMark-Program-LintVerbosity`. This scenario is tested by `Program_Run_WithLintFlag_MultipleErrors_ReportsAll`. -#### Program_HandleIssues_WithoutEnforce_EmitsWarning +**Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError**: `Program.Run` is called with `--definition <invalid-file> --plan <planfile>` where the definition is missing `evidence-source`. Expected outcome: Exit code is 1; log output contains "error:" and "evidence-source". Requirement coverage: `ReviewMark-Program-Dispatch`. This scenario is tested by `Program_Run_WithDefinitionFlag_InvalidConfig_ReportsLintError`. -**Scenario**: `Program.HandleIssues` is called with enforce=false and a non-empty issue list. +**Program_HandleIssues_WithEnforce_SetsExitCode1**: `Program.Run` is called with `--report` and an empty evidence index, which triggers HandleIssues via the report path with enforce=true. Expected outcome: Context exit code is set to 1. Requirement coverage: `ReviewMark-Program-HandleIssues-Enforce`. This scenario is tested by `Program_HandleIssues_WithEnforce_SetsExitCode1`. -**Expected**: A warning is written to output; exit code remains 0. +**Program_HandleIssues_WithoutEnforce_EmitsWarning**: `Program.Run` is called with `--report` and an empty evidence index, which triggers HandleIssues via the report path with enforce=false. Expected outcome: A warning is written to output; exit code remains 0. Requirement coverage: `ReviewMark-Program-HandleIssues-Warn`. This scenario is tested by `Program_HandleIssues_WithoutEnforce_EmitsWarning`. -**Requirement coverage**: `ReviewMark-Program-HandleIssues` +**Program_Run_WithIndexFlag_ScansAndWritesIndexFile**: `Program.Run` is called with `["--index", <tempDir>]`. Expected outcome: An `index.json` file is written to the current directory; exit code is 0. Requirement coverage: `ReviewMark-Program-Index`. This scenario is tested by `Program_Run_WithIndexFlag_ScansAndWritesIndexFile`. ### Requirements Coverage -- **ReviewMark-Program-EntryPoint**: Program_Run_WithVersionFlag_DisplaysVersionOnly, - Program_Version_ReturnsNonEmptyString, Program_Run_WithHelpFlag_DisplaysUsageInformation +- **ReviewMark-Program-ParseArguments**: Program_Run_WithVersionFlag_DisplaysVersionOnly, + Program_Version_ReturnsNonEmptyString +- **ReviewMark-Program-ExecuteOperation**: Program_Run_WithVersionFlag_DisplaysVersionOnly, + Program_Run_WithHelpFlag_DisplaysUsageInformation +- **ReviewMark-Program-ExitCode**: Program_HandleIssues_WithEnforce_SetsExitCode1 - **ReviewMark-Program-Dispatch**: Program_Run_WithVersionFlag_DisplaysVersionOnly, Program_Run_WithHelpFlag_DisplaysUsageInformation, Program_Run_WithValidateFlag_RunsValidation, Program_Run_NoArguments_DisplaysDefaultBehavior, Program_Run_WithHelpFlag_IncludesElaborateOption, @@ -207,5 +83,6 @@ the definition is missing `evidence-source`. Program_Run_WithLintFlag_CorruptedYaml_ReportsError, Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError, Program_Run_WithLintFlag_MultipleErrors_ReportsAll -- **ReviewMark-Program-HandleIssues**: Program_HandleIssues_WithEnforce_SetsExitCode1, - Program_HandleIssues_WithoutEnforce_EmitsWarning +- **ReviewMark-Program-HandleIssues-Enforce**: Program_HandleIssues_WithEnforce_SetsExitCode1 +- **ReviewMark-Program-HandleIssues-Warn**: Program_HandleIssues_WithoutEnforce_EmitsWarning +- **ReviewMark-Program-Index**: Program_Run_WithIndexFlag_ScansAndWritesIndexFile diff --git a/docs/verification/review-mark/self-test.md b/docs/verification/review-mark/self-test.md index bbde825..038b34c 100644 --- a/docs/verification/review-mark/self-test.md +++ b/docs/verification/review-mark/self-test.md @@ -2,65 +2,32 @@ ### Verification Approach -The SelfTest subsystem is verified through `SelfTestTests.cs`, which exercises the -`Validation` class by running it against the built assembly's own definition and -checking that it returns a passing result, generates results files in both TRX and -JUnit XML formats, and sets a non-zero exit code when an error occurs. +SelfTest subsystem verification uses `SelfTestTests.cs` to exercise `Validation.Run` through real `Context` instances, captured console output, and generated TRX and JUnit results files. The tests rely on the built ReviewMark assembly and verify the subsystem's qualification behavior, results-output formats, console summary, and exit-code handling without replacing the underlying validation runner. -All SelfTest tests are run sequentially (parallelisation is disabled at assembly -level) because they exercise real file system and process state. +### Test Environment -### Dependencies +Tests run under xUnit on .NET 8, 9, and 10 across Windows, Linux, and macOS. They capture console output in-process, create temporary results paths on demand, and require the built ReviewMark assembly to be present so self-validation can execute its internal suite. -| Dependency | Reason | -| --------------------- | ---------------------------------------------------------- | -| Built assembly output | Self-test is integration-level; requires a real build | +### Acceptance Criteria -### Test Scenarios - -#### SelfTest_Run_AllTestsPass_ExitCodeIsZero - -**Scenario**: `Validation.Run` is called with `--validate`; all built-in validation -tests pass in the correctly functioning environment. - -**Expected**: Exit code is 0; console output contains `Total Tests:`, `Passed:`, and `Failed:`. - -**Requirement coverage**: `ReviewMark-SelfTest-Qualification`, `ReviewMark-SelfTest-ConsoleSummary` - -#### SelfTest_Run_GeneratesResultsFile - -**Scenario**: `Validation.Run` is called with `--validate --results <path>.trx`; the -specified TRX results file path does not exist before the run. +- All SelfTest subsystem integration tests pass with zero failures. +- Each `ReviewMark-SelfTest-*` requirement is traced to at least one scenario and test method. +- Self-validation writes the expected console summary and supported results-file formats, and it drives a non-zero exit code when validation errors occur. -**Expected**: The file is created; its root XML element is `TestRun` (TRX format). - -**Requirement coverage**: `ReviewMark-SelfTest-ResultsOutput` - -#### SelfTest_Run_GeneratesJUnitResultsFile - -**Scenario**: `Validation.Run` is called with `--validate --results <path>.xml`; the -specified JUnit XML results file path does not exist before the run. - -**Expected**: The file is created; its content contains `testsuites` (JUnit format). - -**Requirement coverage**: `ReviewMark-SelfTest-ResultsOutput` - -#### SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero +### Test Scenarios -**Scenario**: `Validation.Run` is called with `--validate --results unsupported-format.csv`; -the `.csv` extension is not a supported results format. +**SelfTest_Run_AllTestsPass_ExitCodeIsZero**: `Validation.Run` is called with `--validate`; all built-in validation tests pass in the correctly functioning environment. Expected outcome: Exit code is 0; console output contains `Total Tests:`, `Passed:`, and `Failed:`. Requirement coverage: `ReviewMark-SelfTest-Qualification`, `ReviewMark-SelfTest-ConsoleSummary`. This scenario is tested by `SelfTest_Run_AllTestsPass_ExitCodeIsZero`. -**Expected**: Exit code is non-zero; the unsupported extension triggers a `WriteError` call -via the same exit-code path used for test failures. +**SelfTest_Run_WithTrxResultsFile_WritesFile**: `Validation.Run` is called with `--validate --results <path>.trx`; the specified TRX results file path does not exist before the run. Expected outcome: The file is created; its root XML element is `TestRun` (TRX format). Requirement coverage: `ReviewMark-SelfTest-ResultsOutput-Trx`. This scenario is tested by `SelfTest_Run_WithTrxResultsFile_WritesFile`. -**Boundary / error path**: Unsupported results file extension. +**SelfTest_Run_WithJUnitResultsFile_WritesFile**: `Validation.Run` is called with `--validate --results <path>.xml`; the specified JUnit XML results file path does not exist before the run. Expected outcome: The file is created; its content contains `testsuites` (JUnit format). Requirement coverage: `ReviewMark-SelfTest-ResultsOutput-Junit`. This scenario is tested by `SelfTest_Run_WithJUnitResultsFile_WritesFile`. -**Requirement coverage**: `ReviewMark-SelfTest-ExitCodeOnFailure` +**SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero**: `Validation.Run` is called with `--validate --results unsupported-format.csv`; the `.csv` extension is not a supported results format. Expected outcome: Exit code is non-zero; the unsupported extension triggers a `WriteError` call via the same exit-code path used for test failures. Boundary or error path: Unsupported results file extension. Requirement coverage: `ReviewMark-SelfTest-ExitCodeOnFailure`. This scenario is tested by `SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero`. ### Requirements Coverage - **ReviewMark-SelfTest-Qualification**: SelfTest_Run_AllTestsPass_ExitCodeIsZero -- **ReviewMark-SelfTest-ResultsOutput**: SelfTest_Run_GeneratesResultsFile, - SelfTest_Run_GeneratesJUnitResultsFile +- **ReviewMark-SelfTest-ResultsOutput-Trx**: SelfTest_Run_WithTrxResultsFile_WritesFile +- **ReviewMark-SelfTest-ResultsOutput-Junit**: SelfTest_Run_WithJUnitResultsFile_WritesFile - **ReviewMark-SelfTest-ExitCodeOnFailure**: SelfTest_Run_UnsupportedResultsFormat_ExitCodeIsNonZero - **ReviewMark-SelfTest-ConsoleSummary**: SelfTest_Run_AllTestsPass_ExitCodeIsZero diff --git a/docs/verification/review-mark/self-test/validation.md b/docs/verification/review-mark/self-test/validation.md index d39555e..f7e6d12 100644 --- a/docs/verification/review-mark/self-test/validation.md +++ b/docs/verification/review-mark/self-test/validation.md @@ -1,99 +1,36 @@ -### Validation Verification - -This document describes the unit-level verification design for the `Validation` unit. -It defines the test scenarios, dependency usage, and requirement coverage for -`SelfTest/Validation.cs`. +### Validation #### Verification Approach -`Validation` is verified with unit tests in `ValidationTests.cs`. Tests call -`Validation.Run(Context)` with controlled `Context` instances created via -`Context.Create` with specific argument arrays, capture console output using -`StringWriter`, and assert on exit codes, output content, and results file presence. - -#### Dependencies - -| Dependency | Reason | -| ---------------------------- | ------------------------------------------------------------- | -| `Context` (real) | Parsing and state are exercised via the real `Context` class | -| Captured `Console.Out` | Allows tests to assert on human-readable output | -| Temporary files/directories | Results file tests need a real writable path | - -#### Test Scenarios - -##### Validation_Run_NullContext_ThrowsArgumentNullException - -**Scenario**: `Validation.Run` is called with a `null` context. - -**Expected**: `ArgumentNullException` is thrown. - -**Boundary / error path**: Null input rejection. - -**Requirement coverage**: `ReviewMark-Validation-Run` - -##### Validation_Run_WritesValidationHeader - -**Scenario**: `Validation.Run` is called with `["--validate"]`; console output is captured. - -**Expected**: Output contains `DEMA Consulting ReviewMark`, `Tool Version`, and `Machine Name`. - -**Requirement coverage**: `ReviewMark-Validation-Run` +Validation unit verification uses `ValidationTests.cs` to call `Validation.Run` with real `Context` instances, captured console and error streams, and temporary results-file paths. The tests verify the validation header, summary output, supported TRX and JUnit serialization, parent-directory creation, and error handling for unsupported results-file extensions. -##### Validation_Run_WritesSummaryWithTotalTests +#### Test Environment -**Scenario**: `Validation.Run` is called with `["--validate"]`; console output is captured. +N/A - standard test environment. Tests run under xUnit on .NET 8, 9, and 10, capture console output with `StringWriter`, create temporary results paths in-process, and require the built ReviewMark assembly so the internal validation suite can execute. -**Expected**: Output contains `Total Tests:`, `Passed:`, and `Failed:`. +#### Acceptance Criteria -**Requirement coverage**: `ReviewMark-Validation-Run` +- All Validation unit tests pass with zero failures. +- Each `ReviewMark-Validation-*` requirement is traced to at least one scenario and test method. +- Validation output, results-file generation, and error handling all produce the documented exit-code and artifact behavior. -##### Validation_Run_AllTestsPass_ExitCodeIsZero - -**Scenario**: `Validation.Run` is called with `["--validate"]` in a correctly functioning -build environment. - -**Expected**: `context.ExitCode` is 0 after the run completes. - -**Requirement coverage**: `ReviewMark-Validation-Run` - -##### Validation_Run_WithTrxResultsFile_WritesFile - -**Scenario**: `Validation.Run` is called with `["--validate", "--results", "<path>.trx"]`. - -**Expected**: The TRX file is created, is non-empty, and contains the text `TestRun`. - -**Requirement coverage**: `ReviewMark-Validation-ResultsFile` - -##### Validation_Run_WithXmlResultsFile_WritesFile - -**Scenario**: `Validation.Run` is called with `["--validate", "--results", "<path>.xml"]`. - -**Expected**: The JUnit XML file is created, is non-empty, and contains the text `testsuites`. - -**Requirement coverage**: `ReviewMark-Validation-ResultsFile` - -##### Validation_Run_WithResultsFileInNewDirectory_CreatesDirectory - -**Scenario**: `Validation.Run` is called with a results path whose parent directory does -not exist yet (e.g. `<tempDir>/output/results.trx`). +#### Test Scenarios -**Expected**: The parent directory is created and the results file is written successfully. +**Validation_Run_NullContext_ThrowsArgumentNullException**: `Validation.Run` is called with a `null` context. Expected outcome: `ArgumentNullException` is thrown. Boundary or error path: Null input rejection. Requirement coverage: `ReviewMark-Validation-Run`. This scenario is tested by `Validation_Run_NullContext_ThrowsArgumentNullException`. -**Boundary / error path**: Parent directory creation. +**Validation_Run_WritesValidationHeader**: `Validation.Run` is called with `["--validate"]`; console output is captured. Expected outcome: Output contains `DEMA Consulting ReviewMark`, `Tool Version`, and `Machine Name`. Requirement coverage: `ReviewMark-Validation-Run`. This scenario is tested by `Validation_Run_WritesValidationHeader`. -**Requirement coverage**: `ReviewMark-Validation-ResultsFile` +**Validation_Run_WritesSummaryWithTotalTests**: `Validation.Run` is called with `["--validate"]`; console output is captured. Expected outcome: Output contains `Total Tests:`, `Passed:`, and `Failed:`. Requirement coverage: `ReviewMark-Validation-Run`. This scenario is tested by `Validation_Run_WritesSummaryWithTotalTests`. -##### Validation_Run_WithUnsupportedResultsFileExtension_WritesError +**Validation_Run_AllTestsPass_ExitCodeIsZero**: `Validation.Run` is called with `["--validate"]` in a correctly functioning build environment. Expected outcome: `context.ExitCode` is 0 after the run completes. Requirement coverage: `ReviewMark-Validation-Run`. This scenario is tested by `Validation_Run_AllTestsPass_ExitCodeIsZero`. -**Scenario**: `Validation.Run` is called with `["--validate", "--results", "results.csv"]`. -The `.csv` extension is not supported. +**Validation_Run_WithTrxResultsFile_WritesFile**: `Validation.Run` is called with `["--validate", "--results", "<path>.trx"]`. Expected outcome: The TRX file is created, is non-empty, and contains the text `TestRun`. Requirement coverage: `ReviewMark-Validation-ResultsFile`. This scenario is tested by `Validation_Run_WithTrxResultsFile_WritesFile`. -**Expected**: No results file is created; `context.ExitCode` is non-zero; error output -contains a message about the unsupported extension. +**Validation_Run_WithXmlResultsFile_WritesFile**: `Validation.Run` is called with `["--validate", "--results", "<path>.xml"]`. Expected outcome: The JUnit XML file is created, is non-empty, and contains the text `testsuites`. Requirement coverage: `ReviewMark-Validation-ResultsFile`. This scenario is tested by `Validation_Run_WithXmlResultsFile_WritesFile`. -**Boundary / error path**: Unsupported results file extension. +**Validation_Run_WithResultsFileInNewDirectory_CreatesDirectory**: `Validation.Run` is called with a results path whose parent directory does not exist yet (e.g. `<tempDir>/output/results.trx`). Expected outcome: The parent directory is created and the results file is written successfully. Boundary or error path: Parent directory creation. Requirement coverage: `ReviewMark-Validation-ResultsFile`. This scenario is tested by `Validation_Run_WithResultsFileInNewDirectory_CreatesDirectory`. -**Requirement coverage**: `ReviewMark-Validation-ResultsFile` +**Validation_Run_WithUnsupportedResultsFileExtension_WritesError**: `Validation.Run` is called with `["--validate", "--results", "results.csv"]`. The `.csv` extension is not supported. Expected outcome: No results file is created; `context.ExitCode` is non-zero; error output contains a message about the unsupported extension. Boundary or error path: Unsupported results file extension. Requirement coverage: `ReviewMark-Validation-ResultsFile`. This scenario is tested by `Validation_Run_WithUnsupportedResultsFileExtension_WritesError`. #### Requirements Coverage diff --git a/docs/verification/title.txt b/docs/verification/title.txt index 56af19b..ede6f0e 100644 --- a/docs/verification/title.txt +++ b/docs/verification/title.txt @@ -1,13 +1,17 @@ --- -title: ReviewMark Verification Design Document -subtitle: File-Review Evidence Management Tool -author: DEMA Consulting -description: Verification design document for ReviewMark +title: "ReviewMark Verification Design Document" +subtitle: "Automated file-review evidence management" +author: "DEMA Consulting" +date: "2026-05-23" +description: "Verification Design Document for ReviewMark" lang: en-US keywords: - ReviewMark - - .NET - - Command-Line Tool - Verification - - Verification Design Document + - Testing + - .NET + - C# + - xUnit + - ReqStream + - Pandoc --- diff --git a/requirements.yaml b/requirements.yaml index 99cdc9d..a6ba8aa 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -3,15 +3,15 @@ includes: - docs/reqstream/review-mark.yaml - docs/reqstream/review-mark/platform-requirements.yaml - docs/reqstream/review-mark/program.yaml - - docs/reqstream/review-mark/cli/cli.yaml + - docs/reqstream/review-mark/cli.yaml - docs/reqstream/review-mark/cli/context.yaml - - docs/reqstream/review-mark/configuration/configuration.yaml + - docs/reqstream/review-mark/configuration.yaml - docs/reqstream/review-mark/configuration/review-mark-configuration.yaml - docs/reqstream/review-mark/configuration/glob-matcher.yaml - - docs/reqstream/review-mark/indexing/indexing.yaml + - docs/reqstream/review-mark/indexing.yaml - docs/reqstream/review-mark/indexing/review-index.yaml - docs/reqstream/review-mark/indexing/path-helpers.yaml - - docs/reqstream/review-mark/self-test/self-test.yaml + - docs/reqstream/review-mark/self-test.yaml - docs/reqstream/review-mark/self-test/validation.yaml - docs/reqstream/ots/xunit.yaml - docs/reqstream/ots/reviewmark.yaml @@ -23,3 +23,7 @@ includes: - docs/reqstream/ots/pandoc.yaml - docs/reqstream/ots/weasyprint.yaml - docs/reqstream/ots/fileassert.yaml + - docs/reqstream/ots/dema-consulting-test-results.yaml + - docs/reqstream/ots/microsoft-extensions-file-system-globbing.yaml + - docs/reqstream/ots/pdfsharp.yaml + - docs/reqstream/ots/yamldotnet.yaml diff --git a/src/DemaConsulting.ReviewMark/Cli/Context.cs b/src/DemaConsulting.ReviewMark/Cli/Context.cs index 3692e6f..c23af5b 100644 --- a/src/DemaConsulting.ReviewMark/Cli/Context.cs +++ b/src/DemaConsulting.ReviewMark/Cli/Context.cs @@ -214,6 +214,7 @@ public static Context Create(string[] args) /// Opens the log file for writing /// </summary> /// <param name="logFile">Log file path</param> + /// <exception cref="InvalidOperationException">Thrown when the log file cannot be opened due to a file-system error.</exception> private void OpenLogFile(string logFile) { try diff --git a/src/DemaConsulting.ReviewMark/Configuration/GlobMatcher.cs b/src/DemaConsulting.ReviewMark/Configuration/GlobMatcher.cs index 3c06730..2b5086c 100644 --- a/src/DemaConsulting.ReviewMark/Configuration/GlobMatcher.cs +++ b/src/DemaConsulting.ReviewMark/Configuration/GlobMatcher.cs @@ -44,6 +44,18 @@ internal static class GlobMatcher /// </returns> /// <remarks> /// This method is stateless and thread-safe; each call creates its own internal state. + /// <para> + /// Per-pattern <see cref="Matcher" /> instances are used rather than a single + /// combined matcher in order to preserve declaration-order semantics: each pattern + /// is applied independently so that a later include can re-add files removed by an + /// earlier exclude, and vice versa. A single combined matcher cannot enforce this + /// ordered include/exclude contract. + /// </para> + /// <para> + /// A <see cref="HashSet{T}" /> accumulator is used to collect matched paths across + /// multiple inclusion patterns and deduplicate results, so that a file matched by + /// more than one include pattern appears only once in the returned list. + /// </para> /// </remarks> /// <exception cref="ArgumentNullException"> /// Thrown when <paramref name="baseDirectory" /> or <paramref name="patterns" /> is <c>null</c>. diff --git a/src/DemaConsulting.ReviewMark/Configuration/ReviewMarkConfiguration.cs b/src/DemaConsulting.ReviewMark/Configuration/ReviewMarkConfiguration.cs index 6a161d2..a590c84 100644 --- a/src/DemaConsulting.ReviewMark/Configuration/ReviewMarkConfiguration.cs +++ b/src/DemaConsulting.ReviewMark/Configuration/ReviewMarkConfiguration.cs @@ -743,7 +743,7 @@ internal IReadOnlyList<string> GetNeedsReviewFiles(string directory) => /// A <see cref="ReviewPlanResult" /> containing the Markdown text and a flag /// indicating whether any files requiring review are uncovered. /// </returns> - /// <exception cref="ArgumentException">Thrown when <paramref name="directory" /> is null or whitespace.</exception> + /// <exception cref="ArgumentException">Thrown when <paramref name="directory" /> is null, empty, or whitespace.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when <paramref name="markdownDepth" /> is less than 1 or greater than 5 /// (subheadings at depth+1 would exceed the maximum Markdown heading level of 6). @@ -833,7 +833,7 @@ internal ReviewPlanResult PublishReviewPlan(string directory, int markdownDepth /// indicating whether any reviews are failed, stale, or missing. /// </returns> /// <exception cref="ArgumentNullException">Thrown when <paramref name="index" /> is <c>null</c>.</exception> - /// <exception cref="ArgumentException">Thrown when <paramref name="directory" /> is null or whitespace.</exception> + /// <exception cref="ArgumentException">Thrown when <paramref name="directory" /> is null, empty, or whitespace.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when <paramref name="markdownDepth" /> is less than 1 or greater than 5 /// (subheadings at depth+1 would exceed the maximum Markdown heading level of 6). diff --git a/src/DemaConsulting.ReviewMark/Indexing/ReviewIndex.cs b/src/DemaConsulting.ReviewMark/Indexing/ReviewIndex.cs index 776bef8..915001a 100644 --- a/src/DemaConsulting.ReviewMark/Indexing/ReviewIndex.cs +++ b/src/DemaConsulting.ReviewMark/Indexing/ReviewIndex.cs @@ -108,6 +108,12 @@ internal sealed record ReviewEvidence( /// content fingerprint. Supports loading from and saving to the /// <c>index.json</c> file, and rebuilding the index from scanned PDF evidence. /// </summary> +/// <remarks> +/// <c>ReviewIndex</c> is immutable after construction: the internal evidence dictionary +/// is populated during construction via a static factory method and is never modified +/// afterward. Instances are therefore safe to share across threads without +/// external synchronization. +/// </remarks> internal sealed class ReviewIndex { // --------------------------------------------------------------------------- @@ -274,7 +280,7 @@ private static ReviewIndex LoadFromFile(string filePath) /// <exception cref="ArgumentNullException"> /// Thrown when <paramref name="stream" /> is <c>null</c>. /// </exception> - /// <exception cref="ArgumentException"> + /// <exception cref="InvalidOperationException"> /// Thrown when the stream does not contain valid JSON. /// </exception> private static ReviewIndex LoadFromStream(Stream stream) @@ -290,7 +296,7 @@ private static ReviewIndex LoadFromStream(Stream stream) } catch (JsonException ex) { - throw new ArgumentException($"Invalid JSON content in review index: {ex.Message}", nameof(stream), ex); + throw new InvalidOperationException($"Invalid JSON content in review index: {ex.Message}", ex); } // Build and populate a new index from the deserialized model @@ -387,7 +393,7 @@ private static ReviewIndex LoadFromUrl(string url, HttpClient httpClient) { return LoadFromStream(stream); } - catch (ArgumentException ex) + catch (InvalidOperationException ex) when (ex.InnerException is JsonException) { throw new InvalidOperationException( $"Failed to parse review index downloaded from '{url}': {ex.Message}", ex); diff --git a/src/DemaConsulting.ReviewMark/Program.cs b/src/DemaConsulting.ReviewMark/Program.cs index c0dba90..0d87be4 100644 --- a/src/DemaConsulting.ReviewMark/Program.cs +++ b/src/DemaConsulting.ReviewMark/Program.cs @@ -34,6 +34,20 @@ internal static class Program /// <summary> /// Gets the application version string. /// </summary> + /// <value> + /// A semantic version string (e.g., <c>1.2.3</c>); may include a git + /// hash suffix when build metadata is present + /// (e.g., <c>1.2.3+abc1234</c>). + /// </value> + /// <remarks> + /// Resolves the version through a fallback chain: tries + /// <see cref="AssemblyInformationalVersionAttribute"/> first + /// (InformationalVersion, which may include a git hash suffix), then + /// falls back to <see cref="System.Reflection.AssemblyName.Version"/> + /// (AssemblyVersionAttribute), and finally falls back to + /// <c>"0.0.0"</c> when neither attribute is present. The property is + /// stateless and thread-safe. + /// </remarks> public static string Version { get @@ -51,8 +65,20 @@ public static string Version /// <summary> /// Main entry point for ReviewMark. /// </summary> + /// <remarks> + /// Implements a three-tier exception-handling design: (1) expected + /// application-level exceptions (<see cref="ArgumentException"/> and + /// <see cref="InvalidOperationException"/>) are caught and reported as + /// clean user-facing errors, returning exit code 1 without a stack + /// trace; (2) any other unexpected exception is caught, its message + /// written to <see cref="Console.Error"/>, and then rethrown. + /// Unexpected exceptions are rethrown rather than swallowed so that + /// the process exits with a non-zero code and the full stack trace is + /// preserved for diagnostics. + /// </remarks> /// <param name="args">Command-line arguments.</param> /// <returns>Exit code: 0 for success, non-zero for failure.</returns> + /// <exception cref="Exception">Thrown when an unexpected error occurs in any subsystem; the exception message is written to Console.Error before rethrowing.</exception> private static int Main(string[] args) { try @@ -89,7 +115,22 @@ private static int Main(string[] args) /// <summary> /// Runs the program logic based on the provided context. /// </summary> + /// <remarks> + /// Implements a priority-ordered dispatch so that diagnostic flags + /// (<c>--version</c>, <c>--help</c>, <c>--validate</c>, <c>--lint</c>) + /// are always evaluated before any output-generating actions, preventing + /// unintended side effects when flags are combined. The application + /// banner is printed between priority-1 (<c>--version</c>) and + /// priority-3 (<c>--help</c>) so it appears for every interactive + /// invocation but is suppressed for <c>--lint</c> to keep that output + /// clean for CI pipelines. + /// </remarks> /// <param name="context">The context containing command line arguments and program state.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when a file write operation fails; propagated from + /// <see cref="RunDefinitionLogic"/> (plan or report file) or + /// <see cref="RunIndexLogic"/> (index file). + /// </exception> public static void Run(Context context) { // Priority 1: Version query @@ -131,8 +172,16 @@ public static void Run(Context context) } /// <summary> - /// Prints the application banner. + /// Prints the application banner to the context output. /// </summary> + /// <remarks> + /// The banner is printed on every invocation except <c>--version</c> + /// and <c>--lint</c>. Suppressing it for <c>--lint</c> keeps the + /// output parseable by linting scripts and CI pipelines (signal, no + /// noise). Suppressing it for <c>--version</c> ensures the output + /// contains only the bare version string, which tooling can capture + /// directly. + /// </remarks> /// <param name="context">The context for output.</param> private static void PrintBanner(Context context) { @@ -142,8 +191,15 @@ private static void PrintBanner(Context context) } /// <summary> - /// Prints usage information. + /// Prints usage information to the context output. /// </summary> + /// <remarks> + /// Called only when <c>--help</c> is set. All available flags and + /// arguments are listed so users can discover the full CLI surface + /// without consulting external documentation. The application banner + /// has already been printed before this method is reached, so it is + /// not repeated here. + /// </remarks> /// <param name="context">The context for output.</param> private static void PrintHelp(Context context) { @@ -171,8 +227,17 @@ private static void PrintHelp(Context context) } /// <summary> - /// Runs the lint logic to validate the definition file. + /// Runs the lint logic to validate the definition file and report issues. /// </summary> + /// <remarks> + /// Designed for machine-readable output: no banner, no summary message. + /// Silence means the definition file is valid, which keeps the output + /// clean for integration with linting scripts and CI pipelines. + /// Issue messages are emitted via <see cref="Context.WriteError"/> for + /// errors (which also sets the exit code to 1) and + /// <see cref="Context.WriteLine"/> for warnings, so the caller can + /// distinguish severity without parsing message content. + /// </remarks> /// <param name="context">The context containing command line arguments and program state.</param> private static void RunLintLogic(Context context) { @@ -232,10 +297,25 @@ private static void RunToolLogic(Context context) } /// <summary> - /// Runs the index scanning logic. + /// Runs the index scanning logic to build an evidence index from PDF files. /// </summary> + /// <remarks> + /// Scans all PDFs matched by <c>context.IndexPaths</c> under + /// <paramref name="directory"/>, extracts review-evidence metadata from + /// each file's PDF Keywords field, and writes the resulting index to + /// <c>index.json</c> in the working directory. Per-file scan failures + /// are forwarded as warnings via <c>context.WriteLine</c> so a single + /// unreadable PDF does not abort the entire scan. I/O failures when + /// writing <c>index.json</c> are propagated as + /// <see cref="InvalidOperationException"/> to <c>Main()</c>, which + /// prints the message and returns exit code 1. + /// </remarks> /// <param name="context">The context for output.</param> - /// <param name="directory">The working directory.</param> + /// <param name="directory">The working directory used as the scan root and index output location.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when the index file cannot be written to disk; propagated from + /// <see cref="ReviewIndex.Save(string)"/>. + /// </exception> private static void RunIndexLogic(Context context, string directory) { // Scan PDF evidence files and save the resulting index @@ -252,6 +332,12 @@ private static void RunIndexLogic(Context context, string directory) /// <param name="context">The context for output.</param> /// <param name="directory">The working directory.</param> /// <param name="definitionFile">The path to the definition YAML file.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when a plan or report file cannot be written to disk due to an + /// I/O failure (e.g., <see cref="IOException"/>, + /// <see cref="UnauthorizedAccessException"/>, or + /// <see cref="DirectoryNotFoundException"/>). + /// </exception> private static void RunDefinitionLogic(Context context, string directory, string definitionFile) { // Load the configuration with integrated linting @@ -321,6 +407,16 @@ private static void RunDefinitionLogic(Context context, string directory, string /// <summary> /// Handles review issues by writing an error or warning to the context. /// </summary> + /// <remarks> + /// This is the single exit-code decision point for review enforcement. + /// When <c>--enforce</c> is set, any detected issues cause + /// <see cref="Context.WriteError"/> to be called, which sets the internal + /// error flag and drives <see cref="Context.ExitCode"/> to 1. Without + /// <c>--enforce</c>, issues are surfaced as non-fatal warnings so the + /// tool can still complete successfully. Centralizing this logic here + /// ensures every plan and report operation observes the same enforcement + /// policy. + /// </remarks> /// <param name="context">The context for output.</param> /// <param name="hasIssues">Whether there are issues to report.</param> /// <param name="message">The issue message.</param> diff --git a/src/DemaConsulting.ReviewMark/SelfTest/Validation.cs b/src/DemaConsulting.ReviewMark/SelfTest/Validation.cs index 238c198..a3636d0 100644 --- a/src/DemaConsulting.ReviewMark/SelfTest/Validation.cs +++ b/src/DemaConsulting.ReviewMark/SelfTest/Validation.cs @@ -49,7 +49,8 @@ internal static partial class Validation /// <summary> /// Runs self-validation tests and optionally writes results to a file. /// </summary> - /// <param name="context">The context containing command line arguments and program state.</param> + /// <param name="context">The context containing command line arguments and program state. Must not be null.</param> + /// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is null.</exception> /// <remarks> /// When one or more validation tests fail, or when the results file cannot be written /// (for example because the file extension is unsupported or an I/O error occurs), diff --git a/test/DemaConsulting.ReviewMark.Tests/Cli/CliTests.cs b/test/DemaConsulting.ReviewMark.Tests/Cli/CliTests.cs index 742e81f..7bf509a 100644 --- a/test/DemaConsulting.ReviewMark.Tests/Cli/CliTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/Cli/CliTests.cs @@ -53,6 +53,7 @@ public void Cli_VersionFlag_FlagSupplied_OutputsVersionOnly() var output = outWriter.ToString(); Assert.Equal(Program.Version, output.Trim()); Assert.DoesNotContain("Copyright", output); + Assert.Equal(0, context.ExitCode); } finally { @@ -83,6 +84,7 @@ public void Cli_HelpFlag_FlagSupplied_OutputsUsageInformation() Assert.Contains("Options:", output); Assert.Contains("--version", output); Assert.Contains("--help", output); + Assert.Equal(0, context.ExitCode); } finally { @@ -665,10 +667,13 @@ public void Cli_LintFlag_ValidConfig_ReportsSuccess() """); var originalOut = Console.Out; + var originalError = Console.Error; try { using var outWriter = new StringWriter(); + using var errWriter = new StringWriter(); Console.SetOut(outWriter); + Console.SetError(errWriter); using var context = Context.Create(["--definition", defFile, "--lint"]); // Act @@ -678,10 +683,12 @@ public void Cli_LintFlag_ValidConfig_ReportsSuccess() Assert.Equal(0, context.ExitCode); var output = outWriter.ToString(); Assert.Equal(string.Empty, output); + Assert.Equal(string.Empty, errWriter.ToString()); } finally { Console.SetOut(originalOut); + Console.SetError(originalError); } } finally @@ -954,6 +961,46 @@ public void Cli_DepthFlag_AboveMaximum_ThrowsArgumentException() Assert.Throws<ArgumentException>(() => Context.Create(["--depth", "6"])); } + /// <summary> + /// Test that --plan-depth with a value below the minimum (0) throws ArgumentException. + /// </summary> + [Fact] + public void Cli_PlanDepthFlag_BelowMinimum_ThrowsArgumentException() + { + // Act & Assert — plan-depth 0 is below the minimum of 1 + Assert.Throws<ArgumentException>(() => Context.Create(["--plan-depth", "0"])); + } + + /// <summary> + /// Test that --plan-depth with a value above the maximum (6) throws ArgumentException. + /// </summary> + [Fact] + public void Cli_PlanDepthFlag_AboveMaximum_ThrowsArgumentException() + { + // Act & Assert — plan-depth 6 exceeds the maximum of 5 + Assert.Throws<ArgumentException>(() => Context.Create(["--plan-depth", "6"])); + } + + /// <summary> + /// Test that --report-depth with a value below the minimum (0) throws ArgumentException. + /// </summary> + [Fact] + public void Cli_ReportDepthFlag_BelowMinimum_ThrowsArgumentException() + { + // Act & Assert — report-depth 0 is below the minimum of 1 + Assert.Throws<ArgumentException>(() => Context.Create(["--report-depth", "0"])); + } + + /// <summary> + /// Test that --report-depth with a value above the maximum (6) throws ArgumentException. + /// </summary> + [Fact] + public void Cli_ReportDepthFlag_AboveMaximum_ThrowsArgumentException() + { + // Act & Assert — report-depth 6 exceeds the maximum of 5 + Assert.Throws<ArgumentException>(() => Context.Create(["--report-depth", "6"])); + } + /// <summary> /// Test that --lint flag with an invalid config reports issue messages. /// </summary> diff --git a/test/DemaConsulting.ReviewMark.Tests/Configuration/ConfigurationTests.cs b/test/DemaConsulting.ReviewMark.Tests/Configuration/ConfigurationTests.cs index e34f3bd..e28293d 100644 --- a/test/DemaConsulting.ReviewMark.Tests/Configuration/ConfigurationTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/Configuration/ConfigurationTests.cs @@ -257,7 +257,7 @@ public void Configuration_Elaboration_ValidId_Succeeds() /// Test that elaborating a review-set with an unknown ID throws ArgumentException. /// </summary> [Fact] - public void Configuration_LoadConfig_ElaborateUnknownId_ThrowsArgumentException() + public void Configuration_ElaborateReviewSet_UnknownId_ThrowsArgumentException() { // Arrange var indexFile = PathHelpers.SafePathCombine(_testDirectory, "index.json"); diff --git a/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexTests.cs b/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexTests.cs index 5a6eb16..138ee8c 100644 --- a/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexTests.cs @@ -129,6 +129,8 @@ protected override void Dispose(bool disposing) [Fact] public void ReviewIndex_Empty_ReturnsEmptyIndex() { + // Arrange + // Act var index = ReviewIndex.Empty(); @@ -1060,7 +1062,7 @@ public void ReviewIndex_GetAllForId_ExistingId_ReturnsAllEntries() var entries = index.GetAllForId("Core-Logic"); // Assert — both entries for "Core-Logic" are returned - Assert.Equal(2, entries.Count()); + Assert.Equal(2, entries.Count); var fingerprints = entries.Select(e => e.Fingerprint).ToHashSet(); Assert.Contains("fp-v1", fingerprints); diff --git a/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexingTests.cs b/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexingTests.cs index 6a21af5..66b9aaf 100644 --- a/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexingTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/Indexing/IndexingTests.cs @@ -258,6 +258,37 @@ public void Indexing_ReviewIndex_Scan_WithValidPdf_ReturnsPopulatedIndex() Assert.Equal("pass", evidence.Result); } + /// <summary> + /// Test that loading from a fileshare source whose file does not exist throws + /// <see cref="InvalidOperationException" /> with a meaningful message. + /// </summary> + [Fact] + public void Indexing_ReviewIndex_Load_MissingFile_ThrowsInvalidOperationException() + { + // Arrange — construct a path that does not exist on disk + var missingPath = PathHelpers.SafePathCombine(_testDirectory, "evidence/missing-index.json"); + var source = new EvidenceSource("fileshare", missingPath, null, null); + + // Act & Assert — a missing file must surface as InvalidOperationException + Assert.Throws<InvalidOperationException>(() => ReviewIndex.Load(source)); + } + + /// <summary> + /// Test that loading from a fileshare source whose file contains malformed JSON + /// throws <see cref="InvalidOperationException" /> with a meaningful message. + /// </summary> + [Fact] + public void Indexing_ReviewIndex_Load_MalformedJson_ThrowsInvalidOperationException() + { + // Arrange — write non-JSON content to a temp file + var jsonPath = PathHelpers.SafePathCombine(_testDirectory, "malformed.json"); + File.WriteAllText(jsonPath, "this is not valid json {{{"); + var source = new EvidenceSource("fileshare", jsonPath, null, null); + + // Act & Assert — malformed JSON must surface as InvalidOperationException + Assert.Throws<InvalidOperationException>(() => ReviewIndex.Load(source)); + } + /// <summary> /// Minimal fake HTTP message handler that returns a fixed JSON response body. /// </summary> diff --git a/test/DemaConsulting.ReviewMark.Tests/Indexing/PathHelpersTests.cs b/test/DemaConsulting.ReviewMark.Tests/Indexing/PathHelpersTests.cs index f65df64..5c5442a 100644 --- a/test/DemaConsulting.ReviewMark.Tests/Indexing/PathHelpersTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/Indexing/PathHelpersTests.cs @@ -77,27 +77,40 @@ public void PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentExcepti } /// <summary> - /// Test that SafePathCombine throws ArgumentException for absolute paths. + /// Test that SafePathCombine throws ArgumentException for Unix absolute paths. + /// Runs on all platforms. /// </summary> [Fact] - public void PathHelpers_SafePathCombine_AbsolutePath_ThrowsArgumentException() + public void PathHelpers_SafePathCombine_AbsoluteUnixPath_ThrowsArgumentException() { - // Test Unix absolute path - var unixBasePath = "/home/user/project"; - var unixRelativePath = "/etc/passwd"; - var unixException = Assert.Throws<ArgumentException>(() => - PathHelpers.SafePathCombine(unixBasePath, unixRelativePath)); - Assert.Contains("Invalid path component", unixException.Message); - - // Test Windows absolute path (only on Windows since Windows paths may not be rooted on Unix) - if (OperatingSystem.IsWindows()) - { - var windowsBasePath = "C:\\Users\\project"; - var windowsRelativePath = "C:\\Windows\\System32\\file.txt"; - var windowsException = Assert.Throws<ArgumentException>(() => - PathHelpers.SafePathCombine(windowsBasePath, windowsRelativePath)); - Assert.Contains("Invalid path component", windowsException.Message); - } + // Arrange + var basePath = "/home/user/project"; + var relativePath = "/etc/passwd"; + + // Act & Assert + var exception = Assert.Throws<ArgumentException>(() => + PathHelpers.SafePathCombine(basePath, relativePath)); + Assert.Contains("Invalid path component", exception.Message); + } + + /// <summary> + /// Test that SafePathCombine throws ArgumentException for Windows absolute paths. + /// Runs on Windows only. + /// </summary> + [Fact] + public void PathHelpers_SafePathCombine_AbsoluteWindowsPath_ThrowsArgumentException() + { + // Skip on non-Windows platforms — Windows drive-rooted paths are only absolute on Windows + Assert.SkipUnless(OperatingSystem.IsWindows(), "Windows drive-rooted path test runs on Windows only"); + + // Arrange + var basePath = "C:\\Users\\project"; + var relativePath = "C:\\Windows\\System32\\file.txt"; + + // Act & Assert + var exception = Assert.Throws<ArgumentException>(() => + PathHelpers.SafePathCombine(basePath, relativePath)); + Assert.Contains("Invalid path component", exception.Message); } /// <summary> diff --git a/test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs b/test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs index 3389202..d80953b 100644 --- a/test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/IntegrationTests.cs @@ -239,8 +239,8 @@ public void ReviewMark_UnknownArgument_Provided_ReturnsNonZeroAndError() _dllPath, "--unknown"); - // Assert — unknown argument produces a non-zero exit code and an error message - Assert.NotEqual(0, exitCode); + // Assert — unknown argument produces exit code 1 and an error message + Assert.Equal(1, exitCode); Assert.Contains("Error", output); } @@ -385,8 +385,8 @@ public void ReviewMark_EnforceFlag_WithNoEvidence_ReturnsNonZero() reportFile, "--enforce"); - // Assert — non-zero because evidence source is 'none' so no reviews are current - Assert.NotEqual(0, exitCode); + // Assert — exit code is 1 because evidence source is 'none' so no reviews are current + Assert.Equal(1, exitCode); } finally { @@ -575,6 +575,144 @@ public void ReviewMark_DepthFlag_Invoked_SetsDefaultHeadingDepth() } } + /// <summary> + /// Test that --plan-depth flag overrides the heading depth for the plan document only, + /// leaving the report at the global --depth. + /// </summary> + /// <remarks>Links to ReviewMark-System-PlanDepth.</remarks> + [Fact] + public void ReviewMark_PlanDepthFlag_Invoked_OverridesPlanHeadingOnly() + { + // Arrange + var defFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".yaml")); + var planFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".md")); + var reportFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".md")); + + try + { + File.WriteAllText(defFile, """ + needs-review: + - "src/**/*.cs" + evidence-source: + type: none + reviews: + - id: Test-Review + title: Test review + paths: + - "src/**/*.cs" + """); + + // Act — global depth is 1, plan-depth overrides to 2 + var exitCode = Runner.Run( + out _, + "dotnet", + _dllPath, + "--definition", + defFile, + "--plan", + planFile, + "--report", + reportFile, + "--depth", + "1", + "--plan-depth", + "2"); + + // Assert — exit succeeds; plan uses ## (depth 2), report stays at # (depth 1) + Assert.Equal(0, exitCode); + Assert.True(File.Exists(planFile), "Plan file was not created"); + Assert.True(File.Exists(reportFile), "Report file was not created"); + var planContent = File.ReadAllText(planFile); + var reportContent = File.ReadAllText(reportFile); + Assert.Contains("## Review Coverage", planContent); + Assert.DoesNotContain("## Review Status", reportContent); + } + finally + { + if (File.Exists(defFile)) + { + File.Delete(defFile); + } + if (File.Exists(planFile)) + { + File.Delete(planFile); + } + if (File.Exists(reportFile)) + { + File.Delete(reportFile); + } + } + } + + /// <summary> + /// Test that --report-depth flag overrides the heading depth for the report document only, + /// leaving the plan at the global --depth. + /// </summary> + /// <remarks>Links to ReviewMark-System-ReportDepth.</remarks> + [Fact] + public void ReviewMark_ReportDepthFlag_Invoked_OverridesReportHeadingOnly() + { + // Arrange + var defFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".yaml")); + var planFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".md")); + var reportFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".md")); + + try + { + File.WriteAllText(defFile, """ + needs-review: + - "src/**/*.cs" + evidence-source: + type: none + reviews: + - id: Test-Review + title: Test review + paths: + - "src/**/*.cs" + """); + + // Act — global depth is 1, report-depth overrides to 2 + var exitCode = Runner.Run( + out _, + "dotnet", + _dllPath, + "--definition", + defFile, + "--plan", + planFile, + "--report", + reportFile, + "--depth", + "1", + "--report-depth", + "2"); + + // Assert — exit succeeds; report uses ## (depth 2), plan stays at # (depth 1) + Assert.Equal(0, exitCode); + Assert.True(File.Exists(planFile), "Plan file was not created"); + Assert.True(File.Exists(reportFile), "Report file was not created"); + var planContent = File.ReadAllText(planFile); + var reportContent = File.ReadAllText(reportFile); + Assert.Contains("## Review Status", reportContent); + Assert.DoesNotContain("## Review Coverage", planContent); + } + finally + { + if (File.Exists(defFile)) + { + File.Delete(defFile); + } + if (File.Exists(planFile)) + { + File.Delete(planFile); + } + if (File.Exists(reportFile)) + { + File.Delete(reportFile); + } + } + } + /// <summary> /// Test that --depth flag sets the heading depth in the self-validation report. /// </summary> @@ -640,6 +778,48 @@ public void ReviewMark_LintFlag_WithValidConfig_ProducesNoOutput() } } + /// <summary> + /// Test that --lint with an invalid config file reports issue messages and no version banner. + /// </summary> + [Fact] + public void ReviewMark_LintFlag_WithInvalidConfig_ReportsOnlyIssueMessages() + { + // Arrange — create a temp file with malformed YAML (invalid content triggers lint errors) + var defFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".yaml")); + + try + { + File.WriteAllText(defFile, """ + {{{this is not valid yaml + """); + + // Act + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--definition", + defFile, + "--lint"); + + // Assert — non-zero exit code because the config is invalid + Assert.Equal(1, exitCode); + + // Assert — at least one issue message appears in output + Assert.Contains("error:", output); + + // Assert — version banner is NOT present (lint mode suppresses the banner) + Assert.DoesNotContain("ReviewMark version", output); + } + finally + { + if (File.Exists(defFile)) + { + File.Delete(defFile); + } + } + } + /// <summary> /// Test that an invalid log file path causes Main() to return a non-zero exit code. /// </summary> @@ -663,4 +843,31 @@ public void ReviewMark_LogFlag_WithInvalidPath_ReturnsNonZero() Assert.NotEqual(0, exitCode); Assert.Contains("Error", output); } + + /// <summary> + /// Test that multiple --index flags accumulate evidence across all specified paths. + /// </summary> + [Fact] + public void ReviewMark_IndexFlag_WithRepeat_ScansAllPaths() + { + // Arrange — create a temp directory to index (with no PDF files) + using var tempDir = new TestDirectory(); + var indexFile = Path.Combine(tempDir.DirectoryPath, "index.json"); + + // Act — supply two --index glob patterns pointing to the same empty directory + var exitCode = Runner.Run( + out _, + "dotnet", + _dllPath, + "--dir", + tempDir.DirectoryPath, + "--index", + Path.Combine(tempDir.DirectoryPath, "sub1", "**", "*.pdf"), + "--index", + Path.Combine(tempDir.DirectoryPath, "sub2", "**", "*.pdf")); + + // Assert — exits successfully and produces a single merged index.json + Assert.Equal(0, exitCode); + Assert.True(File.Exists(indexFile), "index.json was not created"); + } } diff --git a/test/DemaConsulting.ReviewMark.Tests/ProgramTests.cs b/test/DemaConsulting.ReviewMark.Tests/ProgramTests.cs index d39ca67..7fea560 100644 --- a/test/DemaConsulting.ReviewMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/ProgramTests.cs @@ -26,6 +26,7 @@ namespace DemaConsulting.ReviewMark.Tests; /// <summary> /// Unit tests for the Program class. /// </summary> +[Collection("ConsoleTests")] public class ProgramTests { /// <summary> @@ -762,6 +763,71 @@ public void Program_HandleIssues_WithoutEnforce_EmitsWarning() } } + /// <summary> + /// Test that Run does nothing (exit code 0, no warnings) when there are no review issues — + /// both with and without --enforce. + /// </summary> + [Fact] + public void Program_HandleIssues_NoIssues_DoesNothing() + { + // Arrange — definition with no needs-review files and no reviews produces HasIssues=false + // for both plan and report, so HandleIssues is always called with hasIssues=false + using var tempDir = new TestDirectory(); + var definitionFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "definition.yaml"); + File.WriteAllText(definitionFile, """ + needs-review: [] + evidence-source: + type: none + reviews: [] + """); + + var planFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "plan.md"); + var reportFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "report.md"); + + var originalOut = Console.Out; + try + { + // Act (without --enforce) — no issues present; exit code must stay 0 with no warning + using var outWriterNoEnforce = new StringWriter(); + Console.SetOut(outWriterNoEnforce); + int exitCodeNoEnforce; + using (var context = Context.Create([ + "--definition", definitionFile, + "--plan", planFile, + "--report", reportFile])) + { + Program.Run(context); + exitCodeNoEnforce = context.ExitCode; + } + + // Assert — exit code is 0 and no warning is emitted without --enforce + Assert.Equal(0, exitCodeNoEnforce); + Assert.DoesNotContain("Warning:", outWriterNoEnforce.ToString()); + + // Act (with --enforce) — no issues present; exit code must still be 0 with no warning + using var outWriterEnforce = new StringWriter(); + Console.SetOut(outWriterEnforce); + int exitCodeEnforce; + using (var context = Context.Create([ + "--definition", definitionFile, + "--plan", planFile, + "--report", reportFile, + "--enforce"])) + { + Program.Run(context); + exitCodeEnforce = context.ExitCode; + } + + // Assert — exit code is 0 and no warning is emitted even with --enforce when no issues exist + Assert.Equal(0, exitCodeEnforce); + Assert.DoesNotContain("Warning:", outWriterEnforce.ToString()); + } + finally + { + Console.SetOut(originalOut); + } + } + /// <summary> /// Test that Run with --index flag scans PDF evidence files and writes index.json. /// </summary> @@ -784,3 +850,10 @@ public void Program_Run_WithIndexFlag_ScansAndWritesIndexFile() Assert.True(File.Exists(indexFile), "index.json was not created"); } } + +/// <summary> +/// Collection definition that disables parallelization for Console-redirecting tests. +/// Ensures tests using Console.SetOut / Console.SetError run sequentially. +/// </summary> +[CollectionDefinition("ConsoleTests", DisableParallelization = true)] +public class ConsoleTestsCollection { } diff --git a/test/DemaConsulting.ReviewMark.Tests/SelfTest/SelfTestTests.cs b/test/DemaConsulting.ReviewMark.Tests/SelfTest/SelfTestTests.cs index 057da07..6153c39 100644 --- a/test/DemaConsulting.ReviewMark.Tests/SelfTest/SelfTestTests.cs +++ b/test/DemaConsulting.ReviewMark.Tests/SelfTest/SelfTestTests.cs @@ -63,7 +63,7 @@ public void SelfTest_Run_AllTestsPass_ExitCodeIsZero() /// Test that running self-validation with --results creates a TRX results file. /// </summary> [Fact] - public void SelfTest_Run_GeneratesResultsFile() + public void SelfTest_Run_WithTrxResultsFile_WritesFile() { // Arrange var resultsFile = Path.Combine(Path.GetTempPath(), $"reviewmark-selftest-{Guid.NewGuid()}.trx"); @@ -103,7 +103,7 @@ public void SelfTest_Run_GeneratesResultsFile() /// Test that running self-validation with --results creates a JUnit XML results file. /// </summary> [Fact] - public void SelfTest_Run_GeneratesJUnitResultsFile() + public void SelfTest_Run_WithJUnitResultsFile_WritesFile() { // Arrange var resultsFile = Path.Combine(Path.GetTempPath(), $"reviewmark-selftest-{Guid.NewGuid()}.xml"); diff --git a/test/OtsSoftwareTests/AssemblyInfo.cs b/test/OtsSoftwareTests/AssemblyInfo.cs new file mode 100644 index 0000000..8b358e3 --- /dev/null +++ b/test/OtsSoftwareTests/AssemblyInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/test/OtsSoftwareTests/DemaConsultingTestResultsTests.cs b/test/OtsSoftwareTests/DemaConsultingTestResultsTests.cs new file mode 100644 index 0000000..1546b6a --- /dev/null +++ b/test/OtsSoftwareTests/DemaConsultingTestResultsTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using DemaConsulting.TestResults.IO; + +namespace OtsSoftwareTests; + +/// <summary> +/// OTS software tests for the DemaConsulting.TestResults serialization library. +/// </summary> +public sealed class DemaConsultingTestResultsTests +{ + /// <summary> + /// Verifies that <see cref="TrxSerializer.Serialize" /> produces output containing a + /// <c>TestRun</c> element when given a completed test run with at least one result. + /// </summary> + [Fact] + public void TrxSerializer_Serialize_CompletedTestRun_ContainsTestRunElement() + { + // Arrange + var results = new DemaConsulting.TestResults.TestResults + { + Name = "Test Run" + }; + results.Results.Add(new DemaConsulting.TestResults.TestResult + { + Name = "SampleTest", + Outcome = DemaConsulting.TestResults.TestOutcome.Passed, + Duration = TimeSpan.Zero + }); + + // Act + var xml = TrxSerializer.Serialize(results); + + // Assert — TRX output must contain the TestRun root element + Assert.False(string.IsNullOrWhiteSpace(xml), "Serialized TRX content must not be empty"); + Assert.Contains("<TestRun", xml, StringComparison.Ordinal); + } + + /// <summary> + /// Verifies that <see cref="JUnitSerializer.Serialize" /> produces output containing a + /// <c>testsuites</c> element when given a completed test run with at least one result. + /// </summary> + [Fact] + public void JUnitSerializer_Serialize_CompletedTestRun_ContainsTestSuitesElement() + { + // Arrange + var results = new DemaConsulting.TestResults.TestResults + { + Name = "Test Run" + }; + results.Results.Add(new DemaConsulting.TestResults.TestResult + { + Name = "SampleTest", + Outcome = DemaConsulting.TestResults.TestOutcome.Passed, + Duration = TimeSpan.Zero + }); + + // Act + var xml = JUnitSerializer.Serialize(results); + + // Assert — JUnit output must contain the testsuites root element + Assert.False(string.IsNullOrWhiteSpace(xml), "Serialized JUnit content must not be empty"); + Assert.Contains("testsuites", xml, StringComparison.Ordinal); + } +} diff --git a/test/OtsSoftwareTests/MicrosoftExtensionsFileSystemGlobbingTests.cs b/test/OtsSoftwareTests/MicrosoftExtensionsFileSystemGlobbingTests.cs new file mode 100644 index 0000000..4ecf5b2 --- /dev/null +++ b/test/OtsSoftwareTests/MicrosoftExtensionsFileSystemGlobbingTests.cs @@ -0,0 +1,140 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Microsoft.Extensions.FileSystemGlobbing; + +namespace OtsSoftwareTests; + +/// <summary> +/// OTS software tests for <see cref="Matcher" /> from +/// Microsoft.Extensions.FileSystemGlobbing. +/// </summary> +public sealed class MicrosoftExtensionsFileSystemGlobbingTests : IDisposable +{ + /// <summary> + /// Unique temporary directory created before each test and deleted after. + /// </summary> + private readonly string _testDirectory; + + /// <summary> + /// Initializes a new instance of <see cref="MicrosoftExtensionsFileSystemGlobbingTests" />. + /// </summary> + public MicrosoftExtensionsFileSystemGlobbingTests() + { + _testDirectory = Path.Combine( + Path.GetTempPath(), + $"OtsFSGlobbingTests_{Guid.NewGuid()}"); + Directory.CreateDirectory(_testDirectory); + } + + /// <inheritdoc /> + public void Dispose() + { + if (Directory.Exists(_testDirectory)) + { + Directory.Delete(_testDirectory, recursive: true); + } + + GC.SuppressFinalize(this); + } + + /// <summary> + /// Verifies that <c>**/*.cs</c> added via <see cref="Matcher.AddInclude" /> matches + /// files located in subdirectories, confirming double-wildcard traversal. + /// </summary> + [Fact] + public void Matcher_GetResultsInFullPath_DoubleWildcard_MatchesFilesInSubdirectories() + { + // Arrange + var subDir = Path.Combine(_testDirectory, "SubFolder"); + Directory.CreateDirectory(subDir); + File.WriteAllText(Path.Combine(subDir, "Alpha.cs"), "class Alpha {}"); + + var matcher = new Matcher(); + matcher.AddInclude("**/*.cs"); + + // Act + var results = matcher.GetResultsInFullPath(_testDirectory).ToList(); + + // Assert + Assert.Single(results); + Assert.Contains(results, r => r.EndsWith("Alpha.cs", StringComparison.Ordinal)); + } + + /// <summary> + /// Verifies that <c>*.cs</c> added via <see cref="Matcher.AddInclude" /> matches only + /// files in the root directory and does not traverse subdirectories. + /// </summary> + [Fact] + public void Matcher_GetResultsInFullPath_SingleWildcard_MatchesFilesInDirectory() + { + // Arrange + File.WriteAllText(Path.Combine(_testDirectory, "Root.cs"), "class Root {}"); + var subDir = Path.Combine(_testDirectory, "SubFolder"); + Directory.CreateDirectory(subDir); + File.WriteAllText(Path.Combine(subDir, "Sub.cs"), "class Sub {}"); + + var matcher = new Matcher(); + matcher.AddInclude("*.cs"); + + // Act + var results = matcher.GetResultsInFullPath(_testDirectory).ToList(); + + // Assert — only the root-level file is returned + Assert.Single(results); + Assert.Contains(results, r => r.EndsWith("Root.cs", StringComparison.Ordinal)); + } + + /// <summary> + /// Verifies that files matching an exclude pattern (using a separate <see cref="Matcher" /> + /// with <see cref="Matcher.AddInclude" />) are absent from the result set after removal, + /// confirming the exclusion technique used by <c>GlobMatcher</c>. + /// </summary> + [Fact] + public void Matcher_GetResultsInFullPath_ExcludePattern_OmitsMatchingFiles() + { + // Arrange + var genDir = Path.Combine(_testDirectory, "Generated"); + Directory.CreateDirectory(genDir); + File.WriteAllText(Path.Combine(_testDirectory, "Real.cs"), "class Real {}"); + File.WriteAllText(Path.Combine(genDir, "Generated.cs"), "class Generated {}"); + + // Include all .cs files + var includeMatcher = new Matcher(); + includeMatcher.AddInclude("**/*.cs"); + var included = includeMatcher.GetResultsInFullPath(_testDirectory).ToHashSet(StringComparer.Ordinal); + + // Exclude Generated/** using a separate include matcher on the exclude pattern (stripped of '!') + var excludeMatcher = new Matcher(); + excludeMatcher.AddInclude("Generated/**"); + foreach (var path in excludeMatcher.GetResultsInFullPath(_testDirectory)) + { + included.Remove(path); + } + + // Act — convert to sorted list + var results = included.Order(StringComparer.Ordinal).ToList(); + + // Assert — only Real.cs is returned; Generated.cs is excluded + Assert.Single(results); + Assert.Contains(results, r => r.EndsWith("Real.cs", StringComparison.Ordinal)); + Assert.DoesNotContain(results, r => r.EndsWith("Generated.cs", StringComparison.Ordinal)); + } +} diff --git a/test/OtsSoftwareTests/OtsSoftwareTests.csproj b/test/OtsSoftwareTests/OtsSoftwareTests.csproj new file mode 100644 index 0000000..3224a66 --- /dev/null +++ b/test/OtsSoftwareTests/OtsSoftwareTests.csproj @@ -0,0 +1,76 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <!-- Build Configuration --> + <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> + <OutputType>Exe</OutputType> + <LangVersion>latest</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + + <IsPackable>false</IsPackable> + <IsTestProject>true</IsTestProject> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + + <!-- Code Quality Configuration --> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <AnalysisLevel>latest</AnalysisLevel> + </PropertyGroup> + + <!-- Implicit Usings --> + <ItemGroup> + <Using Include="Polyfills" /> + <Using Include="Xunit" /> + </ItemGroup> + + <!-- Test Framework Dependencies --> + <ItemGroup> + <!-- coverlet.collector uses child-element form to configure both PrivateAssets and IncludeAssets: + - PrivateAssets="all" keeps this test-coverage tool out of any consuming project's dependencies. + - IncludeAssets lists all asset types (including 'build' and 'buildtransitive') to ensure the + data collector MSBuild targets are activated so coverage is collected during test runs. --> + <PackageReference Include="coverlet.collector" Version="10.0.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> + <PackageReference Include="xunit.v3" Version="3.2.2" /> + <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <!-- OTS Package Dependencies --> + <ItemGroup> + <PackageReference Include="DemaConsulting.TestResults" Version="1.7.0" /> + <PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.8" /> + <PackageReference Include="PDFsharp" Version="6.2.4" /> + <PackageReference Include="YamlDotNet" Version="17.1.0" /> + </ItemGroup> + + <!-- Build Tool Dependencies --> + <ItemGroup> + <PackageReference Include="Polyfill" Version="10.5.1" PrivateAssets="All" /> + </ItemGroup> + + <!-- Code Analysis Dependencies --> + <ItemGroup> + <!-- Analyzer packages use child-element form to configure both PrivateAssets and IncludeAssets: + - PrivateAssets="all" prevents these build-time analyzers from becoming transitive dependencies + in any project that references this test project. + - IncludeAssets lists all asset types (including 'analyzers' and 'buildtransitive') to ensure + Roslyn analyzers and MSBuild targets are fully activated during the build. --> + <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.300"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="SonarAnalyzer.CSharp" Version="10.25.0.139117"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + +</Project> diff --git a/test/OtsSoftwareTests/PDFsharpTests.cs b/test/OtsSoftwareTests/PDFsharpTests.cs new file mode 100644 index 0000000..0bf2d1e --- /dev/null +++ b/test/OtsSoftwareTests/PDFsharpTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; + +namespace OtsSoftwareTests; + +/// <summary> +/// OTS software tests for the PDFsharp PDF import library. +/// </summary> +public sealed class PDFsharpTests +{ + /// <summary> + /// Verifies that <see cref="PdfReader" /> opened in import mode exposes the Keywords + /// metadata field with the value that was written to the PDF. + /// </summary> + [Fact] + public void PdfReader_Open_ImportMode_ExposesKeywordsField() + { + // Arrange — create a temporary PDF with a known Keywords value + var pdfPath = Path.Combine(Path.GetTempPath(), $"OtsPdfTest_{Guid.NewGuid()}.pdf"); + try + { + using (var doc = new PdfDocument()) + { + doc.Info.Keywords = "review-id=ABC fingerprint=XYZ"; + doc.AddPage(); + doc.Save(pdfPath); + } + + // Act — open the PDF in import mode and read Keywords + using var importedDoc = PdfReader.Open(pdfPath, PdfDocumentOpenMode.Import); + var keywords = importedDoc.Info.Keywords; + + // Assert — the Keywords field contains the expected value + Assert.Equal("review-id=ABC fingerprint=XYZ", keywords); + } + finally + { + if (File.Exists(pdfPath)) + { + File.Delete(pdfPath); + } + } + } + + /// <summary> + /// Verifies that opening a PDF that contains no Keywords field in import mode returns a + /// null or empty value, confirming graceful handling of absent metadata. + /// </summary> + [Fact] + public void PdfReader_Open_ImportMode_NoKeywords_ReturnsNullOrEmpty() + { + // Arrange — create a temporary PDF without setting any Keywords value + var pdfPath = Path.Combine(Path.GetTempPath(), $"OtsPdfTestNoKw_{Guid.NewGuid()}.pdf"); + try + { + using (var doc = new PdfDocument()) + { + doc.AddPage(); + doc.Save(pdfPath); + } + + // Act — open the PDF in import mode and read Keywords + using var importedDoc = PdfReader.Open(pdfPath, PdfDocumentOpenMode.Import); + var keywords = importedDoc.Info.Keywords; + + // Assert — no Keywords field means null or empty is returned + Assert.True( + keywords == null || keywords.Length == 0, + $"Expected null or empty Keywords but got: '{keywords}'"); + } + finally + { + if (File.Exists(pdfPath)) + { + File.Delete(pdfPath); + } + } + } +} diff --git a/test/OtsSoftwareTests/YamlDotNetTests.cs b/test/OtsSoftwareTests/YamlDotNetTests.cs new file mode 100644 index 0000000..0674c4c --- /dev/null +++ b/test/OtsSoftwareTests/YamlDotNetTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace OtsSoftwareTests; + +/// <summary> +/// Simple configuration class used for YamlDotNet deserialization tests. +/// </summary> +internal sealed class SimpleConfig +{ + /// <summary>Gets or sets the name field.</summary> + public string Name { get; set; } = string.Empty; + + /// <summary>Gets or sets the value field.</summary> + public int Value { get; set; } +} + +/// <summary> +/// OTS software tests for the YamlDotNet deserialization library. +/// </summary> +public sealed class YamlDotNetTests +{ + /// <summary> + /// Verifies that a well-formed YAML string is correctly deserialized into a typed object + /// with the expected field values. + /// </summary> + [Fact] + public void Deserializer_Deserialize_WellFormedYaml_MapsToTypedObject() + { + // Arrange + const string yaml = "name: TestName\nvalue: 42\n"; + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + // Act + var config = deserializer.Deserialize<SimpleConfig>(yaml); + + // Assert + Assert.NotNull(config); + Assert.Equal("TestName", config.Name); + Assert.Equal(42, config.Value); + } + + /// <summary> + /// Verifies that structurally invalid YAML causes <c>IDeserializer.Deserialize</c> + /// to throw a <see cref="YamlException" />. + /// </summary> + [Fact] + public void Deserializer_Deserialize_MalformedYaml_ThrowsYamlException() + { + // Arrange — colon in a plain scalar without a space is structurally valid in most cases, + // so use a tab character which is illegal in YAML indentation. + const string malformedYaml = "name: valid\n\tinvalid_tab: oops\n"; + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + // Act & Assert + Assert.ThrowsAny<YamlException>(() => deserializer.Deserialize<SimpleConfig>(malformedYaml)); + } + + /// <summary> + /// Verifies that YAML containing unrecognized keys does not throw when the deserializer + /// is configured with <c>IgnoreUnmatchedProperties</c>, and that known fields are still + /// populated correctly. + /// </summary> + [Fact] + public void Deserializer_Deserialize_UnknownKeys_DoesNotThrow() + { + // Arrange + const string yaml = "name: KnownName\nvalue: 7\nunknownKey: unexpected\n"; + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + // Act + var config = deserializer.Deserialize<SimpleConfig>(yaml); + + // Assert — no exception was thrown and known fields are correctly populated + Assert.NotNull(config); + Assert.Equal("KnownName", config.Name); + Assert.Equal(7, config.Value); + } +}