diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..9143d0e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,21 @@ +# Repository Instructions For GitHub Copilot + +Follow the project `AGENTS.md`. + +This project uses architecture-knowledge-toolkit for architecture documentation, +ADRs, quality scenarios, risks, traceability metadata, templates, validators, +generated include fragments, and SDLC task workflows that are not described more +specifically in this repository. + +Use the toolkit repository as source of truth when local toolkit files are missing: + +https://github.com/docs-as-code-toolkit/architecture-knowledge-toolkit + +For architecture-related or SDLC workflow changes: + +- prefer small, reviewable changes +- preserve stable IDs +- keep AI-generated or AI-modified architecture content in `draft` or `proposed` state unless reviewed +- do not manually maintain generated fragments +- consult the relevant toolkit skill before issue slicing, issue implementation, commit message, pull request review, post-merge synchronization, ADR, quality scenario, risk, or traceability-review work when local instructions are missing +- do not introduce Copilot-specific rules into engine-independent toolkit files diff --git a/.github/workflows/architecture-docs.yml b/.github/workflows/architecture-docs.yml new file mode 100644 index 0000000..4363a02 --- /dev/null +++ b/.github/workflows/architecture-docs.yml @@ -0,0 +1,63 @@ +name: Architecture Documentation + +on: + pull_request: + push: + branches: [ main ] + workflow_dispatch: + +concurrency: + group: architecture-docs-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + validate: + name: Build Architecture Documentation + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Build architecture HTML + run: ./build.sh build + + build-pages: + name: Build Architecture Pages + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Build architecture HTML + run: ./build.sh build + + - name: Upload architecture HTML + uses: actions/upload-pages-artifact@v3 + with: + path: build/architecture + + deploy-pages: + name: Deploy Architecture Pages + runs-on: ubuntu-latest + needs: build-pages + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index b63da45..2286fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,7 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### Generated architecture documentation ### +**/generated/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..32d02d0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Project Agent Instructions + +This project uses architecture-knowledge-toolkit for architecture work and +software-development-lifecycle tasks that are not described more specifically in +this repository. + +Apply instructions in this order: + +1. User instruction +2. This project `AGENTS.md` +3. Relevant toolkit skill or contract +4. Toolkit `general-semantic-contracts.md` + +Use the toolkit for product clarification, arc42 documentation, ADRs, quality +scenarios, risks and technical debt, runtime scenarios, traceability metadata, +templates, validators, generated include fragments, issue slicing, issue +implementation workflow, commit messages, pull request reviews, post-merge +synchronization, and traceability reviews. + +Toolkit source of truth: + +https://github.com/docs-as-code-toolkit/architecture-knowledge-toolkit + +Preferred local lookup order: + +1. `$ARCHITECTURE_KNOWLEDGE_TOOLKIT` +2. `../architecture-knowledge-toolkit` +3. project-local recorded toolkit reference, submodule, vendored copy, or pinned path +4. the public repository above, preferably at a stable release tag or commit SHA + +Before architecture or SDLC workflow work: + +- inspect existing `src/docs/`, `metamodel/`, `templates/`, `scripts/`, and `skills/` +- verify referenced toolkit skill paths before copying or linking them +- preserve stable artifact IDs +- use AsciiDoc as the default architecture documentation format +- mark AI-created or AI-modified architecture content as `draft` or `proposed` +- set `reviewed: false` unless human acceptance is already recorded +- do not manually maintain generated fragments when a generator exists +- copy missing toolkit templates, schemas, validators, and generator scripts from the toolkit instead of inventing alternatives diff --git a/Dockerfile b/Dockerfile index 3f64999..805ebb5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,11 @@ RUN apt-get update && apt-get install -y \ graphviz \ unzip \ curl \ + && gem install --no-document asciidoctor-diagram asciidoctor-diagram-plantuml asciidoctor-revealjs \ && rm -rf /var/lib/apt/lists/* # Arbeitsverzeichnis WORKDIR /app # Flexible entrypoint โ€“ beliebige Commands -ENTRYPOINT [] \ No newline at end of file +ENTRYPOINT [] diff --git a/README.md b/README.md index 0687478..2034030 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ Unlike ad-hoc setups, this image is designed to be **the single source of truth - Reproducible documentation builds across environments - Zero-setup onboarding for contributors - Consistent toolchain in local and CI workflows +- Diagram rendering without project-specific host setup +- Reveal.js presentation builds from AsciiDoc sources +- Direct Asciidoctor CLI builds and Gradle/AsciidoctorJ builds in the same runtime --- @@ -32,9 +35,12 @@ Unlike ad-hoc setups, this image is designed to be **the single source of truth This image provides a ready-to-use toolchain for **Docs-as-Code pipelines**: - Asciidoctor +- Asciidoctor Diagram with PlantUML support +- Asciidoctor reveal.js converter - Pandoc - Graphviz - Common CLI utilities +- Java runtime support for Gradle/AsciidoctorJ-based documentation builds ๐Ÿ‘‰ Everything preconfigured to work together. @@ -64,11 +70,56 @@ docker run --rm \ -v $(pwd):/workspace \ -w /workspace \ ghcr.io/docs-as-code-toolkit/docs-toolbox:latest \ - ./asciidoctor -v + asciidoctor -v ``` > ๐Ÿ’ก On Windows, replace `$(pwd)` with the appropriate path syntax. +### Render AsciiDoc with diagrams + +```bash +docker run --rm \ + -v $(pwd):/workspace \ + -w /workspace \ + ghcr.io/docs-as-code-toolkit/docs-toolbox:latest \ + asciidoctor \ + -r asciidoctor-diagram \ + -r asciidoctor-diagram/plantuml \ + --failure-level=ERROR \ + docs/index.adoc +``` + +### Render reveal.js presentations + +The image includes the Ruby `asciidoctor-revealjs` CLI. Point `revealjsdir` at a +CDN or at project-provided Reveal.js assets: + +```bash +docker run --rm \ + -v $(pwd):/workspace \ + -w /workspace \ + ghcr.io/docs-as-code-toolkit/docs-toolbox:latest \ + asciidoctor-revealjs \ + -a revealjsdir=https://cdn.jsdelivr.net/npm/reveal.js@4.5.0 \ + docs/slides.adoc +``` + +For offline or customized slide decks, vendor Reveal.js in the project and set +`revealjsdir` to that local path. + +### Use with Gradle / AsciidoctorJ + +The image includes a Java runtime, so projects can run Gradle builds that use +AsciidoctorJ and its diagram module: + +```bash +docker run --rm \ + -v $(pwd):/workspace \ + -w /workspace \ + ghcr.io/docs-as-code-toolkit/docs-toolbox:latest \ + ./gradlew --no-daemon build +``` + ### Use in scripts (example) ```bash @@ -76,7 +127,16 @@ docker run --rm \ ``` (assuming your project wraps the container execution) -### GitHub Actions (example +This repository's own `build.sh` validates the toolkit metadata, generates +derived architecture fragments, and renders the assembled architecture HTML. +It first looks for a published `df-` image and builds a local +image from the current Dockerfile if that exact image does not exist. + +```bash +./build.sh build +``` + +### GitHub Actions (example) ```yaml jobs: @@ -92,6 +152,8 @@ jobs: ## ๐Ÿงฉ Typical Use Cases - ๐Ÿ“„ Generate HTML / PDF / Markdown from AsciiDoc +- ๐Ÿ–ผ๏ธ Render PlantUML diagrams from AsciiDoc +- ๐ŸŽž๏ธ Build Reveal.js presentations from AsciiDoc - ๐Ÿงฑ Docs-as-Code pipelines - ๐Ÿ“ฆ CI/CD documentation builds - ๐Ÿ‘ฅ Onboarding without local setup @@ -104,12 +166,45 @@ This image follows a few simple principles: - ๐Ÿงผ No hidden magic ## ๐Ÿ”„ Versioning -- latest โ†’ most recent stable toolbox -- version tags โ†’ reproducible builds -- `df-` โ†’ image of latest commit (does not need to be the same as 'latest') +- `latest` โ†’ most recent tagged stable toolbox image +- version tags โ†’ reproducible release builds +- `df-` โ†’ image built from a Dockerfile with that SHA-256 hash prefix ๐Ÿ‘‰ Pin versions in CI for full determinism. +The `df-` tag is useful for local wrappers: compute the hash of the local +Dockerfile, try to pull that image, and build locally when the image is not yet +published. + +## ๐Ÿ—๏ธ Architecture Documentation + +This repository contains toolkit-compatible architecture documentation under +`src/docs/`. + +The architecture documentation is dogfooded: it is generated with this +project's own toolbox image. `build.sh` computes the local Dockerfile hash, +uses the matching published `df-` image when available, and otherwise +builds the current Dockerfile locally before rendering the documentation. + +Build it locally with: + +```bash +./build.sh build +``` + +The output is written to: + +```text +build/architecture/index.html +``` + +Pull requests validate the architecture metadata. Builds on `main` generate the +architecture HTML and publish it through GitHub Pages. + +Expected GitHub Pages URL after the first successful deployment: + +๐Ÿ‘‰ https://docs-as-code-toolkit.github.io/docs-toolbox/ + ## ๐Ÿ› ๏ธ Customization If you need additional tools: - Extend this image via your own Dockerfile diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..1f841ef --- /dev/null +++ b/build.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env sh +set -eu + +COMMAND="${1:-build}" +IMAGE_REPOSITORY="${DOCS_TOOLBOX_IMAGE_REPOSITORY:-ghcr.io/docs-as-code-toolkit/docs-toolbox}" +BUILD_DIR="${BUILD_DIR:-build/architecture}" +SOURCE_DOC="${SOURCE_DOC:-src/docs/doc-001-arc42.adoc}" + +usage() { + cat <<'USAGE' +Usage: ./build.sh [build|validate|clean] + +Commands: + build Validate, generate architecture fragments, and render HTML. + validate Validate and generate architecture documentation metadata. + clean Remove local architecture build output. + +The script prefers a published docs-toolbox image tagged with the local +Dockerfile hash (df-). If that image is not available, it builds a local +image from the current Dockerfile so local Dockerfile changes are used. +USAGE +} + +find_engine() { + if command -v podman >/dev/null 2>&1; then + printf '%s\n' "podman" + elif command -v docker >/dev/null 2>&1; then + printf '%s\n' "docker" + else + printf '%s\n' "" + fi +} + +dockerfile_hash() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum Dockerfile | awk '{print $1}' | cut -c1-12 + else + shasum -a 256 Dockerfile | awk '{print $1}' | cut -c1-12 + fi +} + +run_validate() { + ruby scripts/validate-metamodel.rb --generate +} + +run_build() { + run_validate + mkdir -p "$BUILD_DIR" + asciidoctor \ + -r asciidoctor-diagram \ + -r asciidoctor-diagram/plantuml \ + --failure-level=ERROR \ + -a skip-front-matter \ + -a imagesdir=. \ + -a imagesoutdir="$BUILD_DIR" \ + -D "$BUILD_DIR" \ + -o index.html \ + "$SOURCE_DOC" + echo "Built architecture HTML: $BUILD_DIR/index.html" +} + +run_local() { + case "$COMMAND" in + build) + run_build + ;; + validate) + run_validate + ;; + clean) + rm -rf "$BUILD_DIR" + echo "Removed $BUILD_DIR" + ;; + help|-h|--help) + usage + ;; + *) + usage + exit 2 + ;; + esac +} + +run_in_container() { + ENGINE="$1" + HASH="$(dockerfile_hash)" + REMOTE_IMAGE="${IMAGE_REPOSITORY}:df-${HASH}" + LOCAL_IMAGE="docs-toolbox-local:df-${HASH}" + + echo "Dockerfile hash tag: df-${HASH}" + echo "Trying runtime image: ${REMOTE_IMAGE}" + + if "$ENGINE" pull "$REMOTE_IMAGE"; then + IMAGE="$REMOTE_IMAGE" + echo "Using published runtime image: $IMAGE" + else + echo "Published image for local Dockerfile hash not found. Building local runtime image." + "$ENGINE" build -t "$LOCAL_IMAGE" . + IMAGE="$LOCAL_IMAGE" + fi + + "$ENGINE" run --rm \ + -e DOCS_TOOLBOX_IN_CONTAINER=1 \ + -e BUILD_DIR="$BUILD_DIR" \ + -e SOURCE_DOC="$SOURCE_DOC" \ + -v "$PWD":/app \ + -w /app \ + "$IMAGE" \ + sh ./build.sh "$COMMAND" +} + +if [ "${DOCS_TOOLBOX_IN_CONTAINER:-}" = "1" ]; then + run_local + exit 0 +fi + +case "$COMMAND" in + build|validate) + ENGINE="$(find_engine)" + if [ -n "$ENGINE" ]; then + run_in_container "$ENGINE" + else + echo "No container engine found. Running locally." + run_local + fi + ;; + clean|help|-h|--help) + run_local + ;; + *) + usage + exit 2 + ;; +esac diff --git a/general-semantic-contracts.md b/general-semantic-contracts.md new file mode 100644 index 0000000..6343a72 --- /dev/null +++ b/general-semantic-contracts.md @@ -0,0 +1,28 @@ +# General Semantic Contracts + +This repository delegates architecture documentation conventions, SDLC workflow +guidance, metadata rules, validators, generators, and reusable task skills to the +architecture-knowledge-toolkit. + +Local repository evidence remains the source of truth. README prose, Dockerfile +instructions, workflow definitions, source code, tests, and reviewed +architecture records are evidence. AI-created architecture content is draft or +proposed until reviewed by the accountable owner. + +For missing guidance, use the toolkit lookup order in `AGENTS.md`. Do not copy +the full toolkit rule set into this repository; keep this file as the local +semantic entry point and let the toolkit remain the maintained source. + +Project-local facts currently supported by repository evidence: + +- The product is a Docker image named `docs-toolbox`. +- The image supports reproducible Docs-as-Code pipelines locally and in CI. +- The Dockerfile uses `openjdk:22-jdk-slim`. +- The image installs Asciidoctor, Pandoc, Graphviz, unzip, and curl. +- GitHub Actions builds and pushes multi-platform images to GHCR. +- Tags include a Dockerfile hash tag and, for Git-tagged commits, the Git tag and `latest`. +- The project is MIT licensed. + +Architecture documentation in this repository is under `src/docs/` and follows +the toolkit metamodel. Generated files under `generated/` directories are +derived output and must not be edited manually. diff --git a/metamodel/artifact.schema.yaml b/metamodel/artifact.schema.yaml new file mode 100644 index 0000000..cb09dd7 --- /dev/null +++ b/metamodel/artifact.schema.yaml @@ -0,0 +1,108 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://architecture-knowledge-toolkit.example/metamodel/artifact.schema.yaml" +title: Architecture Artifact Metadata +description: >- + Common YAML front matter metadata for reviewed architecture knowledge + artifacts. The metadata is intentionally small and extensible; prose remains + in the AsciiDoc body, while traceability is captured explicitly here. +type: object +additionalProperties: false +required: + - id + - type + - title + - status + - owner + - created +properties: + id: + type: string + description: Stable architecture artifact identifier. IDs must not be reused. + pattern: "^[A-Z]+-[0-9]{3,}(-[a-z0-9]+)*$" + examples: + - DOC-001-arc42 + - DOC-09000-architecture-decisions + - DOC-09001-adr-index + type: + type: string + description: Supported architecture artifact type. + enum: + - ADR + - QualityScenario + - Risk + - Requirement + - Constraint + - Component + - Interface + - Stakeholder + - Concept + - Document + title: + type: string + minLength: 1 + status: + type: string + description: Lifecycle state of the artifact. + enum: + - draft + - proposed + - reviewed + - accepted + - rejected + - superseded + - deprecated + owner: + type: string + minLength: 1 + created: + type: string + format: date + updated: + type: string + format: date + reviewed: + type: boolean + description: True only after human review of the artifact content. + default: false + generated: + type: boolean + description: True when the file is derived output rather than source content. + default: false + summary: + type: string + description: Short human-readable summary for indexes and generated views. + tags: + type: array + uniqueItems: true + items: + type: string + pattern: "^[a-z0-9][a-z0-9-]*$" + source: + type: string + description: Optional repository-relative source path when metadata is stored separately. + superseded_by: + type: string + description: Stable ID of the artifact that supersedes this one. + relations: + type: array + description: Explicit outgoing relationships from this artifact. + items: + $ref: "relations.schema.yaml#/$defs/relation" + metadata_version: + type: string + default: "1.0" +examples: + - id: DOC-09001-adr-index + type: Document + title: ADR Index + status: draft + owner: project maintainers + created: "2026-06-28" + reviewed: false + tags: + - arc42 + relations: + - type: refines + target: DOC-09000-architecture-decisions + status: proposed + rationale: This detail page elaborates the corresponding arc42 chapter. diff --git a/metamodel/relations.schema.yaml b/metamodel/relations.schema.yaml new file mode 100644 index 0000000..d6113bd --- /dev/null +++ b/metamodel/relations.schema.yaml @@ -0,0 +1,117 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://architecture-knowledge-toolkit.example/metamodel/relations.schema.yaml" +title: Architecture Relationship Metadata +description: >- + Schema for explicit traceability relationships between architecture artifacts. + Relationships can be embedded in YAML front matter or collected in a separate + relation index. + + **Relationship Directionality Convention:** All relations in source artifacts are + authoritative outgoing relations. Incoming relations are derived during generation + and appear only in generated traceability views, not in source metadata. This prevents + bidirectional duplication and reduces maintenance overhead. +type: object +additionalProperties: false +required: + - relations +properties: + relations: + type: array + items: + $ref: "#/$defs/indexedRelation" +$defs: + artifactId: + type: string + pattern: "^[A-Z]+-[0-9]{3,}(-[a-z0-9]+)*$" + relationshipType: + type: string + enum: + - addresses + - depends_on + - constrains + - refines + - supersedes + - conflicts_with + - mitigates + - introduces_risk + - affects + - verifies + - documents + - relates_to + relationshipStatus: + type: string + enum: + - proposed + - reviewed + - accepted + - rejected + relation: + type: object + additionalProperties: false + required: + - type + - target + - status + properties: + type: + $ref: "#/$defs/relationshipType" + target: + $ref: "#/$defs/artifactId" + status: + $ref: "#/$defs/relationshipStatus" + rationale: + type: string + evidence: + type: string + description: Repository-relative path or section reference supporting the relation. + reviewed: + type: boolean + default: false + indexedRelation: + type: object + additionalProperties: false + required: + - id + - source + - type + - target + - status + properties: + id: + type: string + description: Stable relationship identifier for relation index files. + pattern: "^REL-[0-9]{3,}(-[a-z0-9]+)*$" + source: + $ref: "#/$defs/artifactId" + type: + $ref: "#/$defs/relationshipType" + target: + $ref: "#/$defs/artifactId" + status: + $ref: "#/$defs/relationshipStatus" + rationale: + type: string + evidence: + type: string + description: Repository-relative path or section reference supporting the relation. + reviewed: + type: boolean + default: false +examples: + - relations: + - id: REL-001-adr-index-refines-decisions + source: DOC-09001-adr-index + type: refines + target: DOC-09000-architecture-decisions + status: proposed + rationale: The ADR index detail page elaborates the arc42 architecture decisions chapter. + evidence: src/docs/arc42/09-architecture-decisions/doc-09001-adr-index.adoc + reviewed: false + - id: REL-002-risks-refines-risk-chapter + source: DOC-11001-risks + type: refines + target: DOC-11000-risks-and-technical-debt + status: proposed + rationale: The risks detail page elaborates the arc42 risks and technical debt chapter. + evidence: src/docs/arc42/11-risks-and-technical-debt/doc-11001-risks.adoc + reviewed: false diff --git a/scripts/validate-metamodel.rb b/scripts/validate-metamodel.rb new file mode 100755 index 0000000..c8d9260 --- /dev/null +++ b/scripts/validate-metamodel.rb @@ -0,0 +1,1038 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'date' +require 'fileutils' +require 'optparse' +require 'pathname' +require 'set' +require 'yaml' + +class MetamodelValidator + Artifact = Struct.new(:path, :metadata, :document_id, keyword_init: true) + + attr_reader :errors, :warnings, :root, :docs_dir + + def initialize(root:, docs_dir:, artifact_schema:, relations_schema:) + @root = Pathname.new(root).expand_path + @docs_paths = Array(docs_dir).map { |path| Pathname.new(path).expand_path } + @docs_dir = @docs_paths.length == 1 ? @docs_paths.first : @docs_paths + @artifact_schema = Pathname.new(artifact_schema).expand_path + @relations_schema = Pathname.new(relations_schema).expand_path + @errors = [] + @warnings = [] + end + + def validate + @errors = [] + @warnings = [] + + artifact_schema = load_yaml(@artifact_schema) + relation_schema = load_yaml(@relations_schema) + artifact_required_fields = artifact_schema.fetch('required') + artifact_type_values = artifact_schema.fetch('properties').fetch('type').fetch('enum') + artifact_status_values = artifact_schema.fetch('properties').fetch('status').fetch('enum') + relation_types = relation_schema.fetch('$defs').fetch('relationshipType').fetch('enum') + relation_keys = relation_schema.fetch('$defs').fetch('relation').fetch('properties').keys + + artifacts = scan_artifacts + validate_artifacts(artifacts, artifact_required_fields, artifact_type_values, artifact_status_values) + validate_filename_matches_id(artifacts) + validate_decimal_classification(artifacts) + validate_unique_ids(artifacts) + validate_relations(artifacts, relation_types, relation_keys) + detect_bidirectional_relations(artifacts) + + artifacts + rescue StandardError => e + @errors << "validator setup failed: #{e.message}" + [] + end + + def print_report(artifacts, io: $stdout) + io.puts 'Architecture metamodel validation report' + io.puts "Docs target: #{docs_target_label}" + io.puts "Artifacts scanned: #{artifacts.length}" + io.puts "Errors: #{@errors.length}" + io.puts "Warnings: #{@warnings.length}" + + unless @errors.empty? + io.puts + io.puts 'Errors:' + @errors.each { |error| io.puts " - #{error}" } + end + + unless @warnings.empty? + io.puts + io.puts 'Warnings:' + @warnings.each { |warning| io.puts " - #{warning}" } + end + + io.puts + io.puts(@errors.empty? ? 'Validation passed.' : 'Validation failed.') + end + + private + + def scan_artifacts + paths = @docs_paths.flat_map do |docs_path| + unless docs_path.exist? + @errors << "docs target does not exist: #{relative(docs_path)}" + next [] + end + + if docs_path.directory? + Dir.glob(docs_path.join('**/*.adoc').to_s) + else + [docs_path.to_s] + end + end.uniq.sort + + paths.map do |path| + artifact_path = Pathname.new(path) + next if generated_path?(artifact_path) + + metadata = read_front_matter(artifact_path) + Artifact.new( + path: artifact_path, + metadata: metadata, + document_id: document_id_for(artifact_path, metadata) + ) + end.compact + end + + def generated_path?(path) + path.expand_path.each_filename.include?('generated') + end + + def validate_artifacts(artifacts, required_fields, type_values, status_values) + artifacts.each do |artifact| + metadata = artifact.metadata + next unless metadata + + required_fields.each do |field| + next if metadata.key?(field) && !blank?(metadata[field]) + + @errors << "#{relative(artifact.path)} missing required metadata field '#{field}'" + end + + type = metadata['type'] + if !blank?(type) && !type_values.include?(type) + @errors << "#{relative(artifact.path)} metadata field 'type' has unsupported value '#{type}'" + end + + status = metadata['status'] + if !blank?(status) && !status_values.include?(status) + @errors << "#{relative(artifact.path)} metadata field 'status' has unsupported value '#{status}'" + end + end + end + + def validate_unique_ids(artifacts) + by_id = Hash.new { |hash, key| hash[key] = [] } + + artifacts.each do |artifact| + id = artifact.document_id + by_id[id] << artifact if id + end + + by_id.each do |id, matches| + next if matches.length == 1 + + paths = matches.map { |artifact| relative(artifact.path) }.join(', ') + @errors << "duplicate artifact id '#{id}' in #{paths}" + end + end + + def validate_filename_matches_id(artifacts) + artifacts.each do |artifact| + id = artifact.document_id + next unless id + + expected = "#{normalized_id(id)}.adoc" + actual = artifact.path.basename.to_s + next if actual == expected + + @warnings << "#{relative(artifact.path)} filename should be '#{expected}' to match artifact id '#{id}'" + end + end + + def validate_decimal_classification(artifacts) + artifacts.each do |artifact| + id = artifact.document_id + next unless id&.start_with?('DOC-') + + arc42_relative = arc42_relative_path(artifact.path) + next unless arc42_relative + + parts = arc42_relative.each_filename.to_a + if parts.length == 1 + expected_chapter = chapter_from_root_filename(parts.first) + next unless expected_chapter + + expected_prefix = "DOC-#{expected_chapter}000-" + next if id.start_with?(expected_prefix) + + @warnings << "#{relative(artifact.path)} artifact id should start with '#{expected_prefix}' for arc42 chapter #{expected_chapter}" + elsif parts.first =~ /\A(\d{2})-/ + chapter = Regexp.last_match(1) + expected_prefix = "DOC-#{chapter}" + next if id =~ /\ADOC-#{chapter}\d{3}-/ && !id.start_with?("DOC-#{chapter}000-") + + @warnings << "#{relative(artifact.path)} artifact id should start with '#{expected_prefix}' plus a three-digit local sequence greater than 000" + end + end + end + + def validate_relations(artifacts, relation_types, relation_keys) + known_ids = artifacts.map(&:document_id).compact.to_set + + artifacts.each do |artifact| + metadata = artifact.metadata + next unless metadata + + relations = metadata['relations'] || [] + unless relations.is_a?(Array) + @errors << "#{relative(artifact.path)} metadata field 'relations' must be a list" + next + end + + relations.each_with_index do |relation, index| + location = "#{relative(artifact.path)} relation ##{index + 1}" + + unless relation.is_a?(Hash) + @errors << "#{location} must be a mapping" + next + end + + unknown_keys = relation.keys.map(&:to_s) - relation_keys + unless unknown_keys.empty? + @errors << "#{location} has unknown relation key(s): #{unknown_keys.sort.join(', ')}" + end + + type = relation['type'] + target = relation['target'] + + @errors << "#{location} missing required relation key 'type'" if blank?(type) + @errors << "#{location} missing required relation key 'target'" if blank?(target) + @errors << "#{location} missing required relation key 'status'" if blank?(relation['status']) + + if type && !relation_types.include?(type) + @errors << "#{location} uses unknown relation type '#{type}'" + end + + if target && !known_ids.include?(target) + @errors << "#{location} references unknown artifact id '#{target}'" + end + end + end + end + + def detect_bidirectional_relations(artifacts) + # Build a map of all outgoing relations: source_id -> [target_ids] + outgoing_map = Hash.new { |hash, key| hash[key] = Set.new } + artifacts.each do |artifact| + metadata = artifact.metadata + next unless metadata && metadata['id'] + source_id = metadata['id'] + (metadata['relations'] || []).each do |relation| + next unless relation.is_a?(Hash) + target_id = relation['target'] + outgoing_map[source_id] << target_id if target_id + end + end + + # Track seen pairs to avoid duplicate warnings (A->B and B->A) + seen_pairs = Set.new + + # Check for bidirectional patterns: if A -> B exists, check if B -> A exists + outgoing_map.each do |source_id, target_ids| + target_ids.each do |target_id| + # Check if the target has a relation back to the source + if outgoing_map.key?(target_id) && outgoing_map[target_id].include?(source_id) + # Normalize pair to avoid duplicate warnings + pair_key = [source_id, target_id].sort + next if seen_pairs.include?(pair_key) + seen_pairs << pair_key + @warnings << "Bidirectional relation detected: #{source_id} -> #{target_id} and #{target_id} -> #{source_id}. " \ + "Consider removing the reciprocal relation from one artifact." + end + end + end + end + + def read_front_matter(path) + text = path.read + return nil unless text.start_with?("---\n") + + parts = text.split(/^---\s*$/, 3) + if parts.length < 3 + @errors << "#{relative(path)} has no closing YAML front matter marker" + return nil + end + + data = YAML.safe_load(parts[1], permitted_classes: [Date], aliases: false) + unless data.is_a?(Hash) + @errors << "#{relative(path)} YAML front matter must be a mapping" + return nil + end + + data.transform_keys(&:to_s) + rescue Psych::SyntaxError => e + @errors << "#{relative(path)} has invalid YAML front matter: #{e.message.lines.first.strip}" + nil + end + + def document_id_for(path, metadata) + return metadata['id'] if metadata && metadata['id'] + + path.each_line.first(20).each do |line| + return Regexp.last_match(1).strip if line =~ /^:id:\s*(.+?)\s*$/ + end + nil + end + + def load_yaml(path) + YAML.safe_load(path.read, permitted_classes: [Date], aliases: false) + end + + def blank?(value) + value.nil? || (value.respond_to?(:empty?) && value.empty?) + end + + def normalized_id(id) + id.to_s + .downcase + .gsub(/[^a-z0-9]+/, '-') + .gsub(/\A-+|-+\z/, '') + end + + def arc42_relative_path(path) + expanded = path.expand_path + @docs_paths.each do |docs_path| + base = docs_path.directory? ? docs_path : docs_path.dirname + arc42 = base.basename.to_s == 'arc42' ? base : base.join('arc42') + next unless expanded.to_s.start_with?("#{arc42.expand_path}/") + + return expanded.relative_path_from(arc42.expand_path) + end + nil + rescue ArgumentError + nil + end + + def chapter_from_root_filename(filename) + return Regexp.last_match(1) if filename =~ /\Adoc-(\d{2})000-/ + return Regexp.last_match(1) if filename =~ /\Adoc-(\d{2})\d{3}-/ + + nil + end + + def relative(path) + Pathname.new(path).expand_path.relative_path_from(@root).to_s + rescue ArgumentError + path.to_s + end + + def docs_target_label + @docs_paths.map { |path| relative(path) }.join(', ') + end +end + +class TraceabilityMatrixGenerator + DEFAULT_OUTPUT = 'generated/traceability-matrix.adoc' + + def initialize(root:, docs_dir:, output_path: nil) + @root = Pathname.new(root).expand_path + @docs_dir = Array(docs_dir).first + @docs_dir = Pathname.new(@docs_dir).expand_path + @output_path = Pathname.new(output_path || @docs_dir.join(DEFAULT_OUTPUT)).expand_path + end + + def write(artifacts) + content = render(artifacts) + FileUtils.mkdir_p(@output_path.dirname) + @output_path.write(content) + @output_path + end + + def render(artifacts) + artifacts_by_id = artifacts.each_with_object({}) do |artifact, index| + index[artifact.metadata['id']] = artifact if artifact.metadata + end + incoming = incoming_relations(artifacts) + sorted = artifacts.sort_by { |artifact| [artifact.metadata['type'].to_s, artifact.metadata['id'].to_s] } + + lines = [] + lines << "[[#{anchor_for(@output_path)}]]" + lines << '= Traceability Matrix' + lines << ':toc:' + lines << ':toclevels: 1' + lines << '' + lines << '// Generated from architecture artifact metadata. Do not edit manually.' + lines << '' + lines << '[cols="1,1,2,1,3,3", options="header"]' + lines << '|===' + lines << '| Artifact ID | Type | Title | Status | Outgoing relations | Incoming relations' + lines << '' + + sorted.each do |artifact| + metadata = artifact.metadata + id = metadata['id'] + lines << "| #{artifact_link(artifact)}" + lines << "| #{cell(metadata['type'])}" + lines << "| #{cell(metadata['title'])}" + lines << "| #{cell(metadata['status'])}" + lines << "| #{relations_cell(metadata['relations'] || [], artifacts_by_id, :outgoing)}" + lines << "| #{relations_cell(incoming.fetch(id, []), artifacts_by_id, :incoming)}" + lines << '' + end + + lines << '|===' + lines << '' + lines.join("\n") + end + + private + + def incoming_relations(artifacts) + artifacts.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |artifact, index| + source_id = artifact.metadata['id'] + Array(artifact.metadata['relations']).each do |relation| + index[relation['target']] << relation.merge('source' => source_id) + end + end + end + + def relations_cell(relations, artifacts_by_id, direction) + return '-' if relations.empty? + + sorted = relations.sort_by do |relation| + other_id = direction == :outgoing ? relation['target'] : relation['source'] + [relation['type'].to_s, other_id.to_s] + end + + sorted.map do |relation| + if direction == :outgoing + "#{cell(relation['type'])} -> #{artifact_ref(relation['target'], artifacts_by_id)}" + else + "#{artifact_ref(relation['source'], artifacts_by_id)} -> #{cell(relation['type'])}" + end + end.join(" +\n") + end + + def artifact_link(artifact) + target = artifact.path.expand_path.relative_path_from(@output_path.dirname).to_s + "xref:#{target}##{artifact_anchor(artifact.path)}[#{cell(artifact.metadata['id'])}]" + end + + def anchor_for(path) + normalized_anchor(path.basename(path.extname).to_s) + end + + def artifact_anchor(path) + explicit_anchor(path) || anchor_for(path) + end + + def artifact_ref(id, artifacts_by_id) + artifact = artifacts_by_id[id] + return cell(id) unless artifact + + artifact_link(artifact) + end + + def cell(value) + value.to_s.gsub('|', '\|').gsub("\n", ' ') + end + + def explicit_anchor(path) + text = Pathname.new(path).read + body = text.start_with?("---\n") ? text.split(/^---\s*$/, 3).last.to_s : text + body.each_line.first(20).each do |line| + return Regexp.last_match(1) if line =~ /\[\[([a-z][a-z0-9-]*)\]\]/ + return Regexp.last_match(1) if line =~ /\[#([a-z][a-z0-9-]*)\]/ + end + nil + rescue Errno::ENOENT + nil + end + + def normalized_anchor(value) + value.to_s + .downcase + .sub(/\A[0-9]+[-_ ]+/, '') + .gsub(/[^a-z0-9]+/, '-') + .gsub(/\A-+|-+\z/, '') + end +end + +class ArtifactIndexGenerator + INDEX_DEFINITIONS = { + 'ADR' => { + output: '09-architecture-decisions/generated/doc-09001-adr-index.adoc', + anchor: 'adr-index', + title: 'ADR Index', + cols: '1,2,1,3', + columns: %w[ADR Title Status Notes], + row: lambda do |artifact, helper| + metadata = artifact.metadata + [ + helper.artifact_link(artifact, label: helper.short_id(metadata['id'])), + helper.cell(metadata['title']), + helper.cell(metadata['status']), + helper.cell(metadata['summary']) + ] + end + }, + 'QualityScenario' => { + output: '10-quality-requirements/generated/doc-10001-quality-scenarios.adoc', + anchor: 'quality-scenarios', + title: 'Quality Scenarios', + cols: '1,1,2,2,2,2,2,2', + columns: ['ID', 'Objective', 'Source', 'Stimulus', 'Artifact', 'Environment', 'Response', 'Response measure'], + row: lambda do |artifact, helper| + fields = helper.definition_table_fields(artifact) + [ + helper.artifact_link(artifact, label: helper.short_id(artifact.metadata['id'])), + helper.relation_targets(artifact, 'refines'), + helper.cell(fields['Source']), + helper.cell(fields['Stimulus']), + helper.cell(fields['Artifact']), + helper.cell(fields['Environment']), + helper.cell(fields['Response']), + helper.cell(fields['Response Measure']) + ] + end + }, + 'Risk' => { + output: '11-risks-and-technical-debt/generated/doc-11001-risks.adoc', + anchor: 'risks', + title: 'Risks', + cols: '1,3,1,1,1,3', + columns: ['ID', 'Risk', 'Probability', 'Impact', 'Priority', 'Mitigation/action'], + row: lambda do |artifact, helper| + fields = helper.definition_table_fields(artifact) + [ + helper.artifact_link(artifact, label: helper.short_id(artifact.metadata['id'])), + helper.cell(artifact.metadata['title']), + helper.cell(fields['Likelihood']), + helper.cell(fields['Impact']), + helper.cell(fields['Priority']), + helper.relation_targets(artifact, 'affects') + ] + end + } + }.freeze + + attr_reader :output_paths + + def initialize(root:, docs_dir:) + @root = Pathname.new(root).expand_path + @docs_dir = output_base(docs_dir) + @output_paths = [] + end + + def write(artifacts) + @output_paths = [] + + INDEX_DEFINITIONS.each_value do |definition| + output_path = @docs_dir.join(definition.fetch(:output)) + content = render(artifacts, definition, output_path) + FileUtils.mkdir_p(output_path.dirname) + output_path.write(content) + @output_paths << output_path + end + + @output_paths + end + + def render(artifacts, definition, output_path = @docs_dir.join(definition.fetch(:output))) + type = INDEX_DEFINITIONS.key(definition) + selected = artifacts.select { |artifact| artifact.metadata && artifact.metadata['type'] == type } + .sort_by { |artifact| artifact.metadata['id'].to_s } + + lines = [] + lines << "[[#{definition.fetch(:anchor)}]]" + lines << "== #{definition.fetch(:title)}" + lines << '' + lines << '// Generated from architecture artifact metadata. Do not edit manually.' + lines << '' + lines << %([cols="#{definition.fetch(:cols)}", options="header"]) + lines << '|===' + lines << "| #{definition.fetch(:columns).join(' | ')}" + lines << '' + + selected.each do |artifact| + helper = ArtifactRenderHelper.new(output_path, artifacts) + definition.fetch(:row).call(artifact, helper).each do |value| + lines << "| #{value}" + end + lines << '' + end + + lines << '|===' + lines << '' + lines.join("\n") + end +end + +class OpenQuestionsIndexGenerator + Question = Struct.new(:anchor, :id, :title, :role, keyword_init: true) + + OUTPUT = '09-architecture-decisions/generated/open-questions.adoc' + + attr_reader :output_paths + + def initialize(root:, docs_dir:, questions_path: nil) + @root = Pathname.new(root).expand_path + @docs_dir = output_base(docs_dir) + @questions_path = Pathname.new(questions_path || @root.join('src/docs/doc-005-questions-and-answers.adoc')).expand_path + @output_paths = [] + end + + def write + output_path = @docs_dir.join(OUTPUT) + content = render(output_path) + FileUtils.mkdir_p(output_path.dirname) + output_path.write(content) + @output_paths = [output_path] + end + + def render(output_path = @docs_dir.join(OUTPUT)) + questions = parse_open_questions(@questions_path.read) + + lines = [] + lines << '[[open-questions]]' + lines << '== Open Questions' + lines << '' + lines << '// Generated from doc-005-questions-and-answers.adoc. Do not edit manually.' + lines << '' + + if questions.empty? + lines << 'No open questions recorded.' + lines << '' + return lines.join("\n") + end + + lines << '[cols="1,1,3", options="header"]' + lines << '|===' + lines << '| Question | Role | Topic' + lines << '' + + questions.each do |question| + lines << "| xref:#{question.anchor}[#{cell(question.id)}]" + lines << "| #{cell(question.role)}" + lines << "| #{cell(question.title)}" + lines << '' + end + + lines << '|===' + lines << '' + lines.join("\n") + end + + def parse_open_questions(text) + role = nil + pending_anchor = nil + current = nil + questions = [] + + text.each_line do |line| + case line + when /^==\s+Open Questions For\s+(.+?)\s*$/ + role = Regexp.last_match(1) + when /^\[\[(q-[a-z]+-[0-9]{3})\]\]\s*$/ + pending_anchor = Regexp.last_match(1) + when /^===\s+(Q-[A-Z]+-[0-9]{3}):\s*(.+?)\s*$/ + current = Question.new( + anchor: pending_anchor, + id: Regexp.last_match(1), + title: Regexp.last_match(2), + role: role + ) + pending_anchor = nil + when /^Answer:\s+Open\.\s*$/ + questions << current if current&.anchor + end + end + + questions.sort_by { |question| question.id.to_s } + end + + private + + def cell(value) + text = value.to_s.strip + return '-' if text.empty? + + text.gsub('|', '\|').gsub("\n", ' ') + end +end + +class TraceabilityFragmentGenerator + attr_reader :output_paths + + def initialize(root:, docs_dir:) + @root = Pathname.new(root).expand_path + @output_paths = [] + end + + def write(artifacts) + artifacts_by_id = artifacts.each_with_object({}) do |artifact, index| + index[artifact.metadata['id']] = artifact if artifact.metadata + end + incoming = incoming_relations(artifacts) + + @output_paths = artifacts.map do |artifact| + output_path = traceability_output_path(artifact) + content = render(artifact, artifacts_by_id, incoming.fetch(artifact.metadata['id'], []), output_path) + FileUtils.mkdir_p(output_path.dirname) + output_path.write(content) + output_path + end + end + + def render(artifact, artifacts_by_id, incoming, output_path) + metadata = artifact.metadata + outgoing = Array(metadata['relations']) + helper = ArtifactRenderHelper.new(output_path, artifacts_by_id.values) + + lines = [] + lines << "[[generated-traceability-#{anchor_for(artifact.path)}]]" + lines << '== Traceability' + lines << '' + lines << '// Generated from architecture artifact metadata. Do not edit manually.' + lines << '' + lines << '[cols="1,1,3", options="header"]' + lines << '|===' + lines << '| Direction | Relation | Target' + lines << '' + + if outgoing.empty? && incoming.empty? + lines << '| -' + lines << '| -' + lines << '| No relations recorded in metadata.' + lines << '' + else + outgoing.sort_by { |relation| [relation['type'].to_s, relation['target'].to_s] }.each do |relation| + lines << '| outgoing' + lines << "| #{helper.cell(relation['type'])}" + lines << "| #{helper.artifact_ref(relation['target'], artifacts_by_id)}" + lines << '' + end + + incoming.sort_by { |relation| [relation['type'].to_s, relation['source'].to_s] }.each do |relation| + lines << '| incoming' + lines << "| #{helper.cell(relation['type'])}" + lines << "| #{helper.artifact_ref(relation['source'], artifacts_by_id)}" + lines << '' + end + end + + lines << '|===' + lines << '' + lines.join("\n") + end + + private + + def incoming_relations(artifacts) + artifacts.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |artifact, index| + source_id = artifact.metadata['id'] + Array(artifact.metadata['relations']).each do |relation| + index[relation['target']] << relation.merge('source' => source_id) + end + end + end + + def anchor_for(path) + normalized_anchor(path.basename(path.extname).to_s) + end + + def normalized_anchor(value) + value.to_s + .downcase + .sub(/\A[0-9]+[-_ ]+/, '') + .gsub(/[^a-z0-9]+/, '-') + .gsub(/\A-+|-+\z/, '') + end + + def traceability_output_path(artifact) + artifact.path.dirname.join('generated', "#{anchor_for(artifact.path)}-traceability.adoc") + end +end + +class ChapterIncludeFragmentGenerator + attr_reader :output_paths + + def initialize(root:, docs_dir:) + @root = Pathname.new(root).expand_path + @docs_dir = output_base(docs_dir) + @output_paths = [] + end + + def write(artifacts) + @output_paths = chapter_groups(artifacts).map do |chapter, details| + output_path = include_output_path(chapter) + content = render(chapter, details, output_path) + FileUtils.mkdir_p(output_path.dirname) + output_path.write(content) + output_path + end + end + + def render(chapter, details, output_path = include_output_path(chapter)) + sorted = details.sort_by { |artifact| artifact.metadata['id'].to_s } + + lines = [] + lines << "// Generated from arc42 chapter metadata for #{chapter.metadata['id']}. Do not edit manually." + lines << '' + + sorted.each do |artifact| + target = artifact.path.expand_path.relative_path_from(output_path.dirname).to_s + lines << '' + lines << "include::#{target}[]" + lines << '' + end + + lines.join("\n") + end + + private + + def chapter_groups(artifacts) + metadata_artifacts = artifacts.select(&:metadata) + chapters = metadata_artifacts.select { |artifact| chapter_artifact?(artifact) } + details_by_chapter = metadata_artifacts + .reject { |artifact| chapter_artifact?(artifact) } + .reject { |artifact| chapter_number_for(artifact.path).nil? } + .group_by { |artifact| chapter_number_for(artifact.path) } + + chapters.each_with_object({}) do |chapter, groups| + chapter_number = chapter_number_for(chapter.path) + next unless chapter_number + + details = details_by_chapter.fetch(chapter_number, []) + groups[chapter] = details unless details.empty? + end + end + + def chapter_artifact?(artifact) + relative = arc42_relative_path(artifact.path) + return false unless relative + + parts = relative.each_filename.to_a + # Chapter overview documents live directly below arc42; nested files are + # detail documents grouped by their numbered chapter directory. + parts.length == 1 && parts.first =~ /\Adoc-\d{2}000-.*\.adoc\z/ + end + + def chapter_number_for(path) + relative = arc42_relative_path(path) + return nil unless relative + + parts = relative.each_filename.to_a + if parts.length == 1 && parts.first =~ /\Adoc-(\d{2})000-/ + Regexp.last_match(1) + elsif parts.first =~ /\A(\d{2})-/ + Regexp.last_match(1) + end + end + + def arc42_relative_path(path) + expanded = path.expand_path + return expanded.relative_path_from(@docs_dir) if expanded.to_s.start_with?("#{@docs_dir}/") + + nil + rescue ArgumentError + nil + end + + def include_output_path(chapter) + @docs_dir.join('generated', "#{chapter.path.basename(chapter.path.extname)}-includes.adoc") + end +end + +def output_base(docs_dir) + targets = Array(docs_dir).map { |path| Pathname.new(path).expand_path } + directory = targets.find(&:directory?) + return directory.join('arc42') if directory && directory.basename.to_s == 'docs' && directory.join('arc42').directory? + + directory || targets.first.dirname +end + +class ArtifactRenderHelper + def initialize(output_path, artifacts) + @output_path = Pathname.new(output_path).expand_path + @artifacts_by_id = artifacts.each_with_object({}) do |artifact, index| + index[artifact.metadata['id']] = artifact if artifact.metadata + end + end + + def artifact_link(artifact, label: nil) + "xref:#{artifact_anchor(artifact.path)}[#{cell(label || artifact.metadata['id'])}]" + end + + def artifact_ref(id, artifacts_by_id = @artifacts_by_id) + artifact = artifacts_by_id[id] + return cell(id) unless artifact + + artifact_link(artifact) + end + + def relation_targets(artifact, type) + matches = Array(artifact.metadata['relations']).select { |relation| relation['type'] == type } + return '-' if matches.empty? + + matches.map { |relation| artifact_ref(relation['target']) }.join(" +\n") + end + + def definition_table_fields(artifact) + body = artifact.path.read.split(/^---\s*$/, 3).last.to_s + fields = {} + body.scan(/^\|\s*([^|\n]+?)\s*\|\s*([^|\n]+(?:\n(?!\|===|\| [^|]+\s*\|).*)*)/m) do |key, value| + fields[key.strip] = value.strip.gsub(/\s+/, ' ').delete_suffix('.') + end + fields + end + + def short_id(id) + id.to_s.split('-', 3).first(2).join('-') + end + + def cell(value) + text = value.to_s.strip + return '-' if text.empty? + + text.gsub('|', '\|').gsub("\n", ' ') + end + + private + + def anchor_for(path) + normalized_anchor(path.basename(path.extname).to_s) + end + + def artifact_anchor(path) + explicit_anchor(path) || anchor_for(path) + end + + def explicit_anchor(path) + text = Pathname.new(path).read + body = text.start_with?("---\n") ? text.split(/^---\s*$/, 3).last.to_s : text + body.each_line.first(20).each do |line| + return Regexp.last_match(1) if line =~ /\[\[([a-z][a-z0-9-]*)\]\]/ + return Regexp.last_match(1) if line =~ /\[#([a-z][a-z0-9-]*)\]/ + end + nil + rescue Errno::ENOENT + nil + end + + def normalized_anchor(value) + value.to_s + .downcase + .sub(/\A[0-9]+[-_ ]+/, '') + .gsub(/[^a-z0-9]+/, '-') + .gsub(/\A-+|-+\z/, '') + end +end + +class DocumentationGenerator + def initialize(root:, docs_dir:, matrix_output_path: nil) + @root = Pathname.new(root).expand_path + @docs_dir = Array(docs_dir) + @matrix_output_path = matrix_output_path + end + + def write(artifacts) + written = [] + metadata_artifacts = artifacts.select(&:metadata) + matrix = TraceabilityMatrixGenerator.new( + root: @root, + docs_dir: @docs_dir, + output_path: @matrix_output_path + ) + written << matrix.write(metadata_artifacts) + + artifact_indexes = ArtifactIndexGenerator.new(root: @root, docs_dir: @docs_dir) + written.concat(artifact_indexes.write(metadata_artifacts)) + + open_questions = OpenQuestionsIndexGenerator.new(root: @root, docs_dir: @docs_dir) + written.concat(open_questions.write) + + chapter_includes = ChapterIncludeFragmentGenerator.new(root: @root, docs_dir: @docs_dir) + written.concat(chapter_includes.write(metadata_artifacts)) + + traceability = TraceabilityFragmentGenerator.new(root: @root, docs_dir: @docs_dir) + written.concat(traceability.write(metadata_artifacts)) + + written + end +end + + +if $PROGRAM_NAME == __FILE__ + root = Pathname.new(__dir__).join('..').expand_path + default_docs_targets = [ + root.join('src/docs') + ] + options = { + docs_dir: default_docs_targets, + artifact_schema: root.join('metamodel/artifact.schema.yaml'), + relations_schema: root.join('metamodel/relations.schema.yaml'), + generate: false, + output: nil, + custom_docs: false + } + + OptionParser.new do |parser| + parser.banner = 'Usage: ruby scripts/validate-metamodel.rb [options]' + parser.on('--docs PATH', 'Architecture .adoc file or directory; may be repeated') do |value| + options[:docs_dir] = [] if options[:docs_dir] == default_docs_targets + options[:docs_dir] << Pathname.new(value) + options[:custom_docs] = true + end + parser.on('--relations-schema FILE', 'Path to metamodel/relations.schema.yaml') do |value| + options[:relations_schema] = Pathname.new(value) + end + parser.on('--artifact-schema FILE', 'Path to metamodel/artifact.schema.yaml') do |value| + options[:artifact_schema] = Pathname.new(value) + end + parser.on('--generate', 'Generate derived AsciiDoc indexes and traceability fragments after successful validation') do + options[:generate] = true + end + parser.on('--output FILE', 'Generated traceability matrix path; legacy override for --generate') do |value| + options[:output] = Pathname.new(value) + end + end.parse! + + validator = MetamodelValidator.new( + root: root, + docs_dir: options[:docs_dir], + artifact_schema: options[:artifact_schema], + relations_schema: options[:relations_schema] + ) + artifacts = validator.validate + validator.print_report(artifacts) + exit(1) unless validator.errors.empty? + + if options[:generate] + output_path = options[:output] || if options[:custom_docs] + first_docs_target = Pathname.new(Array(options[:docs_dir]).first) + output_base = first_docs_target.directory? ? first_docs_target : first_docs_target.dirname + output_base.join('generated/traceability-matrix.adoc') + else + root.join('src/docs/generated/traceability-matrix.adoc') + end + generator = DocumentationGenerator.new( + root: root, + docs_dir: options[:docs_dir], + matrix_output_path: output_path + ) + output_paths = generator.write(artifacts) + output_paths.each do |path| + puts "Generated: #{path.relative_path_from(root)}" + end + end +end diff --git a/src/docs/arc42/01-introduction-and-goals/doc-01001-quality-goals.adoc b/src/docs/arc42/01-introduction-and-goals/doc-01001-quality-goals.adoc new file mode 100644 index 0000000..64d4479 --- /dev/null +++ b/src/docs/arc42/01-introduction-and-goals/doc-01001-quality-goals.adoc @@ -0,0 +1,35 @@ +--- +id: DOC-01001-quality-goals +type: Document +title: Quality Goals +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Lists the primary quality goals derived from README and workflow evidence. +tags: +- arc42 +- quality +relations: +- type: refines + target: DOC-01000-introduction-and-goals + status: proposed + rationale: This detail page elaborates the introduction and goals chapter. + reviewed: false +metadata_version: '1.0' +--- +[[quality-goals]] +== Quality Goals + +The top-level quality goals are derived from `README.md`, +`.github/workflows/create-docs-toolbox-image.yml`, and `Dockerfile`. + +[cols="1,3,3", options="header"] +|=== +| Priority | Goal | Evidence +| 1 | Reproducible documentation builds across local and CI environments. | README states the image is for reproducible Docs-as-Code pipelines. +| 2 | Fast contributor onboarding with zero local toolchain setup. | README emphasizes zero-setup onboarding and avoiding random local installs. +| 3 | Consistent CI execution through a published container image. | GitHub Actions example uses the image as a job container. +| 4 | Traceable image publication. | Workflow publishes a Dockerfile-hash tag and tag-based aliases. +|=== diff --git a/src/docs/arc42/01-introduction-and-goals/doc-01002-requirements-overview.adoc b/src/docs/arc42/01-introduction-and-goals/doc-01002-requirements-overview.adoc new file mode 100644 index 0000000..a0bee0a --- /dev/null +++ b/src/docs/arc42/01-introduction-and-goals/doc-01002-requirements-overview.adoc @@ -0,0 +1,40 @@ +--- +id: DOC-01002-requirements-overview +type: Document +title: Requirements Overview +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Summarizes functional requirements visible in the current repository. +tags: +- arc42 +- requirements +relations: +- type: refines + target: DOC-01000-introduction-and-goals + status: proposed + rationale: This detail page elaborates the introduction and goals chapter. + reviewed: false +metadata_version: '1.0' +--- +[[requirements-overview]] +== Requirements Overview + +[cols="1,3,3", options="header"] +|=== +| ID | Requirement | Evidence +| REQ-001 | The repository shall build a Docker image named `docs-toolbox`. | Dockerfile and workflow image name step. +| REQ-002 | The image shall include Asciidoctor, Asciidoctor Diagram, Asciidoctor reveal.js, Pandoc, Graphviz, unzip, and curl. | Dockerfile package and gem installation. +| REQ-003 | The image shall allow callers to run arbitrary commands. | Dockerfile uses an empty `ENTRYPOINT []`. +| REQ-004 | The release workflow shall publish multi-platform images to GHCR. | Workflow uses Docker Buildx with `linux/amd64,linux/arm64` and `--push`. +| REQ-005 | The workflow shall publish a Dockerfile-hash tag for every build on `main` or tags. | Workflow computes `df-`. +| REQ-006 | The repository shall build architecture documentation HTML without Gradle, including rendered PlantUML diagrams. | `build.sh` validates metadata, generates fragments, and runs Asciidoctor with `asciidoctor-diagram`. +| REQ-007 | Pull requests shall validate architecture documentation. | `.github/workflows/architecture-docs.yml` runs `./build.sh validate` for pull requests. +| REQ-008 | Pushes to `main` shall publish generated architecture HTML to GitHub Pages. | `.github/workflows/architecture-docs.yml` builds `build/architecture` and deploys it with GitHub Pages actions. +| REQ-009 | The image shall expose `asciidoctor-revealjs` so downstream projects can build Reveal.js presentations from `.adoc` files. | Dockerfile gem installation and README usage example. +|=== + +The list is incomplete until maintainers confirm which README claims are part +of the supported contract. diff --git a/src/docs/arc42/01-introduction-and-goals/doc-01003-stakeholders.adoc b/src/docs/arc42/01-introduction-and-goals/doc-01003-stakeholders.adoc new file mode 100644 index 0000000..fe0079d --- /dev/null +++ b/src/docs/arc42/01-introduction-and-goals/doc-01003-stakeholders.adoc @@ -0,0 +1,32 @@ +--- +id: DOC-01003-stakeholders +type: Document +title: Stakeholders +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Identifies stakeholder roles visible from README and workflow usage. +tags: +- arc42 +- stakeholders +relations: +- type: refines + target: DOC-01000-introduction-and-goals + status: proposed + rationale: This detail page elaborates the introduction and goals chapter. + reviewed: false +metadata_version: '1.0' +--- +[[stakeholders]] +== Stakeholders + +[cols="1,3,3", options="header"] +|=== +| Stakeholder | Interest | Evidence +| Documentation contributors | Run documentation builds without local setup. | README local Docker usage. +| CI maintainers | Use one consistent toolchain image in workflows. | README GitHub Actions example. +| Project maintainers | Publish traceable and reusable image versions. | GHCR workflow with hash and Git tags. +| Downstream Docs-as-Code projects | Build AsciiDoc, Markdown, diagram, and document outputs. | README typical use cases. +|=== diff --git a/src/docs/arc42/02-architecture-constraints/doc-02001-toolbox-constraints.adoc b/src/docs/arc42/02-architecture-constraints/doc-02001-toolbox-constraints.adoc new file mode 100644 index 0000000..a3e8ed9 --- /dev/null +++ b/src/docs/arc42/02-architecture-constraints/doc-02001-toolbox-constraints.adoc @@ -0,0 +1,41 @@ +--- +id: DOC-02001-toolbox-constraints +type: Document +title: Toolbox Constraints +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Captures binding constraints currently visible in repository files. +tags: +- arc42 +- constraints +relations: +- type: refines + target: DOC-02000-architecture-constraints + status: proposed + rationale: This detail page elaborates the architecture constraints chapter. + reviewed: false +- type: constrains + target: ADR-001-containerized-docs-toolchain + status: proposed + rationale: Existing repository evidence already centers delivery on a Docker image. + reviewed: false +metadata_version: '1.0' +--- +[[toolbox-constraints]] +== Toolbox Constraints + +[cols="1,3,3", options="header"] +|=== +| Constraint | Description | Evidence +| Container delivery | The supported toolchain is packaged as an OCI/Docker image. | README and Dockerfile. +| Base image | The current base is `openjdk:22-jdk-slim`. | Dockerfile `FROM` line. +| Installed packages | Tools are installed with `apt-get install`. | Dockerfile package installation. +| GHCR publication | Images are published to `ghcr.io/${GITHUB_REPOSITORY_OWNER}/docs-toolbox`. | Workflow `Set image name` step. +| Multi-platform build | Workflow builds `linux/amd64` and `linux/arm64`. | Workflow `docker buildx build` command. +| MIT license | Use and distribution follow MIT license terms. | `LICENSE.md`. +|=== + +Open constraints are recorded in xref:canvas-questions-and-answers[]. diff --git a/src/docs/arc42/03-system-scope-and-context/doc-03001-business-context.adoc b/src/docs/arc42/03-system-scope-and-context/doc-03001-business-context.adoc new file mode 100644 index 0000000..8bd18b1 --- /dev/null +++ b/src/docs/arc42/03-system-scope-and-context/doc-03001-business-context.adoc @@ -0,0 +1,43 @@ +--- +id: DOC-03001-business-context +type: Document +title: Business Context +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Shows users and external systems around the docs-toolbox image. +tags: +- arc42 +- context +relations: +- type: refines + target: DOC-03000-system-scope-and-context + status: proposed + rationale: This detail page elaborates the system scope and context chapter. + reviewed: false +metadata_version: '1.0' +--- +[[business-context]] +== Business Context + +include::../../fragments/architecture/system-boundary.adoc[] + +[plantuml, docs-toolbox-context, svg] +---- +@startuml +left to right direction +actor "Documentation contributor" as Contributor +actor "CI maintainer" as Maintainer +rectangle "docs-toolbox image" as Toolbox +cloud "GHCR" as Ghcr +rectangle "Downstream Docs-as-Code project" as Project + +Contributor --> Toolbox : runs documentation commands +Maintainer --> Project : configures CI image +Project --> Ghcr : pulls image +Ghcr --> Toolbox : stores published image +Toolbox --> Contributor : provides toolchain +@enduml +---- diff --git a/src/docs/arc42/03-system-scope-and-context/doc-03002-technical-context.adoc b/src/docs/arc42/03-system-scope-and-context/doc-03002-technical-context.adoc new file mode 100644 index 0000000..ac9c091 --- /dev/null +++ b/src/docs/arc42/03-system-scope-and-context/doc-03002-technical-context.adoc @@ -0,0 +1,32 @@ +--- +id: DOC-03002-technical-context +type: Document +title: Technical Context +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Identifies technical interfaces for building, publishing, and running the image. +tags: +- arc42 +- context +relations: +- type: refines + target: DOC-03000-system-scope-and-context + status: proposed + rationale: This detail page elaborates the system scope and context chapter. + reviewed: false +metadata_version: '1.0' +--- +[[technical-context]] +== Technical Context + +[cols="1,2,3", options="header"] +|=== +| Interface | Partner | Description +| Docker build context | Docker Buildx | The workflow builds the repository root using `Dockerfile`. +| GHCR registry API | GHCR | The workflow authenticates and pushes built images. +| Container command execution | Docker or CI runtime | Callers mount a workspace and run documentation commands inside the image. +| README marker block | GitHub Actions shell step | The workflow extracts package description metadata from README markers. +|=== diff --git a/src/docs/arc42/04-solution-strategy/doc-04001-strategy-overview.adoc b/src/docs/arc42/04-solution-strategy/doc-04001-strategy-overview.adoc new file mode 100644 index 0000000..dec6d02 --- /dev/null +++ b/src/docs/arc42/04-solution-strategy/doc-04001-strategy-overview.adoc @@ -0,0 +1,40 @@ +--- +id: DOC-04001-strategy-overview +type: Document +title: Strategy Overview +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes the main strategy decisions visible in the current repository. +tags: +- arc42 +- strategy +relations: +- type: refines + target: DOC-04000-solution-strategy + status: proposed + rationale: This detail page elaborates the solution strategy chapter. + reviewed: false +- type: addresses + target: QS-001-reproducible-toolchain + status: proposed + rationale: A single image is the strategy for reproducible documentation builds. + reviewed: false +metadata_version: '1.0' +--- +[[strategy-overview]] +== Strategy Overview + +The current architecture uses a container image as the distribution unit for the +Docs-as-Code toolchain. Build and publication are automated with GitHub Actions. + +[cols="1,3,3", options="header"] +|=== +| Strategy | Rationale | Evidence +| Containerize the toolchain | Avoid host setup and CI drift. | README philosophy and Dockerfile. +| Keep entrypoint empty | Let consumers run arbitrary documentation commands. | Dockerfile `ENTRYPOINT []`. +| Publish traceable tags | Help users choose deterministic image references. | Workflow `df-` tag. +| Reuse README marker text | Keep package description close to product documentation. | Workflow README extraction step. +|=== diff --git a/src/docs/arc42/05-building-block-view/doc-05001-level-1.adoc b/src/docs/arc42/05-building-block-view/doc-05001-level-1.adoc new file mode 100644 index 0000000..17bdf23 --- /dev/null +++ b/src/docs/arc42/05-building-block-view/doc-05001-level-1.adoc @@ -0,0 +1,59 @@ +--- +id: DOC-05001-level-1 +type: Document +title: Level 1 Building Blocks +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes the top-level building blocks of docs-toolbox. +tags: +- arc42 +- building-blocks +relations: +- type: refines + target: DOC-05000-building-block-view + status: proposed + rationale: This detail page elaborates the building block view chapter. + reviewed: false +- type: affects + target: QS-002-toolchain-availability + status: proposed + rationale: Installed toolchain packages determine whether documented commands are available. + reviewed: false +metadata_version: '1.0' +--- +[[level-1-building-blocks]] +== Level 1 Building Blocks + +[plantuml, docs-toolbox-building-blocks, svg] +---- +@startuml +!include + +Person(user, "Docs-as-Code user", "Runs documentation commands") +System_Boundary(toolbox, "docs-toolbox") { + Container(image, "OCI image", "Docker", "Runtime environment for documentation commands") + Container(toolchain, "Documentation toolchain", "Debian packages and Ruby gems", "Asciidoctor, Asciidoctor Diagram, Asciidoctor reveal.js, Pandoc, Graphviz, unzip, curl") + Container(metadata, "Image metadata", "OCI labels and annotations", "Title, description, source, license") +} +System_Ext(ghcr, "GHCR", "Container registry") +System_Ext(workflow, "GitHub Actions workflow", "Builds and publishes images") + +Rel(user, image, "Runs commands in") +Rel(image, toolchain, "Contains") +Rel(workflow, image, "Builds") +Rel(workflow, ghcr, "Pushes") +Rel(metadata, ghcr, "Describes package") +@enduml +---- + +[cols="1,2,2,2", options="header"] +|=== +| Building Block | Responsibility | Interface | Storage Location +| OCI image | Provide a runnable documentation toolbox environment. | Docker image command execution. | Built from `Dockerfile`, published to GHCR. +| Documentation toolchain | Provide Asciidoctor, Asciidoctor Diagram, Asciidoctor reveal.js, Pandoc, Graphviz, unzip, and curl. | CLI commands and Asciidoctor Ruby extensions inside the image. | Installed by `apt-get` and `gem install` in `Dockerfile`. +| Build workflow | Build, tag, and push the image. | GitHub Actions workflow dispatch, push, and tag events. | `.github/workflows/create-docs-toolbox-image.yml`. +| README description marker | Provide package description metadata. | Marker block parsed by workflow shell step. | `README.md`. +|=== diff --git a/src/docs/arc42/06-runtime-view/doc-06001-local-usage.adoc b/src/docs/arc42/06-runtime-view/doc-06001-local-usage.adoc new file mode 100644 index 0000000..8cba681 --- /dev/null +++ b/src/docs/arc42/06-runtime-view/doc-06001-local-usage.adoc @@ -0,0 +1,64 @@ +--- +id: DOC-06001-local-usage +type: Document +title: Local Usage Runtime Scenario +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes how a contributor runs a documentation command in the image. +tags: +- arc42 +- runtime +relations: +- type: refines + target: DOC-06000-runtime-view + status: proposed + rationale: This detail page elaborates the runtime view chapter. + reviewed: false +- type: addresses + target: QS-001-reproducible-toolchain + status: proposed + rationale: Local usage is the primary reproducibility workflow. + reviewed: false +metadata_version: '1.0' +--- +[[local-usage-runtime-scenario]] +== Local Usage Runtime Scenario + +[plantuml, docs-toolbox-local-usage, svg] +---- +@startuml +actor Contributor +participant "Docker runtime" as Docker +participant "docs-toolbox image" as Image +participant "Mounted workspace" as Workspace + +Contributor -> Docker : docker run --rm -v project:/workspace -w /workspace image command +Docker -> Image : start container +Image -> Workspace : read documentation sources +Image --> Contributor : command output and exit code +@enduml +---- + +The README example runs `asciidoctor -v` in the toolbox container. + +=== Architecture Documentation Build + +For this repository itself, `./build.sh build` validates architecture metadata, +generates derived fragments, and renders `src/docs/doc-001-arc42.adoc` to +`build/architecture/index.html` with Asciidoctor and `asciidoctor-diagram`. +Generated diagram assets are written beside the HTML output. + +This is intentional dogfooding: the architecture documentation for the toolbox is +generated with the toolbox image itself whenever a container engine is available. + +The script first computes the local Dockerfile hash and tries to pull +`ghcr.io/docs-as-code-toolkit/docs-toolbox:df-`. If that image is not +available, the script builds a local image from the current Dockerfile before +running the documentation build. This keeps local Dockerfile changes visible to +the build without requiring Gradle. + +Gradle projects can still use AsciidoctorJ inside the same toolbox image. This +repository's direct CLI build uses the Ruby Asciidoctor path. diff --git a/src/docs/arc42/06-runtime-view/doc-06002-image-publication.adoc b/src/docs/arc42/06-runtime-view/doc-06002-image-publication.adoc new file mode 100644 index 0000000..ad7de15 --- /dev/null +++ b/src/docs/arc42/06-runtime-view/doc-06002-image-publication.adoc @@ -0,0 +1,45 @@ +--- +id: DOC-06002-image-publication +type: Document +title: Image Publication Runtime Scenario +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes the GitHub Actions image build and push workflow. +tags: +- arc42 +- runtime +relations: +- type: refines + target: DOC-06000-runtime-view + status: proposed + rationale: This detail page elaborates the runtime view chapter. + reviewed: false +- type: addresses + target: QS-003-publication-traceability + status: proposed + rationale: The publication scenario creates traceable image tags and metadata. + reviewed: false +metadata_version: '1.0' +--- +[[image-publication-runtime-scenario]] +== Image Publication Runtime Scenario + +[plantuml, docs-toolbox-publication, svg] +---- +@startuml +actor Maintainer +participant "GitHub Actions" as Actions +participant "Docker Buildx" as Buildx +database "GHCR" as Ghcr + +Maintainer -> Actions : push to main, push tag, or workflow_dispatch +Actions -> Actions : compute image name and tags +Actions -> Actions : extract README description +Actions -> Buildx : build linux/amd64 and linux/arm64 +Buildx -> Ghcr : push tags and cache +Actions --> Maintainer : print hash tag and publication summary +@enduml +---- diff --git a/src/docs/arc42/06-runtime-view/doc-06003-failure-recovery.adoc b/src/docs/arc42/06-runtime-view/doc-06003-failure-recovery.adoc new file mode 100644 index 0000000..438aa44 --- /dev/null +++ b/src/docs/arc42/06-runtime-view/doc-06003-failure-recovery.adoc @@ -0,0 +1,38 @@ +--- +id: DOC-06003-failure-recovery +type: Document +title: Failure And Recovery Runtime Scenario +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records likely failure and recovery paths for image publication and usage. +tags: +- arc42 +- runtime +- failure +relations: +- type: refines + target: DOC-06000-runtime-view + status: proposed + rationale: This detail page elaborates the runtime view chapter. + reviewed: false +- type: affects + target: RISK-001-unpinned-package-versions + status: proposed + rationale: Package repository changes can cause build failures or drift. + reviewed: false +metadata_version: '1.0' +--- +[[failure-recovery-runtime-scenario]] +== Failure And Recovery Runtime Scenario + +[cols="1,3,3", options="header"] +|=== +| Failure | Observed Effect | Recovery Candidate +| GHCR authentication fails | Workflow cannot push images. | Verify `GHCR_PAT`, package permissions, and GitHub actor access. +| Package installation fails | Docker build fails during `apt-get install`. | Re-run after upstream recovery or pin/update affected package source. +| Tool command missing in image | Downstream documentation command fails. | Add a smoke test and update Dockerfile if the tool is supported. +| README marker missing | Workflow uses fallback description. | Restore marker block or accept fallback behavior. +|=== diff --git a/src/docs/arc42/07-deployment-view/doc-07001-local-and-ci.adoc b/src/docs/arc42/07-deployment-view/doc-07001-local-and-ci.adoc new file mode 100644 index 0000000..fbe8c2e --- /dev/null +++ b/src/docs/arc42/07-deployment-view/doc-07001-local-and-ci.adoc @@ -0,0 +1,33 @@ +--- +id: DOC-07001-local-and-ci +type: Document +title: Local And CI Deployment +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes local, CI, and registry deployment environments. +tags: +- arc42 +- deployment +relations: +- type: refines + target: DOC-07000-deployment-view + status: proposed + rationale: This detail page elaborates the deployment view chapter. + reviewed: false +metadata_version: '1.0' +--- +[[local-and-ci-deployment]] +== Local And CI Deployment + +[cols="1,2,3", options="header"] +|=== +| Environment | Deployment Form | Notes +| Contributor workstation | Docker container run with mounted workspace. | README uses `docker run --rm -v $(pwd):/workspace -w /workspace`. +| GitHub Actions runner | Job container or image build environment. | README shows the image as a job container; workflow builds with Buildx. +| GHCR | Published multi-platform image. | Workflow pushes `linux/amd64` and `linux/arm64` images. +| Downstream CI | Pinned image reference. | README recommends pinning versions for determinism. +| GitHub Pages | Static architecture HTML generated from `src/docs/doc-001-arc42.adoc`. | `.github/workflows/architecture-docs.yml` uploads `build/architecture` to the expected URL `https://docs-as-code-toolkit.github.io/docs-toolbox/` after the first successful deployment. +|=== diff --git a/src/docs/arc42/08-crosscutting-concepts/doc-08001-threat-model.adoc b/src/docs/arc42/08-crosscutting-concepts/doc-08001-threat-model.adoc new file mode 100644 index 0000000..a71d39f --- /dev/null +++ b/src/docs/arc42/08-crosscutting-concepts/doc-08001-threat-model.adoc @@ -0,0 +1,32 @@ +--- +id: DOC-08001-threat-model +type: Document +title: Threat Model +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Draft STRIDE-style threats for the toolbox image and publication workflow. +tags: +- arc42 +- threat-model +relations: +- type: refines + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: This detail page elaborates the crosscutting concepts chapter. + reviewed: false +metadata_version: '1.0' +--- +[[threat-model]] +== Threat Model + +[cols="1,1,3,3", options="header"] +|=== +| ID | STRIDE | Threat | Mitigation Candidate +| T-001 | Tampering | Published image contents may not match maintainer intent. | Build from repository source in GitHub Actions and publish traceable tags. +| T-002 | Spoofing | Unauthorized actor may push to GHCR if credentials are misused. | Limit `GHCR_PAT` scope and review package permissions. +| T-003 | Information disclosure | Workflow logs may expose sensitive token handling if commands change. | Keep token use through `docker login --password-stdin`. +| T-004 | Denial of service | Upstream package or registry outage may block image publication. | Retry after outage and consider package pinning/mirroring policy. +|=== diff --git a/src/docs/arc42/08-crosscutting-concepts/doc-08002-security.adoc b/src/docs/arc42/08-crosscutting-concepts/doc-08002-security.adoc new file mode 100644 index 0000000..efd19f5 --- /dev/null +++ b/src/docs/arc42/08-crosscutting-concepts/doc-08002-security.adoc @@ -0,0 +1,40 @@ +--- +id: DOC-08002-security +type: Document +title: Security +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records security concepts for image publication and consumption. +tags: +- arc42 +- security +relations: +- type: refines + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: This detail page elaborates the crosscutting concepts chapter. + reviewed: false +- type: mitigates + target: RISK-002-registry-publication-credentials + status: proposed + rationale: Credential handling and permissions reduce registry publication risk. + reviewed: false +metadata_version: '1.0' +--- +[[security]] +== Security + +Current evidence: + +* Workflow requests `contents: read` and `packages: write`. +* GHCR login uses `${{ secrets.GHCR_PAT }}` via standard input. +* The Dockerfile removes `/var/lib/apt/lists/*` after package installation. + +Open security decisions: + +* Whether image signing is required. +* Whether vulnerability scanning is required before publishing. +* Whether the GHCR token can be replaced with `GITHUB_TOKEN` permissions. diff --git a/src/docs/arc42/08-crosscutting-concepts/doc-08003-testing.adoc b/src/docs/arc42/08-crosscutting-concepts/doc-08003-testing.adoc new file mode 100644 index 0000000..8095a2f --- /dev/null +++ b/src/docs/arc42/08-crosscutting-concepts/doc-08003-testing.adoc @@ -0,0 +1,54 @@ +--- +id: DOC-08003-testing +type: Document +title: Testing +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Identifies current test gaps and proposed smoke test coverage. +tags: +- arc42 +- testing +relations: +- type: refines + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: This detail page elaborates the crosscutting concepts chapter. + reviewed: false +- type: verifies + target: QS-002-toolchain-availability + status: proposed + rationale: Smoke tests should verify documented tool commands are present. + reviewed: false +metadata_version: '1.0' +--- +[[testing]] +== Testing + +No automated tests were found in the repository during this migration. + +Proposed smoke tests: + +* Build the Docker image. +* Run `asciidoctor --version`. +* Run `asciidoctor-revealjs --version`. +* Run `ruby -e 'require "asciidoctor-diagram"'`. +* Run `ruby -e 'require "asciidoctor-diagram/plantuml"'`. +* Run `pandoc --version`. +* Run `dot -V`. +* Run `curl --version`. +* Run `unzip -v`. +* Render a minimal PlantUML block through `asciidoctor -r asciidoctor-diagram`. +* Render a minimal slide deck through `asciidoctor-revealjs` with an explicit + `revealjsdir` value. + +These tests would turn README claims about available tools into executable +evidence. + +Current documentation checks: + +* Pull requests run `./build.sh validate`. +* The main branch Pages workflow runs `./build.sh build`. +* `build.sh validate` runs `ruby scripts/validate-metamodel.rb --generate`. diff --git a/src/docs/arc42/08-crosscutting-concepts/doc-08004-observability.adoc b/src/docs/arc42/08-crosscutting-concepts/doc-08004-observability.adoc new file mode 100644 index 0000000..de66e65 --- /dev/null +++ b/src/docs/arc42/08-crosscutting-concepts/doc-08004-observability.adoc @@ -0,0 +1,32 @@ +--- +id: DOC-08004-observability +type: Document +title: Observability +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes observable outputs from usage and publication workflows. +tags: +- arc42 +- observability +relations: +- type: refines + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: This detail page elaborates the crosscutting concepts chapter. + reviewed: false +metadata_version: '1.0' +--- +[[observability]] +== Observability + +Observable signals currently visible from repository evidence: + +* Docker command exit codes and standard output for local usage. +* GitHub Actions logs for image name, tags, README description, and summary. +* GHCR package tags and annotations after a successful build. + +No runtime application logs, metrics, traces, or audit trails exist inside the +image because the image is a toolbox environment, not a long-running service. diff --git a/src/docs/arc42/08-crosscutting-concepts/doc-08005-error-handling.adoc b/src/docs/arc42/08-crosscutting-concepts/doc-08005-error-handling.adoc new file mode 100644 index 0000000..13a964c --- /dev/null +++ b/src/docs/arc42/08-crosscutting-concepts/doc-08005-error-handling.adoc @@ -0,0 +1,31 @@ +--- +id: DOC-08005-error-handling +type: Document +title: Error Handling +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Captures error handling behavior from shell scripts and Docker usage. +tags: +- arc42 +- error-handling +relations: +- type: refines + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: This detail page elaborates the crosscutting concepts chapter. + reviewed: false +metadata_version: '1.0' +--- +[[error-handling]] +== Error Handling + +The workflow shell steps use `set -euo pipefail` in the tag computation, +description extraction, and build/push steps. This means most command failures +fail the workflow instead of being silently ignored. + +The README description extraction step has a fallback description if the marker +block is absent or empty. Docker command failures are surfaced through the +container process exit code. diff --git a/src/docs/arc42/09-architecture-decisions/adr-001-containerized-docs-toolchain.adoc b/src/docs/arc42/09-architecture-decisions/adr-001-containerized-docs-toolchain.adoc new file mode 100644 index 0000000..56d57f8 --- /dev/null +++ b/src/docs/arc42/09-architecture-decisions/adr-001-containerized-docs-toolchain.adoc @@ -0,0 +1,95 @@ +--- +id: ADR-001-containerized-docs-toolchain +type: ADR +title: Containerized Docs Toolchain +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Proposes treating the Docker image as the supported distribution unit for the Docs-as-Code toolchain. +tags: +- adr +- container +relations: +- type: addresses + target: QS-001-reproducible-toolchain + status: proposed + rationale: A container image supports reproducible local and CI toolchain execution. + reviewed: false +- type: introduces_risk + target: RISK-001-unpinned-package-versions + status: proposed + rationale: Container builds based on unpinned package repositories may drift over time. + reviewed: false +metadata_version: '1.0' +--- +[[adr-001-containerized-docs-toolchain]] +== ADR-001: Containerized Docs Toolchain + +=== Status + +Proposed. + +This ADR is derived from existing repository evidence and requires maintainer +review before it becomes accepted architecture truth. + +=== Decision + +We propose to treat the `docs-toolbox` Docker image as the supported +distribution unit for the documentation toolchain. + +=== Context + +The README describes the image as the single source of truth for the +documentation toolchain. The Dockerfile packages OpenJDK plus documentation +tools. The workflow publishes the image to GHCR. + +=== Decision Drivers + +* Reproducible local and CI builds. +* Low contributor setup effort. +* Traceable image publication. + +=== Considered Options + +==== Option 1: Containerized Toolbox Image + +Package the toolchain as a Docker image. + +==== Option 2: Host Installation Documentation + +Document installation commands for each supported host environment. + +==== Option 3: Project-Specific CI Setup + +Let every downstream project install tools in its own pipeline. + +=== Pugh Matrix + +[.pugh-matrix] +[cols="2,1,1,1", options="header"] +|=== +| Criterion | Container image | Host installation | Project-specific CI +| Reproducibility | +1 | -1 | -1 +| Contributor onboarding | +1 | -1 | 0 +| CI consistency | +1 | -1 | -1 +| Maintenance simplicity | 0 | -1 | -1 +s| Sum s| 3 s| -4 s| -3 +|=== + +=== Consequences + +==== Positive + +* Downstream users can run the same image locally and in CI. +* Maintainers can publish traceable image tags. + +==== Negative + +* Image rebuilds may drift if upstream package versions change. +* Users need Docker or a compatible container runtime. + +==== Neutral or Follow-Up + +* Maintainers should decide whether package versions need explicit pinning. diff --git a/src/docs/arc42/09-architecture-decisions/adr-002-ghcr-publication-tags.adoc b/src/docs/arc42/09-architecture-decisions/adr-002-ghcr-publication-tags.adoc new file mode 100644 index 0000000..816b768 --- /dev/null +++ b/src/docs/arc42/09-architecture-decisions/adr-002-ghcr-publication-tags.adoc @@ -0,0 +1,86 @@ +--- +id: ADR-002-ghcr-publication-tags +type: ADR +title: GHCR Publication Tags +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Proposes documenting Dockerfile-hash, Git tag, and latest tag semantics. +tags: +- adr +- release +relations: +- type: addresses + target: QS-003-publication-traceability + status: proposed + rationale: Tag semantics are central to publication traceability. + reviewed: false +- type: introduces_risk + target: RISK-003-ambiguous-latest-tag + status: proposed + rationale: Users may treat `latest` as reproducible if guidance is unclear. + reviewed: false +metadata_version: '1.0' +--- +[[adr-002-ghcr-publication-tags]] +== ADR-002: GHCR Publication Tags + +=== Status + +Proposed. + +=== Decision + +We propose to document the current publication tag behavior as an explicit +architecture and user contract after maintainer review. + +=== Context + +The workflow always computes a Dockerfile hash tag `df-`. If the commit +has a Git tag, it also publishes the Git tag and `latest`. + +=== Decision Drivers + +* Deterministic downstream CI configuration. +* Clear release communication. +* Compatibility with the existing workflow. + +=== Considered Options + +==== Option 1: Current Tag Set + +Use Dockerfile hash tags for all builds and Git tag plus `latest` for tagged +commits. + +==== Option 2: Git Tags Only + +Publish only human release tags. + +==== Option 3: Latest Only + +Publish only `latest`. + +=== Pugh Matrix + +[.pugh-matrix] +[cols="2,1,1,1", options="header"] +|=== +| Criterion | Current tag set | Git tags only | Latest only +| Traceability | +1 | 0 | -1 +| User simplicity | 0 | +1 | +1 +| Reproducibility | +1 | +1 | -1 +| Workflow fit | +1 | 0 | -1 +s| Sum s| 3 s| 2 s| -2 +|=== + +=== Consequences + +==== Positive + +* Users can choose between hash-based traceability and release tags. + +==== Negative + +* README guidance must explain when `latest` is published and why pinning is preferred. diff --git a/src/docs/arc42/09-architecture-decisions/doc-09001-adr-candidates.adoc b/src/docs/arc42/09-architecture-decisions/doc-09001-adr-candidates.adoc new file mode 100644 index 0000000..24e0d15 --- /dev/null +++ b/src/docs/arc42/09-architecture-decisions/doc-09001-adr-candidates.adoc @@ -0,0 +1,33 @@ +--- +id: DOC-09001-adr-candidates +type: Document +title: ADR Candidates +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Lists missing decision records identified during migration. +tags: +- arc42 +- adr +relations: +- type: refines + target: DOC-09000-architecture-decisions + status: proposed + rationale: This detail page elaborates the architecture decisions chapter. + reviewed: false +metadata_version: '1.0' +--- +[[adr-candidates]] +== ADR Candidates + +The migration found the following decision candidates: + +* Whether `openjdk:22-jdk-slim` is the long-term base image. +* Whether Debian package versions should be pinned. +* Whether `GHCR_PAT` should remain the registry authentication mechanism. +* Whether image signing or vulnerability scanning is required before publishing. +* Whether GitHub Pages is the long-term publication channel for architecture documentation. +* Whether Reveal.js frontend assets should be bundled in the toolbox image or + supplied by downstream projects through `revealjsdir`. diff --git a/src/docs/arc42/10-quality-requirements/doc-10002-quality-tree.adoc b/src/docs/arc42/10-quality-requirements/doc-10002-quality-tree.adoc new file mode 100644 index 0000000..f94c2a5 --- /dev/null +++ b/src/docs/arc42/10-quality-requirements/doc-10002-quality-tree.adoc @@ -0,0 +1,31 @@ +--- +id: DOC-10002-quality-tree +type: Document +title: Quality Tree +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Relates top-level quality goals to proposed quality scenarios. +tags: +- arc42 +- quality +relations: +- type: refines + target: DOC-10000-quality-requirements + status: proposed + rationale: This detail page elaborates the quality requirements chapter. + reviewed: false +metadata_version: '1.0' +--- +[[quality-tree]] +== Quality Tree + +* Reproducibility +** xref:qs-001-reproducible-toolchain[QS-001: Reproducible Toolchain] +** xref:qs-003-publication-traceability[QS-003: Publication Traceability] +* Usability +** xref:qs-002-toolchain-availability[QS-002: Toolchain Availability] +* Maintainability +** Candidate: automated smoke tests for all supported tools. diff --git a/src/docs/arc42/10-quality-requirements/qs-001-reproducible-toolchain.adoc b/src/docs/arc42/10-quality-requirements/qs-001-reproducible-toolchain.adoc new file mode 100644 index 0000000..4c68276 --- /dev/null +++ b/src/docs/arc42/10-quality-requirements/qs-001-reproducible-toolchain.adoc @@ -0,0 +1,44 @@ +--- +id: QS-001-reproducible-toolchain +type: QualityScenario +title: Reproducible Toolchain +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: A pinned image reference should let local and CI users run the same documentation toolchain. +tags: +- quality +- reproducibility +relations: +- type: refines + target: DOC-01001-quality-goals + status: proposed + rationale: The scenario concretizes the reproducibility quality goal. + reviewed: false +metadata_version: '1.0' +--- +[[qs-001-reproducible-toolchain]] +== QS-001: Reproducible Toolchain + +=== Status + +Proposed. + +=== Scenario + +[cols="1,3", options="header"] +|=== +| Element | Description +| Source | Documentation contributor or CI pipeline. +| Stimulus | Runs a documentation build using the same pinned `docs-toolbox` image reference. +| Environment | Local workstation and GitHub Actions runner. +| Artifact | Containerized documentation toolchain. +| Response | The same documented tools are available in both environments. +| Response Measure | All supported tool smoke-test commands pass in both environments for the same image reference. +|=== + +=== Assumptions + +* The exact supported smoke-test command list requires maintainer confirmation. diff --git a/src/docs/arc42/10-quality-requirements/qs-002-toolchain-availability.adoc b/src/docs/arc42/10-quality-requirements/qs-002-toolchain-availability.adoc new file mode 100644 index 0000000..8257cf2 --- /dev/null +++ b/src/docs/arc42/10-quality-requirements/qs-002-toolchain-availability.adoc @@ -0,0 +1,47 @@ +--- +id: QS-002-toolchain-availability +type: QualityScenario +title: Toolchain Availability +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: The image should expose the documented documentation tools. +tags: +- quality +- usability +relations: +- type: refines + target: DOC-01001-quality-goals + status: proposed + rationale: The scenario concretizes contributor onboarding and command availability. + reviewed: false +metadata_version: '1.0' +--- +[[qs-002-toolchain-availability]] +== QS-002: Toolchain Availability + +=== Status + +Proposed. + +=== Scenario + +[cols="1,3", options="header"] +|=== +| Element | Description +| Source | Downstream project pipeline. +| Stimulus | Starts the image and calls a documented CLI tool. +| Environment | Normal CI execution. +| Artifact | Installed packages inside `docs-toolbox`. +| Response | The command exists and returns version or help output. +| Response Measure | `asciidoctor`, `asciidoctor-revealjs`, `pandoc`, `dot`, `curl`, and `unzip` version/help checks all exit successfully, and Ruby can require `asciidoctor-diagram` and `asciidoctor-diagram/plantuml`. +|=== + +=== Assumptions + +* The command names should be confirmed against the actual image entrypoint and package paths. +* A minimal diagram rendering smoke test should confirm the Asciidoctor CLI path. +* A minimal slide rendering smoke test should confirm the Asciidoctor reveal.js + CLI path and explicit `revealjsdir` handling. diff --git a/src/docs/arc42/10-quality-requirements/qs-003-publication-traceability.adoc b/src/docs/arc42/10-quality-requirements/qs-003-publication-traceability.adoc new file mode 100644 index 0000000..377a791 --- /dev/null +++ b/src/docs/arc42/10-quality-requirements/qs-003-publication-traceability.adoc @@ -0,0 +1,44 @@ +--- +id: QS-003-publication-traceability +type: QualityScenario +title: Publication Traceability +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Published image tags should allow maintainers to trace images back to source inputs. +tags: +- quality +- release +relations: +- type: refines + target: DOC-01001-quality-goals + status: proposed + rationale: The scenario concretizes the image publication traceability quality goal. + reviewed: false +metadata_version: '1.0' +--- +[[qs-003-publication-traceability]] +== QS-003: Publication Traceability + +=== Status + +Proposed. + +=== Scenario + +[cols="1,3", options="header"] +|=== +| Element | Description +| Source | Maintainer. +| Stimulus | Inspects a published image tag after workflow completion. +| Environment | Successful GitHub Actions build on `main` or a Git tag. +| Artifact | GHCR image tags and annotations. +| Response | The image has a Dockerfile-hash tag and source annotation; tagged commits also publish Git tag and `latest`. +| Response Measure | Workflow summary prints the Dockerfile hash tag on every successful run and prints the Git tag when present. +|=== + +=== Assumptions + +* Maintainers should confirm whether source annotations are sufficient traceability or whether commit SHA labels are required. diff --git a/src/docs/arc42/11-risks-and-technical-debt/doc-11002-technical-debt.adoc b/src/docs/arc42/11-risks-and-technical-debt/doc-11002-technical-debt.adoc new file mode 100644 index 0000000..b92c7d3 --- /dev/null +++ b/src/docs/arc42/11-risks-and-technical-debt/doc-11002-technical-debt.adoc @@ -0,0 +1,30 @@ +--- +id: DOC-11002-technical-debt +type: Document +title: Technical Debt +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records technical debt candidates discovered during migration. +tags: +- arc42 +- technical-debt +relations: +- type: refines + target: DOC-11000-risks-and-technical-debt + status: proposed + rationale: This detail page elaborates the risks and technical debt chapter. + reviewed: false +metadata_version: '1.0' +--- +[[technical-debt]] +== Technical Debt + +[cols="1,3,3", options="header"] +|=== +| ID | Debt Candidate | Affected Building Block +| TD-001 | No automated smoke tests currently prove that the image contains every documented tool. | Documentation toolchain. +| TD-003 | Package version support policy is not recorded. | Dockerfile and publication workflow. +|=== diff --git a/src/docs/arc42/11-risks-and-technical-debt/risk-001-unpinned-package-versions.adoc b/src/docs/arc42/11-risks-and-technical-debt/risk-001-unpinned-package-versions.adoc new file mode 100644 index 0000000..b896cf0 --- /dev/null +++ b/src/docs/arc42/11-risks-and-technical-debt/risk-001-unpinned-package-versions.adoc @@ -0,0 +1,51 @@ +--- +id: RISK-001-unpinned-package-versions +type: Risk +title: Unpinned Package Versions +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Package versions can drift when the Dockerfile installs latest repository packages. +tags: +- risk +- reproducibility +relations: +- type: affects + target: QS-001-reproducible-toolchain + status: proposed + rationale: Package drift can undermine reproducibility claims across builds. + reviewed: false +metadata_version: '1.0' +--- +[[risk-001-unpinned-package-versions]] +== RISK-001: Unpinned Package Versions + +=== Status + +Proposed. + +=== Risk Statement + +Because the Dockerfile installs packages from distribution repositories without +explicit package version pins, future image rebuilds may contain different tool +versions, leading to unexpected documentation build differences. + +=== Assessment + +[cols="1,3", options="header"] +|=== +| Field | Value +| Likelihood | Medium. +| Impact | Medium. +| Priority | Medium. +| Timeframe | Medium-term. +| Confidence | Medium. +|=== + +=== Mitigation Options + +* Decide whether package pinning is required. +* Add smoke tests and publish tested image references. +* Document recommended tag pinning for downstream CI. diff --git a/src/docs/arc42/11-risks-and-technical-debt/risk-002-registry-publication-credentials.adoc b/src/docs/arc42/11-risks-and-technical-debt/risk-002-registry-publication-credentials.adoc new file mode 100644 index 0000000..bba6319 --- /dev/null +++ b/src/docs/arc42/11-risks-and-technical-debt/risk-002-registry-publication-credentials.adoc @@ -0,0 +1,51 @@ +--- +id: RISK-002-registry-publication-credentials +type: Risk +title: Registry Publication Credentials +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: The GHCR publication workflow depends on a package-write secret. +tags: +- risk +- security +relations: +- type: affects + target: QS-003-publication-traceability + status: proposed + rationale: Publication credentials affect the ability to publish traceable image tags. + reviewed: false +metadata_version: '1.0' +--- +[[risk-002-registry-publication-credentials]] +== RISK-002: Registry Publication Credentials + +=== Status + +Proposed. + +=== Risk Statement + +Because the workflow authenticates to GHCR using `GHCR_PAT`, token +misconfiguration or exposure may prevent image publication or allow unintended +package writes. + +=== Assessment + +[cols="1,3", options="header"] +|=== +| Field | Value +| Likelihood | Low. +| Impact | High. +| Priority | Medium. +| Timeframe | Near-term. +| Confidence | Medium. +|=== + +=== Mitigation Options + +* Keep workflow permissions minimal. +* Review whether `GITHUB_TOKEN` can replace the PAT. +* Rotate the token if exposure is suspected. diff --git a/src/docs/arc42/11-risks-and-technical-debt/risk-003-ambiguous-latest-tag.adoc b/src/docs/arc42/11-risks-and-technical-debt/risk-003-ambiguous-latest-tag.adoc new file mode 100644 index 0000000..d5234b8 --- /dev/null +++ b/src/docs/arc42/11-risks-and-technical-debt/risk-003-ambiguous-latest-tag.adoc @@ -0,0 +1,50 @@ +--- +id: RISK-003-ambiguous-latest-tag +type: Risk +title: Ambiguous Latest Tag +status: proposed +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Users may treat `latest` as reproducible despite it being a moving reference. +tags: +- risk +- release +relations: +- type: affects + target: QS-003-publication-traceability + status: proposed + rationale: Ambiguous tag guidance can reduce publication traceability for downstream users. + reviewed: false +metadata_version: '1.0' +--- +[[risk-003-ambiguous-latest-tag]] +== RISK-003: Ambiguous Latest Tag + +=== Status + +Proposed. + +=== Risk Statement + +Because `latest` is a moving tag and is published for tagged commits, downstream +users may pin `latest` in CI and lose reproducibility across time. + +=== Assessment + +[cols="1,3", options="header"] +|=== +| Field | Value +| Likelihood | Medium. +| Impact | Medium. +| Priority | Medium. +| Timeframe | Near-term. +| Confidence | Medium. +|=== + +=== Mitigation Options + +* Document recommended pinning behavior. +* Prefer Git tag or Dockerfile hash references in CI examples. +* Clarify when `latest` is published. diff --git a/src/docs/arc42/12-glossary/doc-12001-terms.adoc b/src/docs/arc42/12-glossary/doc-12001-terms.adoc new file mode 100644 index 0000000..a6c77be --- /dev/null +++ b/src/docs/arc42/12-glossary/doc-12001-terms.adoc @@ -0,0 +1,34 @@ +--- +id: DOC-12001-terms +type: Document +title: Terms +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Defines core docs-toolbox terminology. +tags: +- arc42 +- glossary +relations: +- type: refines + target: DOC-12000-glossary + status: proposed + rationale: This detail page elaborates the glossary chapter. + reviewed: false +metadata_version: '1.0' +--- +[[terms]] +== Terms + +[cols="1,3", options="header"] +|=== +| Term | Meaning +| Docs-as-Code | Treating documentation sources, reviews, builds, and publishing as code-like versioned workflows. +| docs-toolbox | The container image built by this repository for documentation pipelines. +| GHCR | GitHub Container Registry, the publication target used by the workflow. +| Dockerfile hash tag | Image tag of the form `df-` derived from the Dockerfile SHA-256 hash prefix. +| Asciidoctor reveal.js | Converter that turns AsciiDoc slide sources into Reveal.js HTML presentations. +| Supported tool | A CLI tool that maintainers confirm as part of the image contract. +|=== diff --git a/src/docs/arc42/doc-01000-introduction-and-goals.adoc b/src/docs/arc42/doc-01000-introduction-and-goals.adoc new file mode 100644 index 0000000..4a76535 --- /dev/null +++ b/src/docs/arc42/doc-01000-introduction-and-goals.adoc @@ -0,0 +1,23 @@ +--- +id: DOC-01000-introduction-and-goals +type: Document +title: Introduction And Goals +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes the purpose, stakeholders, requirements overview, and key quality goals for docs-toolbox. +tags: +- arc42 +- goals +relations: [] +metadata_version: '1.0' +--- +[[introduction-and-goals]] += Introduction And Goals + +`docs-toolbox` is a lightweight Docker image for running Docs-as-Code pipelines +in a reproducible environment locally and in CI. + +include::generated/doc-01000-introduction-and-goals-includes.adoc[] diff --git a/src/docs/arc42/doc-02000-architecture-constraints.adoc b/src/docs/arc42/doc-02000-architecture-constraints.adoc new file mode 100644 index 0000000..b313ace --- /dev/null +++ b/src/docs/arc42/doc-02000-architecture-constraints.adoc @@ -0,0 +1,23 @@ +--- +id: DOC-02000-architecture-constraints +type: Document +title: Architecture Constraints +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records externally visible constraints from the Dockerfile, workflow, and repository policy. +tags: +- arc42 +- constraints +relations: [] +metadata_version: '1.0' +--- +[[architecture-constraints]] += Architecture Constraints + +The current constraints are derived from the existing Dockerfile, workflow, and +license files. + +include::generated/doc-02000-architecture-constraints-includes.adoc[] diff --git a/src/docs/arc42/doc-03000-system-scope-and-context.adoc b/src/docs/arc42/doc-03000-system-scope-and-context.adoc new file mode 100644 index 0000000..b4b27c4 --- /dev/null +++ b/src/docs/arc42/doc-03000-system-scope-and-context.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-03000-system-scope-and-context +type: Document +title: System Scope And Context +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Defines the docs-toolbox image boundary and external systems. +tags: +- arc42 +- context +relations: [] +metadata_version: '1.0' +--- +[[system-scope-and-context]] += System Scope And Context + +include::generated/doc-03000-system-scope-and-context-includes.adoc[] diff --git a/src/docs/arc42/doc-04000-solution-strategy.adoc b/src/docs/arc42/doc-04000-solution-strategy.adoc new file mode 100644 index 0000000..9d8e604 --- /dev/null +++ b/src/docs/arc42/doc-04000-solution-strategy.adoc @@ -0,0 +1,23 @@ +--- +id: DOC-04000-solution-strategy +type: Document +title: Solution Strategy +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Summarizes the containerized toolbox strategy and toolkit documentation metamodel. +tags: +- arc42 +- strategy +relations: [] +metadata_version: '1.0' +--- +[[solution-strategy]] += Solution Strategy + +The solution strategy is to publish one versioned OCI image that contains the +documentation toolchain needed by downstream Docs-as-Code projects. + +include::generated/doc-04000-solution-strategy-includes.adoc[] diff --git a/src/docs/arc42/doc-05000-building-block-view.adoc b/src/docs/arc42/doc-05000-building-block-view.adoc new file mode 100644 index 0000000..5872d67 --- /dev/null +++ b/src/docs/arc42/doc-05000-building-block-view.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-05000-building-block-view +type: Document +title: Building Block View +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes the image, installed toolchain, and publication workflow as building blocks. +tags: +- arc42 +- building-blocks +relations: [] +metadata_version: '1.0' +--- +[[building-block-view]] += Building Block View + +include::generated/doc-05000-building-block-view-includes.adoc[] diff --git a/src/docs/arc42/doc-06000-runtime-view.adoc b/src/docs/arc42/doc-06000-runtime-view.adoc new file mode 100644 index 0000000..ed45294 --- /dev/null +++ b/src/docs/arc42/doc-06000-runtime-view.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-06000-runtime-view +type: Document +title: Runtime View +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes usage, publication, and failure/recovery runtime scenarios. +tags: +- arc42 +- runtime +relations: [] +metadata_version: '1.0' +--- +[[runtime-view]] += Runtime View + +include::generated/doc-06000-runtime-view-includes.adoc[] diff --git a/src/docs/arc42/doc-07000-deployment-view.adoc b/src/docs/arc42/doc-07000-deployment-view.adoc new file mode 100644 index 0000000..d129bc6 --- /dev/null +++ b/src/docs/arc42/doc-07000-deployment-view.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-07000-deployment-view +type: Document +title: Deployment View +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Describes how the image is built, tagged, published, and consumed. +tags: +- arc42 +- deployment +relations: [] +metadata_version: '1.0' +--- +[[deployment-view]] += Deployment View + +include::generated/doc-07000-deployment-view-includes.adoc[] diff --git a/src/docs/arc42/doc-08000-crosscutting-concepts.adoc b/src/docs/arc42/doc-08000-crosscutting-concepts.adoc new file mode 100644 index 0000000..f9a3376 --- /dev/null +++ b/src/docs/arc42/doc-08000-crosscutting-concepts.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-08000-crosscutting-concepts +type: Document +title: Crosscutting Concepts +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Captures crosscutting concepts for supply chain, testing, observability, and error handling. +tags: +- arc42 +- concepts +relations: [] +metadata_version: '1.0' +--- +[[crosscutting-concepts]] += Crosscutting Concepts + +include::generated/doc-08000-crosscutting-concepts-includes.adoc[] diff --git a/src/docs/arc42/doc-09000-architecture-decisions.adoc b/src/docs/arc42/doc-09000-architecture-decisions.adoc new file mode 100644 index 0000000..8e672fc --- /dev/null +++ b/src/docs/arc42/doc-09000-architecture-decisions.adoc @@ -0,0 +1,24 @@ +--- +id: DOC-09000-architecture-decisions +type: Document +title: Architecture Decisions +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records proposed ADRs and missing decision candidates. +tags: +- arc42 +- decisions +relations: [] +metadata_version: '1.0' +--- +[[architecture-decisions]] += Architecture Decisions + +include::09-architecture-decisions/generated/doc-09001-adr-index.adoc[] + +include::09-architecture-decisions/generated/open-questions.adoc[] + +include::generated/doc-09000-architecture-decisions-includes.adoc[] diff --git a/src/docs/arc42/doc-10000-quality-requirements.adoc b/src/docs/arc42/doc-10000-quality-requirements.adoc new file mode 100644 index 0000000..be0041b --- /dev/null +++ b/src/docs/arc42/doc-10000-quality-requirements.adoc @@ -0,0 +1,22 @@ +--- +id: DOC-10000-quality-requirements +type: Document +title: Quality Requirements +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records top-level quality goals and proposed measurable quality scenarios. +tags: +- arc42 +- quality +relations: [] +metadata_version: '1.0' +--- +[[quality-requirements]] += Quality Requirements + +include::10-quality-requirements/generated/doc-10001-quality-scenarios.adoc[] + +include::generated/doc-10000-quality-requirements-includes.adoc[] diff --git a/src/docs/arc42/doc-11000-risks-and-technical-debt.adoc b/src/docs/arc42/doc-11000-risks-and-technical-debt.adoc new file mode 100644 index 0000000..cc8d720 --- /dev/null +++ b/src/docs/arc42/doc-11000-risks-and-technical-debt.adoc @@ -0,0 +1,22 @@ +--- +id: DOC-11000-risks-and-technical-debt +type: Document +title: Risks And Technical Debt +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Records proposed risks and technical debt candidates from the migration. +tags: +- arc42 +- risks +relations: [] +metadata_version: '1.0' +--- +[[risks-and-technical-debt]] += Risks And Technical Debt + +include::11-risks-and-technical-debt/generated/doc-11001-risks.adoc[] + +include::generated/doc-11000-risks-and-technical-debt-includes.adoc[] diff --git a/src/docs/arc42/doc-12000-glossary.adoc b/src/docs/arc42/doc-12000-glossary.adoc new file mode 100644 index 0000000..0b16e9f --- /dev/null +++ b/src/docs/arc42/doc-12000-glossary.adoc @@ -0,0 +1,20 @@ +--- +id: DOC-12000-glossary +type: Document +title: Glossary +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Defines terms used by docs-toolbox architecture documentation. +tags: +- arc42 +- glossary +relations: [] +metadata_version: '1.0' +--- +[[glossary]] += Glossary + +include::generated/doc-12000-glossary-includes.adoc[] diff --git a/src/docs/arc42/doc-13000-appendix.adoc b/src/docs/arc42/doc-13000-appendix.adoc new file mode 100644 index 0000000..0b526d4 --- /dev/null +++ b/src/docs/arc42/doc-13000-appendix.adoc @@ -0,0 +1,22 @@ +--- +id: DOC-13000-appendix +type: Document +title: Appendix +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Includes open questions and generated traceability information. +tags: +- arc42 +- appendix +relations: [] +metadata_version: '1.0' +--- +[[appendix]] += Appendix + +include::../doc-005-questions-and-answers.adoc[leveloffset=+1] + +include::../generated/traceability-matrix.adoc[leveloffset=+1] diff --git a/src/docs/canvas/canvas-001-business-model.adoc b/src/docs/canvas/canvas-001-business-model.adoc new file mode 100644 index 0000000..0e07dbb --- /dev/null +++ b/src/docs/canvas/canvas-001-business-model.adoc @@ -0,0 +1,70 @@ +[[business-model-canvas]] += Business Model Canvas +:id: CANVAS-001-business-model +:status: draft +:owner: project maintainers +:revdate: 2026-07-07 + +This draft is derived from repository evidence in `README.md`, `Dockerfile`, and +`.github/workflows/create-docs-toolbox-image.yml`. It needs human review before +being treated as accepted product truth. + +== Customer Segments + +* Documentation maintainers building Docs-as-Code pipelines. +* Contributors who need zero-setup access to documentation tooling. +* CI/CD maintainers who want one versioned toolchain image. + +== Value Propositions + +* Reproducible documentation builds across local and CI environments. +* A single container image with common documentation CLI tools. +* Lower onboarding friction for contributors. +* Published GHCR image tags that support stable or hash-based selection. + +== Channels + +* GitHub repository README. +* GHCR package distribution. +* GitHub Actions publishing workflow. + +== Customer Relationships + +* Self-service usage through Docker commands and CI examples. +* Contribution and review through repository pull requests. + +== Revenue Streams + +* No revenue model is evidenced. Treat the project as open source tooling under + MIT unless maintainers decide otherwise. + +== Key Resources + +* Dockerfile. +* README image description marker. +* GitHub Actions build workflow. +* GHCR package namespace. + +== Key Activities + +* Maintain tool versions and base image choices. +* Build and publish multi-platform container images. +* Keep README usage examples aligned with the image. + +== Key Partners + +* Docs-as-Code Toolkit maintainers. +* GitHub Actions and GHCR as infrastructure providers. +* Upstream package repositories for installed tools. + +== Cost Structure + +* Maintenance effort for the Dockerfile and workflow. +* CI build minutes and package storage. +* Review effort when changing toolchain contents. + +== Assumptions To Validate + +* The installed package versions from the Debian base image are sufficient for users. +* Multi-platform `linux/amd64` and `linux/arm64` images cover the target user base. +* GHCR is the intended long-term distribution channel. diff --git a/src/docs/canvas/canvas-002-value-proposition.adoc b/src/docs/canvas/canvas-002-value-proposition.adoc new file mode 100644 index 0000000..fc5181c --- /dev/null +++ b/src/docs/canvas/canvas-002-value-proposition.adoc @@ -0,0 +1,45 @@ +[[value-proposition-canvas]] += Value Proposition Canvas +:id: CANVAS-002-value-proposition +:status: draft +:owner: project maintainers +:revdate: 2026-07-07 + +== Customer Jobs + +* Build AsciiDoc, Markdown, diagram, presentation, and document outputs in CI. +* Reproduce CI documentation builds on a contributor workstation. +* Avoid manually installing documentation toolchains. + +== Pains + +* Local and CI tool versions drift. +* New contributors lose time on setup. +* Hidden dependencies break documentation builds. + +== Gains + +* A ready-to-use Docs-as-Code environment. +* Versioned image tags for deterministic builds. +* A consistent command surface for local and CI execution. + +== Products And Services + +* `ghcr.io/docs-as-code-toolkit/docs-toolbox` container image. +* README usage and GitHub Actions examples. +* Build workflow that publishes GHCR tags. + +== Pain Relievers + +* Bundles Asciidoctor, Asciidoctor Diagram, Asciidoctor reveal.js, Pandoc, + Graphviz, unzip, and curl. +* Supports Gradle/AsciidoctorJ-based builds through the Java runtime and direct + Asciidoctor CLI and reveal.js presentation builds through the Ruby tooling. +* Uses a containerized environment rather than host setup. +* Publishes Dockerfile-hash tags for traceability. + +== Gain Creators + +* Supports multi-platform image publishing. +* Uses README marker text as package description metadata. +* Publishes `latest` only when the commit has a Git tag, according to the workflow. diff --git a/src/docs/canvas/canvas-003-architecture-inception.adoc b/src/docs/canvas/canvas-003-architecture-inception.adoc new file mode 100644 index 0000000..5f0d8d6 --- /dev/null +++ b/src/docs/canvas/canvas-003-architecture-inception.adoc @@ -0,0 +1,34 @@ +[[architecture-inception-canvas]] += Architecture Inception Canvas +:id: CANVAS-003-architecture-inception +:status: draft +:owner: architecture +:revdate: 2026-07-07 + +== System Purpose + +`docs-toolbox` provides a reproducible containerized documentation toolchain for +Docs-as-Code pipelines. + +== Scope + +include::../fragments/product/initial-scope.adoc[] + +== Out Of Scope + +include::../fragments/product/out-of-scope.adoc[] + +== Quality Drivers + +include::../fragments/architecture/quality-drivers.adoc[] + +== Key Constraints + +* Docker/OCI image delivery. +* Current base image is `openjdk:22-jdk-slim`. +* Current package installation depends on Debian package repositories. +* Publishing workflow depends on GitHub Actions, Docker Buildx, GHCR, and `GHCR_PAT`. + +== Initial Architecture Questions + +See xref:canvas-questions-and-answers[]. diff --git a/src/docs/canvas/canvas-004-architecture-communication.adoc b/src/docs/canvas/canvas-004-architecture-communication.adoc new file mode 100644 index 0000000..15fe07e --- /dev/null +++ b/src/docs/canvas/canvas-004-architecture-communication.adoc @@ -0,0 +1,31 @@ +[[architecture-communication-canvas]] += Architecture Communication Canvas +:id: CANVAS-004-architecture-communication +:status: draft +:owner: architecture +:revdate: 2026-07-07 + +== Audiences + +* Maintainers deciding toolchain changes. +* Contributors using the image locally. +* CI maintainers pinning image tags. + +== Messages + +* The image is the single source of truth for the documentation toolchain. +* Version pinning is preferred for reproducibility. +* Hash tags identify the Dockerfile content used to build an image. + +== Channels + +* `README.md` +* `src/docs/` +* GitHub Actions workflow logs +* GHCR package metadata + +== Review Needs + +* Human review of proposed architecture documentation. +* Maintainer confirmation of tag semantics and supported platforms. +* Maintainer confirmation of which installed tools are part of the supported contract. diff --git a/src/docs/canvas/canvas-005-techstack.adoc b/src/docs/canvas/canvas-005-techstack.adoc new file mode 100644 index 0000000..3b06b0e --- /dev/null +++ b/src/docs/canvas/canvas-005-techstack.adoc @@ -0,0 +1,48 @@ +[[techstack-canvas]] += Techstack Canvas +:id: CANVAS-005-techstack +:status: draft +:owner: architecture +:revdate: 2026-07-07 + +== Runtime And Packaging + +* Docker/OCI image. +* Base image: `openjdk:22-jdk-slim`. +* Working directory: `/app`. +* Empty entrypoint so callers can run arbitrary commands. + +== Documentation Tools + +* Asciidoctor. +* Asciidoctor Diagram. +* Asciidoctor Diagram PlantUML support. +* PlantUML diagram rendering through the Ruby Asciidoctor CLI path. +* Asciidoctor reveal.js converter through the Ruby `asciidoctor-revealjs` CLI. +* Pandoc. +* Graphviz. + +== Java-Based Documentation Builds + +* OpenJDK 22. +* Gradle projects can use AsciidoctorJ plugins inside the toolbox image. + +== Utility Tools + +* unzip. +* curl. + +== Build And Release + +* GitHub Actions. +* Docker Buildx. +* QEMU setup action for multi-platform builds. +* GHCR image registry. + +== Open Validation Topics + +* Whether package versions need explicit pinning. +* Whether Reveal.js frontend assets should be bundled in the image or supplied + by downstream projects through `revealjsdir`. +* Whether additional documentation tools are part of the supported scope. +* Whether `/app` or caller-provided `-w /workspace` is the intended default working directory contract. diff --git a/src/docs/doc-001-arc42.adoc b/src/docs/doc-001-arc42.adoc new file mode 100644 index 0000000..26dfbdb --- /dev/null +++ b/src/docs/doc-001-arc42.adoc @@ -0,0 +1,117 @@ +--- +id: DOC-001-arc42 +type: Document +title: docs-toolbox Architecture +status: draft +owner: architecture +created: '2026-07-07' +reviewed: false +generated: false +summary: Assembled arc42 entry point for the docs-toolbox container image. +tags: +- arc42 +- documentation +relations: +- type: documents + target: DOC-01000-introduction-and-goals + status: proposed + rationale: The assembled architecture includes the introduction and goals chapter. + reviewed: false +- type: documents + target: DOC-02000-architecture-constraints + status: proposed + rationale: The assembled architecture includes the architecture constraints chapter. + reviewed: false +- type: documents + target: DOC-03000-system-scope-and-context + status: proposed + rationale: The assembled architecture includes the system scope and context chapter. + reviewed: false +- type: documents + target: DOC-04000-solution-strategy + status: proposed + rationale: The assembled architecture includes the solution strategy chapter. + reviewed: false +- type: documents + target: DOC-05000-building-block-view + status: proposed + rationale: The assembled architecture includes the building block view chapter. + reviewed: false +- type: documents + target: DOC-06000-runtime-view + status: proposed + rationale: The assembled architecture includes the runtime view chapter. + reviewed: false +- type: documents + target: DOC-07000-deployment-view + status: proposed + rationale: The assembled architecture includes the deployment view chapter. + reviewed: false +- type: documents + target: DOC-08000-crosscutting-concepts + status: proposed + rationale: The assembled architecture includes the crosscutting concepts chapter. + reviewed: false +- type: documents + target: DOC-09000-architecture-decisions + status: proposed + rationale: The assembled architecture includes the architecture decisions chapter. + reviewed: false +- type: documents + target: DOC-10000-quality-requirements + status: proposed + rationale: The assembled architecture includes the quality requirements chapter. + reviewed: false +- type: documents + target: DOC-11000-risks-and-technical-debt + status: proposed + rationale: The assembled architecture includes the risks and technical debt chapter. + reviewed: false +- type: documents + target: DOC-12000-glossary + status: proposed + rationale: The assembled architecture includes the glossary chapter. + reviewed: false +- type: documents + target: DOC-13000-appendix + status: proposed + rationale: The assembled architecture includes the appendix chapter. + reviewed: false +metadata_version: '1.0' +--- +[[docs-toolbox-architecture]] += docs-toolbox Architecture +:toc: left +:toclevels: 3 +:sectnums: + +This assembled architecture is a draft adaptation of the repository's existing +artifacts into the architecture-knowledge-toolkit structure. It treats local +files as evidence and records uncertain claims as open questions or proposed +architecture artifacts. + +include::arc42/doc-01000-introduction-and-goals.adoc[leveloffset=+1] + +include::arc42/doc-02000-architecture-constraints.adoc[leveloffset=+1] + +include::arc42/doc-03000-system-scope-and-context.adoc[leveloffset=+1] + +include::arc42/doc-04000-solution-strategy.adoc[leveloffset=+1] + +include::arc42/doc-05000-building-block-view.adoc[leveloffset=+1] + +include::arc42/doc-06000-runtime-view.adoc[leveloffset=+1] + +include::arc42/doc-07000-deployment-view.adoc[leveloffset=+1] + +include::arc42/doc-08000-crosscutting-concepts.adoc[leveloffset=+1] + +include::arc42/doc-09000-architecture-decisions.adoc[leveloffset=+1] + +include::arc42/doc-10000-quality-requirements.adoc[leveloffset=+1] + +include::arc42/doc-11000-risks-and-technical-debt.adoc[leveloffset=+1] + +include::arc42/doc-12000-glossary.adoc[leveloffset=+1] + +include::arc42/doc-13000-appendix.adoc[leveloffset=+1] diff --git a/src/docs/doc-002-vision-mission.adoc b/src/docs/doc-002-vision-mission.adoc new file mode 100644 index 0000000..a3b3bb4 --- /dev/null +++ b/src/docs/doc-002-vision-mission.adoc @@ -0,0 +1,20 @@ +[[vision-mission]] += Vision And Mission +:id: DOC-002-vision-mission +:status: draft +:owner: project maintainers +:revdate: 2026-07-07 + +== Vision + +include::fragments/product/vision-statement.adoc[] + +== Mission + +include::fragments/product/mission-statement.adoc[] + +== Evidence + +* `README.md` describes the image as a lightweight Docker image for reproducible Docs-as-Code pipelines. +* `Dockerfile` installs documentation tools and declares OCI image metadata. +* `.github/workflows/create-docs-toolbox-image.yml` builds and publishes the image to GHCR. diff --git a/src/docs/doc-004-roadmap.adoc b/src/docs/doc-004-roadmap.adoc new file mode 100644 index 0000000..695ab98 --- /dev/null +++ b/src/docs/doc-004-roadmap.adoc @@ -0,0 +1,24 @@ +[[roadmap]] += Roadmap +:id: DOC-004-roadmap +:status: draft +:owner: project maintainers +:revdate: 2026-07-07 + +== Now + +* Bootstrap toolkit-compatible architecture documentation from repository evidence. +* Validate and generate traceability fragments from source metadata. +* Keep README, Dockerfile, workflow, and architecture docs aligned. + +== Next + +* Confirm supported tool versions and tag policy with maintainers. +* Add a smoke test for expected CLI tools in the built image. +* Decide whether base image and package versions require pinning. + +== Later + +* Define a release policy for stable image tags. +* Consider automated README-to-image-contract checks. +* Consider documenting compatibility guarantees for downstream projects. diff --git a/src/docs/doc-005-questions-and-answers.adoc b/src/docs/doc-005-questions-and-answers.adoc new file mode 100644 index 0000000..462f260 --- /dev/null +++ b/src/docs/doc-005-questions-and-answers.adoc @@ -0,0 +1,64 @@ +[[canvas-questions-and-answers]] += Questions And Answers +:id: DOC-005-questions-and-answers +:status: draft +:owner: project maintainers +:revdate: 2026-07-07 + +This document records open questions and assumptions discovered while adapting +existing repository artifacts into the toolkit documentation structure. + +== Open Questions For Product Owner + +[[q-po-001]] +=== Q-PO-001: Which installed tools are part of the supported contract? + +Answer: Open. + +Evidence currently shows Asciidoctor, Asciidoctor Diagram, Asciidoctor +reveal.js, Pandoc, Graphviz, unzip, curl, and OpenJDK in the Dockerfile. It is +not yet reviewed whether all of them are long-term supported features or +implementation conveniences. + +[[q-po-002]] +=== Q-PO-002: What tag should users pin for reproducible CI builds? + +Answer: Open. + +The workflow publishes `df-` tags and, on Git-tagged commits, the Git tag +and `latest`. Maintainers should confirm recommended user guidance. + +[[q-po-003]] +=== Q-PO-003: Should package versions be pinned inside the Dockerfile? + +Answer: Open. + +The current Dockerfile installs packages from the base distribution repositories +without explicit package version pins. + +== Open Questions For Architecture + +[[q-arch-001]] +=== Q-ARCH-001: Is `openjdk:22-jdk-slim` the intentional long-term base? + +Answer: Open. + +The Dockerfile uses this base image. The architectural rationale and update +policy are not recorded yet. + +[[q-arch-002]] +=== Q-ARCH-002: What smoke tests should verify image readiness? + +Answer: Open. + +No automated tests were found in this repository. A candidate is verifying the +version command for each supported CLI tool. + +[[q-arch-003]] +=== Q-ARCH-003: Should Reveal.js assets be bundled in the toolbox image? + +Answer: Open. + +The image now provides the `asciidoctor-revealjs` converter. Slide rendering +still needs a `revealjsdir` value that points to CDN-hosted, vendored, or local +Reveal.js frontend assets. diff --git a/src/docs/fragments/architecture/quality-drivers.adoc b/src/docs/fragments/architecture/quality-drivers.adoc new file mode 100644 index 0000000..885d622 --- /dev/null +++ b/src/docs/fragments/architecture/quality-drivers.adoc @@ -0,0 +1,2 @@ +The most important quality drivers are reproducibility, contributor onboarding +simplicity, CI consistency, and publication traceability. diff --git a/src/docs/fragments/architecture/system-boundary.adoc b/src/docs/fragments/architecture/system-boundary.adoc new file mode 100644 index 0000000..ef1ec91 --- /dev/null +++ b/src/docs/fragments/architecture/system-boundary.adoc @@ -0,0 +1,4 @@ +The system boundary is the `docs-toolbox` container image and its publication +workflow. It contains the installed documentation toolchain and OCI metadata. +Projects using the image, GitHub Actions runners, GHCR, and contributor +workstations are external actors or systems. diff --git a/src/docs/fragments/product/initial-scope.adoc b/src/docs/fragments/product/initial-scope.adoc new file mode 100644 index 0000000..c98b18d --- /dev/null +++ b/src/docs/fragments/product/initial-scope.adoc @@ -0,0 +1,3 @@ +Initial scope covers a Docker image with OpenJDK, Asciidoctor, +Asciidoctor Diagram, Asciidoctor reveal.js, Pandoc, Graphviz, unzip, curl, +GHCR publishing, README usage guidance, and MIT licensing. diff --git a/src/docs/fragments/product/mission-statement.adoc b/src/docs/fragments/product/mission-statement.adoc new file mode 100644 index 0000000..1ea152e --- /dev/null +++ b/src/docs/fragments/product/mission-statement.adoc @@ -0,0 +1,2 @@ +Package the documentation build tools used by Docs-as-Code pipelines into one +versioned container image so teams can reduce local setup effort and CI drift. diff --git a/src/docs/fragments/product/out-of-scope.adoc b/src/docs/fragments/product/out-of-scope.adoc new file mode 100644 index 0000000..45ad402 --- /dev/null +++ b/src/docs/fragments/product/out-of-scope.adoc @@ -0,0 +1,3 @@ +Out of scope for the current repository evidence are project-specific build +scripts, hosted documentation publishing, user management, SaaS operation, and +application runtime behavior beyond the container image itself. diff --git a/src/docs/fragments/product/vision-statement.adoc b/src/docs/fragments/product/vision-statement.adoc new file mode 100644 index 0000000..d8f43a3 --- /dev/null +++ b/src/docs/fragments/product/vision-statement.adoc @@ -0,0 +1,2 @@ +Provide a lightweight, reproducible toolbox image that lets contributors run the +same Docs-as-Code toolchain locally and in CI. diff --git a/templates/adr.adoc b/templates/adr.adoc new file mode 100644 index 0000000..39ee8ad --- /dev/null +++ b/templates/adr.adoc @@ -0,0 +1,116 @@ +--- +id: ADR-000-short-title +type: ADR +title: Short decision topic +status: proposed +owner: architecture +created: YYYY-MM-DD +reviewed: false +summary: Proposed decision summary. +tags: + - decision +relations: + - type: addresses + target: QS-000-short-title + status: proposed + rationale: Explain why this decision supports the target artifact. + reviewed: false +--- +[[adr-000-short-title]] +== ADR-000: Short Decision Topic + +=== Status + +Proposed. + +This ADR is AI-assisted and must not be treated as accepted architecture truth +until reviewed and accepted by the accountable owner. + +=== Decision + +We propose to choose the selected option. + +=== Context + +Describe the problem, forces, constraints, affected architecture artifacts, and +source-code evidence inspected before writing this decision. + +=== Decision Drivers + +* Driver 1. +* Driver 2. +* Driver 3. + +=== Considered Options + +==== Option 1: Name + +Describe benefits, costs, risks, and fit to the drivers. + +==== Option 2: Name + +Describe benefits, costs, risks, and fit to the drivers. + +==== Option 3: Name + +Describe benefits, costs, risks, and fit to the drivers. + +=== Pugh Matrix + +Use `+1` for better support, `0` for neutral support, and `-1` for weaker +support. Mark cells with `?` when assessment requires human review. Add each +option column manually in the `Sum` row; if any cell in an option column is `?`, +the sum for that option is `?`. + +[.pugh-matrix] +[cols="2,1,1,1", options="header"] +|=== +| Criterion | Option 1 | Option 2 | Option 3 +| Driver 1 | 0 | +1 | ? +| Driver 2 | +1 | -1 | 0 +| Driver 3 | ? | 0 | +1 +s| Sum s| ? s| 0 s| ? +|=== + +=== Consequences + +==== Positive + +* Positive consequence. + +==== Negative + +* Negative consequence. + +==== Neutral or Follow-Up + +* Follow-up decision or implementation concern. + +=== Impact + +[cols="1,1,3", options="header"] +|=== +| Artifact | Impact | Rationale +| xref:quality-scenario.adoc#qs-000-short-title[QS-000-short-title] | addressed | Explain the quality impact. +| xref:risk.adoc#risk-000-short-title[RISK-000-short-title] | introduced | Explain the risk impact. +|=== + +=== Traceability + +[cols="1,1,3", options="header"] +|=== +| Relation | Target | Rationale +| `addresses` | xref:quality-scenario.adoc#qs-000-short-title[QS-000-short-title] | Explain the relation. +|=== + +=== Assumptions + +* Assumption requiring review. + +=== Open Questions + +* Question for the accountable owner. + +=== Review Notes + +State what must be confirmed before this ADR can be marked reviewed or accepted. diff --git a/templates/quality-scenario.adoc b/templates/quality-scenario.adoc new file mode 100644 index 0000000..7efba61 --- /dev/null +++ b/templates/quality-scenario.adoc @@ -0,0 +1,65 @@ +--- +id: QS-000-short-title +type: QualityScenario +title: Short quality scenario title +status: proposed +owner: architecture +created: YYYY-MM-DD +reviewed: false +summary: Proposed quality scenario summary. +tags: + - quality +relations: + - type: depends_on + target: ADR-000-short-title + status: proposed + rationale: Explain why this scenario assumes or depends on the ADR. + reviewed: false +--- +[[qs-000-short-title]] +== QS-000: Short Quality Scenario Title + +=== Status + +Proposed. + +The response measure is a reviewable target. Treat uncertain values as +assumptions until confirmed. + +=== Scenario + +[cols="1,3", options="header"] +|=== +| Element | Description +| Source | Who or what initiates the stimulus. +| Stimulus | Event or condition. +| Environment | Normal load, failure, maintenance, attack, or another explicit context. +| Artifact | Affected system element, interface, component, or process. +| Response | Observable system response. +| Response Measure | Measurable threshold or target. +|=== + +=== Rationale + +Explain why this scenario matters and which stakeholder outcome it supports. + +=== Assumptions + +* Assumption requiring review. + +=== Traceability + +[cols="1,1,3", options="header"] +|=== +| Relation | Target | Rationale +| `depends_on` | xref:adr.adoc#adr-000-short-title[ADR-000-short-title] | Explain the relation. +|=== + +=== Open Questions + +* Question for the accountable owner. + +=== Review Notes + +Mark this scenario as reviewed only after the measure, environment, and +stakeholder value are confirmed. diff --git a/templates/risk.adoc b/templates/risk.adoc new file mode 100644 index 0000000..abd40f5 --- /dev/null +++ b/templates/risk.adoc @@ -0,0 +1,69 @@ +--- +id: RISK-000-short-title +type: Risk +title: Short risk title +status: proposed +owner: architecture +created: YYYY-MM-DD +reviewed: false +summary: Proposed risk summary. +tags: + - risk +relations: + - type: depends_on + target: ADR-000-short-title + status: proposed + rationale: Explain why this risk exists because of the ADR. + reviewed: false +--- +[[risk-000-short-title]] +== RISK-000: Short Risk Title + +=== Status + +Proposed. + +This risk is AI-assisted. It identifies a concern for review and does not decide +risk acceptance. + +=== Risk Statement + +Because , may occur, leading to . + +=== Assessment + +[cols="1,3", options="header"] +|=== +| Field | Value +| Likelihood | Low, medium, or high. +| Impact | Low, medium, or high. +| Priority | Low, medium, or high. +| Timeframe | Near-term, medium-term, or long-term. +| Confidence | Low, medium, or high. +|=== + +=== Mitigation Options + +* Mitigation option. +* Monitoring or review option. + +=== Decision or Acceptance + +The risk remains under analysis until the accountable owner confirms the +assessment and mitigation path. + +=== Traceability + +[cols="1,1,3", options="header"] +|=== +| Relation | Target | Rationale +| `depends_on` | xref:adr.adoc#adr-000-short-title[ADR-000-short-title] | Explain the relation. +|=== + +=== Open Questions + +* Question for risk owner. + +=== Review Notes + +State what must be confirmed before this risk can be marked reviewed or accepted.