markharness

markharness アーキテクチャ設計:Domain / Application / Infrastructureレイヤー分離

ステータス:Accepted(Phase 1〜5実装済み。decisions/0009で決定した方向性の詳細設計) 関連文書:decisions/0009decisions/0008テスト知識管理のGit-nativeモデル_統合版.md 想定読者:markharnessの実装者(Phase 1着手時に参照する)

位置づけ:本書は、markharnessの既存ドキュメントと現在のRust実装を前提として、今後の機能追加・保守性・テスト容易性・大規模リポジトリへの適用を支えるアーキテクチャを整理したものである。Webサーバー・常駐プロセス・正準データベース・マイクロサービスは導入せず、Gitリポジトリを正準な永続化基盤、YAML/JSONを交換形式、CLIおよびCIを利用者向けInterfaceとする現行の性質を維持する。ユーザーから提供された初版提案(2026-08-18)に対し、decisions/0009で決定した2点の修正(ChangeAnalyzerCommitRef一般化、GitRepository trait導入の先送り)を反映している。


1. 目的

提案の中心は、現在のGit-nativeな単一CLIという性質を維持しながら、以下の処理を一貫したパイプラインとして構成することである。

Test Knowledge
  -> TestCaseの決定的生成
  -> マイルストーン間のChangeEvent導出
  -> 影響TestCaseの特定
  -> 実行証拠との照合
  -> pending / stale状態の導出

2. 設計原則

2.1 Git-nativeを維持する

2.2 深いModuleを設計する

各Moduleは、呼び出し側が学ぶInterfaceを小さくし、その内部に複雑な実装を隠す。

2.3 正確性を性能より優先する

3. 推奨アーキテクチャ

flowchart TB
    CLI["CLI / JSON出力"]
    APP["Application Use Cases"]

    subgraph DOMAIN["Domain Modules"]
        KW["KnowledgeWorkspace"]
        TC["TestcaseCompiler"]
        CA["ChangeAnalyzer"]
        VE["VerificationEngine"]
        BF["BackfillCoordinator"]
    end

    subgraph INFRA["Infrastructure"]
        GIT["Git Adapter (git.rs)"]
        KS["KnowledgeSource"]
        FS["WorkspaceStore"]
        SCHEMA["SchemaValidator"]
        CACHE["Derived Index / Cache"]
    end

    subgraph DATA["Git管理データ"]
        KNOW["knowledge / axes"]
        GEN["generated"]
        EXEC["executions"]
        CHANGE["changes"]
        NOTES["git notes"]
    end

    CLI --> APP
    APP --> KW
    APP --> TC
    APP --> CA
    APP --> VE
    APP --> BF

    KW --> FS
    KW --> SCHEMA
    KW --> KS
    TC --> KW
    CA --> GIT
    CA --> KS
    CA --> TC
    VE --> GIT
    VE --> FS
    BF --> CA
    BF --> GIT

    KS --> GIT
    FS --> KNOW
    FS --> GEN
    FS --> EXEC
    FS --> CHANGE
    GIT --> NOTES
    CACHE -.再構築可能.-> KNOW

依存方向は原則として、CLIからApplication、ApplicationからDomain、Domainから必要最小限のInfrastructure上のseamへ向ける。

4. Domain Modules

4.1 KnowledgeWorkspace

knowledge/axes/を読み込み、正規化されたKnowledge Snapshotを提供するModuleである。

impl KnowledgeWorkspace {
    fn load(root: &Path) -> Result<Self>;
    fn validate(&self) -> ValidationReport;
    fn snapshot(&self) -> &KnowledgeSnapshot;
    fn reconcile(&mut self, intent: &IntentDocument) -> Result<ReconcileOutcome>;
}

内部へ隠す処理は以下とする。

現状はsrc/generate.rssrc/validate.rsがそれぞれ独自にknowledge/fs::read_dirで走査しており、走査ロジックが重複している。KnowledgeWorkspaceの導入により、生成、検証、索引作成が同一コマンド内で同じSnapshotを共有できるようにし、この重複を解消する。

4.2 TestcaseCompiler

Knowledge SnapshotからTestCaseおよびトレーサビリティ索引を決定的に生成するModuleである。

fn compile(snapshot: &KnowledgeSnapshot) -> Result<GeneratedArtifacts>;

GeneratedArtifactsには以下を含める。

Compilerはファイルへ書き込まない。Application Use Caseが結果をWorkspaceStoreへ渡す。

不変条件は以下とする。

generateverifyは必ず同じCompilerを使用する。

4.3 ChangeAnalyzer

2つのversion間でFeatureの版を比較し、ChangeEventと影響TestCaseを導出する中核Moduleである。

版参照は、milestoneタグに固定したMilestoneRefではなく、decisions/0009決定3に基づきCommitRefで表現する。

enum CommitRef {
    Milestone(MilestoneId),  // タグ名。内部でgit tagをcommitへ解決する
    Commit(CommitId),        // 任意のcommit(PRのbase/head SHA等)
}

impl ChangeAnalyzer {
    fn compute(
        &self,
        from: CommitRef,
        to: CommitRef,
        options: ChangeOptions,
    ) -> Result<ChangeSet>;
}
struct ChangeOptions {
    cache: CachePolicy,
    impact_source: ImpactSource,
}

enum ImpactSource {
    HistoricalTree,
    CurrentWorkingTree,
}

処理パイプラインは以下とする。

  1. CommitRefをcommitへ解決する(Milestoneはtag解決を経由、Commitはそのまま)。
  2. 各commitのFeature IDとtree SHAを取得する。
  3. Feature IDをキーに旧版と新版を照合する。
  4. added、removed、modifiedを判定する。
  5. to側Knowledgeから影響TestCaseを導出する。
  6. 必要に応じて区間内のmerge commitとtrue_divergencesを調べる。
  7. 安定した順序でChangeEventを返す。

changes computebackfill runCommitRef::Milestoneを使って同じChangeAnalyzerを使用する。decisions/0008 Stage 2で追加するPR Verification Plan機能は、CommitRef::Commitを渡すだけで同じChangeAnalyzerを再利用でき、Interfaceの再設計を要しない。

4.4 VerificationEngine

ChangeEvent、TestCaseとの対応、および実行証拠から再検証状態を導出するModuleである。

impl VerificationEngine {
    fn trace(&self, input: TraceInput) -> TraceReport;
    fn pending(&self, input: VerificationInput) -> PendingReport;
}

状態は文字列ではなく型として表現する。

enum VerificationStatus {
    Current,
    Pending,
    Stale,
    Unknown,
}

VerificationEngineはファイルやGitを直接読まず、読み込み済み入力に対する純粋な判定を行う。Application層がChangeEvent、Execution、Feature versionを収集して渡す。現状のsrc/verify.rstrace/pending関数がfs::read_to_stringを直接呼んでおり、この分離ができていない。

Unknownは、verified_feature_tree_shasを持たない旧形式の実行記録など、判定根拠が不足する場合に使用する。

4.5 BackfillCoordinator

未処理のマイルストーンペアを選択し、ChangeAnalyzerを呼び出して進捗を記録するModuleである。

fn run_once(&self, policy: BackfillPolicy) -> Result<BackfillSummary>;

担当範囲は以下とする。

常駐ワーカーにはせず、CIやスケジューラーから繰り返し実行できる一回実行型を維持する。

5. Application層

CLIサブコマンドに対応するUse Caseを置く。

application/
  init_project.rs
  validate_knowledge.rs
  apply_knowledge.rs
  generate_testcases.rs
  verify_generated.rs
  compute_changes.rs
  record_execution.rs
  verify_pending.rs
  run_backfill.rs

Application層の責務は以下に限定する。

終了コード、標準出力、標準エラー出力を直接扱わない。

enum CommandOutcome {
    Generated(GenerateSummary),
    Validation(ValidationReport),
    Changes(ChangeSummary),
    Verification(PendingReport),
}

6. CLIとPresenter

CLIは以下だけを担当する。

人間向け表示とJSON出力は同じ結果型から生成する。

trait Presenter {
    fn present(&self, outcome: &CommandOutcome) -> PresentedResult;
}

struct PresentedResult {
    stdout: String,
    stderr: String,
    exit_code: i32,
}

これにより、Domain層およびApplication層からprintln!eprintln!std::process::exitを排除する。現状のsrc/cli.rs(2248行)はprocess::exitを32箇所、println!/eprintln!を92箇所含んでおり、この分離ができていない。

7. Infrastructure

7.1 Git Adapter

Gitはmarkharnessのドメインに不可欠であるため、汎用的なRepository<T>には抽象化しない。まず、現在src/changes.rsに分散している直接的なGitプロセス呼び出し(Command::new("git")が5箇所)をgit.rsへ集約する。

trait化は今回のスコープに含めない(decisions/0009決定4)。実装が単一(gitプロセスAdapter)である間は、以下のような関数群としてgit.rsに置く。

// git.rs — 集約後の関数群のイメージ(trait化はしない)
fn resolve_commit_ref(root: &Path, git_ref: &CommitRef) -> Result<CommitId>;
fn feature_trees(root: &Path, commit: &CommitId) -> Result<Vec<FeatureTree>>;
fn milestones(root: &Path) -> Result<Vec<Milestone>>;
fn merges_between(root: &Path, from: &CommitId, to: &CommitId) -> Result<Vec<MergeInfo>>;
fn read_note(root: &Path, key: &NoteKey) -> Result<Option<String>>;
fn write_note(root: &Path, key: &NoteKey, value: &str) -> Result<()>;

trait化(例:GitRepository trait)は、テスト用のfake実装が具体的に必要になった、または複数Adapter(他VCSサポート等)が要件化した、といった明確な必要性が生じた段階で改めて判断する。テストでは、小さな実Gitリポジトリを一時領域に作成する統合テストを引き続き優先する。

7.2 KnowledgeSource

大規模リポジトリ対応として、Knowledgeの供給元を次のseamで切り替えられるようにする。ここは最初から2つの具体的なAdapterが必要なため、7.1とは異なりtrait化する。

trait KnowledgeSource {
    fn list(&self, prefix: &RepoPath) -> Result<Vec<KnowledgeEntry>>;
    fn read(&self, path: &RepoPath) -> Result<Vec<u8>>;
}

Adapterは次の2つを想定する。

これにより、現在のworking treeと過去commitのGit treeを同じParserおよびCompilerへ渡せる。現状のhistorical_testcases_by_feature(src/changes.rs)はマイルストーンごとにgit worktree addgenerate_testcasesgit worktree removeを実行しており、GitTreeKnowledgeSourceの導入によってこの一時worktree作成が不要になる。

7.3 WorkspaceStore

既存のfs_safetyを維持し、以下を共通化する。

generateについては、ディレクトリ全体のトランザクション性を追加する。

1. 一時ディレクトリへ全TestCaseを生成
2. traceability indexを生成
3. 全出力が成功したことを確認
4. generated/testcasesを切り替える
5. traceability indexを切り替える

途中失敗時には既存の生成物を保持する。

7.4 キャッシュと索引

.markharness-cache/は正準データではなく、削除・再構築可能な派生物とする。この方針は現状のsrc/id_cache.rsで既に実装済みであり、そのキャッシュキーは以下の式と一致する。

hash(
  knowledge_tree_sha
  + canonicalization_rule_version
  + id_index_schema_version
  + tool_version
)

将来的に、同じ方針で以下の索引を追加できる。

.markharness-cache/
  feature-versions/           # 既存(id_cache.rs)
  testcase-by-feature/        # 新規
  changeevent-by-feature/     # 新規
  execution-by-case/          # 新規

SQLiteを使用する場合も正準DBにはせず、再構築可能なローカル索引に限定する。

8. 推奨コード構成

src/
  main.rs

  cli/
    mod.rs
    args.rs
    presenter.rs

  application/
    mod.rs
    commands/

  domain/
    knowledge/
      mod.rs
      model.rs
      validation.rs
    generation/
      mod.rs
      compiler.rs
      artifact.rs
    change/
      mod.rs
      analyzer.rs
      model.rs          # CommitRef、ChangeOptions等
    verification/
      mod.rs
      engine.rs
      model.rs
    backfill/
      mod.rs
      coordinator.rs

  infrastructure/
    git/
      mod.rs             # 集約後のgit呼び出し(trait化しない)
    knowledge_source/
      mod.rs
      working_tree.rs
      git_tree.rs
    workspace/
      mod.rs
      yaml.rs
      atomic_write.rs
    schema/
      mod.rs
    cache/
      mod.rs

  safety/
    paths.rs

ファイル分割自体を目的にしない。小さな型や関数だけのファイルを過剰に作らず、ModuleのInterfaceと責務が明確になる単位で分割する。Phase4の段階で、必要に応じてこの構成へ再編する。

9. 現在の実装との相違

9.1 すでに実現されている点

現在の実装は以下をすでに満たしている。

したがって、本設計は全面的な再実装ではなく、現在の長所を維持した構造整理である。

9.2 主な変更点

観点 現在 提案
全体 単一crate 単一crateを維持
Module配置 機能別のフラットな.rs Domain / Application / Infrastructure
CLI 解析、実行、表示、終了を一括担当 解析とPresenter選択に限定
Knowledge 各機能が必要に応じて走査 正規化済みSnapshotを共有
TestCase生成 パスを受けて読込と生成を同時実行 Snapshotを受けるCompiler
Change計算 パスと複数boolを受ける関数、milestoneタグ専用 CommitRefと設定型を受けるAnalyzer(milestone/PR共通)
Verification I/Oと状態判定を同時実行 Data Loaderと純粋なEngineを分離
Git git.rsと一部直接呼び出し Git呼び出しをgit.rsへ集約(trait化はしない)
生成物更新 ファイル単位で安全 ディレクトリ全体でも原子的

10. スケーラビリティ

10.1 本設計だけで改善する領域

スケールの種類 改善度 理由
機能追加 Use CaseとDomainの責務が分離される
コード量 変更の局所性が高くなる
開発人数 巨大なcli.rsへの変更集中を避けられる
テスト数 I/OなしでDomain判定をテストできる
出力形式追加 中〜大 Presenterを追加できる
Importer追加 中〜大 KnowledgeWorkspaceのInterfaceへ接続できる
Knowledge件数 小〜中 Snapshotを共有した場合に重複読込を削減できる
Git履歴・milestone数 中核アルゴリズムは同じ
水平スケール なし ローカルCLIを維持するため

本設計の主な効果は、実行速度よりも、コード量、機能数、開発人数が増えた場合の保守性である。

10.2 データ量への対応

大規模データに対する性能改善には、アーキテクチャ整理に加えて以下を実装する。

Knowledge Snapshotの共有

let snapshot = workspace.load_snapshot()?;
validate(&snapshot);
compile(&snapshot);
build_traceability(&snapshot);

同一プロセス内で検証、生成、索引作成がYAMLを繰り返し読み込まないようにする。

Feature単位の増分生成

Knowledge tree SHA
  -> 変更Feature ID
  -> 該当FeatureのTestCaseだけ再生成
  -> 全体Manifestを更新

正確性を担保するため、全生成を正準動作として維持する。

generate                 全生成
generate --incremental   増分生成
CI                       定期的に全生成で検証

過去Git treeの直接読込

GitTreeKnowledgeSourceにより、一時worktreeを作らずに対象commitのblob/treeから過去Knowledgeを読み込む。historical_testcases_by_featureの置き換え対象。

Verification用索引

以下の検索を再構築可能な索引で高速化する。

Feature ID -> ChangeEvent
Feature ID -> TestCase
case_id    -> Execution milestones
case_id    -> verified tree SHA

Backfillの処理量制御

--max-pairs 10
--time-budget 5m
--from-milestone <name>

CIの実行時間を予測可能にする。並列化は、同一出力ファイルとGit notesへの競合制御を設計した後に行う。

10.3 水平スケール

現時点では、サーバー、共有DB、ジョブキューによる水平スケールは採用しない。

markharnessの主要な実行機会はローカル編集、PR時CI、tag push時のChange計算、定期backfillである。まず単一プロセス内で以下を実施する方がGit-nativeな性質と整合する。

11. テスト戦略

11.1 Domainテスト

11.2 Git統合テスト

11.3 Workspace統合テスト

11.4 CLI契約テスト

12. 段階的な移行計画

decisions/0009決定8の要約。詳細な作業単位は各Phase着手時にchecklist-<task>.mdで管理する。

Phase 1: 小さなInterface改善

  1. [実装済み] compute_changesのbool引数をChangeOptionsへ置換する。
  2. [実装済み] changes.rs内の直接Git呼び出しをgit.rsへ集約する(trait化はしない、7.1節)。
  3. [実装済み] 既存の動作とCLI契約をCharacterization Testとtests/fixtures/stage0/changes-m1-m2.golden.ymlで固定する。

この段階ではディレクトリ構成を変更しない。

Phase 2: CLIの責務分離

  1. [実装済み] CommandOutcomeを導入する。
  2. [実装済み] 対象3コマンドの終了コード決定と出力をPresenterへ移す。
  3. [実装済み] 人間向けPresenterとJSON Presenterを分ける。
  4. [実装済み] generatechanges computeverify pendingからApplication Use Caseを抽出する。

Phase 3: 生成物更新の原子性

  1. [実装済み] TestCaseとtraceability indexを一時領域へ全生成する。
  2. [実装済み] 成功後にbackup付きdirectory switchでgenerated/へ反映する。
  3. [実装済み] 途中失敗時に既存生成物が保持されるテストを追加する。

Phase 4: Knowledge Snapshotと純粋Domain

  1. [実装済み] KnowledgeSnapshotを導入する。
  2. [実装済み] TestcaseCompilerをファイルシステムから分離する。
  3. [実装済み] Verificationの読み込み処理から、Current/Pending/Stale/Unknownを返す純粋な状態判定を分離する。
  4. [実装済み] ChangeAnalyzerCommitRefベースで確定させる(4.3節)。
  5. [実装済み] 現段階では責務境界が既存Module内で明確なため、物理ディレクトリの再編は行わない。

Phase 5: 大規模リポジトリ最適化

  1. [実装済み] KnowledgeSourceとworking tree/Git treeのAdapterを導入する。
  2. [実装済み] GitTreeKnowledgeSourceによるblob直読で一時worktreeを置き換える。
  3. [実装済み] Feature、ChangeEvent、ExecutionのJSON索引を.markharness-cache/index/へ再構築可能な派生物として追加する。
  4. [実装済み] Backfillに--max-pairs--time-budgetを追加する。
  5. [判断済み] 現時点では増分生成・並列処理を導入しない。ボトルネックを示す計測結果がなく、全生成を正準動作として維持する。Git tree直読と処理量制限を導入した状態で今後計測し、必要性が確認された場合のみ追加する。

13. 採用しない設計

マイクロサービス

Gitリポジトリ内のローカルな知識管理という性質に対し、ネットワーク、認証、分散トランザクション、運用基盤の複雑さが過大になるため採用しない。

正準データとしてのRDB

Gitの履歴、レビュー、branch/tag運用と二重の正準データが生じるため採用しない。SQLite等は再構築可能な索引用途に限定する。

全依存のtrait化

一つしか実装がない依存まで抽象化するとInterfaceが増え、保守性を下げる。Git操作(7.1)は集約のみ先行させ、working treeとGit treeのように実際に複数Adapterが必要なseam(KnowledgeSource、7.2)だけを抽象化する。

初期段階からの増分生成のみの運用

キャッシュ破損、削除検出、正規化ルール変更による不整合を発見しにくいため採用しない。全生成を正準として残す。

ChangeAnalyzerMilestoneRef固定で設計する

decisions/0008 Stage 2が既に決定している「PR base/headを第一級のversion rangeとして扱う」という方向と衝突し、着手時に中核Interfaceの再設計という手戻りが生じるため採用しない。CommitRefへ一般化する(4.3節)。

14. 結論

markharnessには、現在のRust単一CLIとGit-nativeなデータモデルを維持したモジュラーモノリスが適している。

中核となるModuleは以下の5つである。

  1. KnowledgeWorkspace
  2. TestcaseCompiler
  3. ChangeAnalyzer(CommitRefベース、milestoneとPR base/headの両方を扱う)
  4. VerificationEngine
  5. BackfillCoordinator

現在の実装は、決定的生成、Change計算の再利用、実Gitテスト、ファイル操作の安全性、内容アドレス化されたキャッシュキーなど、本設計の重要部分をすでに実現している。最優先の改善は全面的な再構築ではなく、巨大化したCLIの責務分離、型付きInterface、Git操作の集約、生成物更新の原子性である。

アーキテクチャ整理によって主に改善されるのは、機能数、コード量、開発人数に対するスケールである。データ量への性能改善は、そのInterfaceを土台としてKnowledgeSource、再構築可能な索引、Feature単位の処理、Backfillの処理量制限を段階的に導入することで実現する。ChangeAnalyzerCommitRefベースで設計することにより、decisions/0008 Stage 2のPR Verification Plan機能への拡張も、この土台の上で後方非互換な再設計なしに行える。