Skip to content

arch: phase-4 Architecture Drift Report (diff two graph snapshots) - #12

Merged
PhysShell merged 3 commits into
mainfrom
claude/sts-runtime-analysis-2mo4z9
Jun 26, 2026
Merged

arch: phase-4 Architecture Drift Report (diff two graph snapshots)#12
PhysShell merged 3 commits into
mainfrom
claude/sts-runtime-analysis-2mo4z9

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Что это

Killer feature №1. Фаза 2 диффит находки — видит нарушение, только когда оно перешло порог правила. Drift — дополнение: диффит саму структуру между двумя прогонами (baseline = граф main, current = граф PR) и репортит, что сдвинулось, ещё до того как это станет находкой. То самое «PR провёл канализацию через гостиную».

Чисто Python поверх component_metrics() (фаза 3) + механики baseline (фаза 2). Контракт graph.json не меняется — со стороны .NET ничего нового не нужно.

Движок

  • arch/drift.py
    • snapshot(g) — компактный, коммитимый: метрики на компонент + namespace-поверхность зависимостей (включая рёбра во внешние фреймворки — там и живёт «new SQL dependency») + множество циклов.
    • diff(base, cur) → risk-tagged items: новые/убранные циклы (type/ns/asm), новые/убранные зависимости, скачки Ce, сдвиги нестабильности.
    • gate(d, level) — PR-ратчет.
  • arch/drift_cli.py--save-snapshot, затем --baseline (снапшот или сырой graph.json) + --graphdrift.json + PR-friendly drift.md (группировка 🔴/🟠/🔵). --gate-level → exit 2 на дрейфе ≥ уровня; иначе report-only.
  • arch/rules.json — блок drift (пороги + sensitive_targets: новая зависимость в SQL/WPF = High).

Проверено на синтетическом стенде

Взял baseline-граф (150 типов), применил PR-style регрессии (новый SQL-leak из Domain, новый cross-module цикл, скачок coupling) → drift.md:

4 High · 3 Medium · 1 Low — architecture change(s) vs baseline.   gate@high: FAIL (exit 2)

🔴 High: new assembly cycle: Sts.Broker, Sts.Domain
🔴 High: new namespace cycle: Sts.Broker.Services, Sts.Domain.Orders
🔴 High: new type cycle: OrderRepository, OrderSync
🔴 High: new dependency: Sts.Domain.Orders → System.Data.SqlClient
🟠 Medium: Sts.UI.ViewModels: efferent coupling Ce 21 → 27 (+6, +29%)
🟠 Medium: new dependency: Sts.Domain.Orders → Sts.Broker.Services

Идентичные графы → ноль items (precision). Resolved-цикл → info, гейт не блочит.

Тесты

arch/tests/test_arch.py 41/41 (bare + -O): +10 на drift — identical=empty, new cycle=high, new SQL dep=high, plain dep=medium, coupling jump, resolved cycle=info, gate high-only, raw-graph baseline, CLI snapshot→gate с кодами выхода.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added architecture drift reporting with risk-tagged diffs between a baseline and current graph snapshot.
    • Introduced a CLI that generates drift.json and a PR-friendly drift.md, with optional drift gating and distinct exit codes.
    • Added configurable drift thresholds and sensitive target patterns to drive risk scoring.
    • Added support for saving and reusing compact architecture snapshots.
  • Bug Fixes

    • Improved cycle/dependency/coupling drift detection accuracy, including cycle identity handling and snapshot level validation.
  • Documentation

    • Updated phase 4 drift report documentation with a concrete plan, examples, and expected artifacts.
  • Tests

    • Added end-to-end and scenario-based drift coverage, including gating behavior and snapshot conversion.

Killer feature #1: where phase-2 diffs *findings* (a violation only shows once it
crosses a rule threshold), drift diffs the *structure* between two runs (baseline =
main's graph, PR = current) and reports what moved — before it trips a rule.

- arch/drift.py: snapshot(g) -> compact, committable {component metrics + namespace
  dependency surface (incl. external framework edges) + cycle set}. diff(base, cur)
  -> risk-tagged items: new/resolved cycles (type/ns/asm), new/removed dependencies,
  Ce coupling jumps, instability shifts. gate(d, level) for a PR ratchet. A new
  dependency into a sensitive_targets layer (SQL/WPF) is High.
- arch/drift_cli.py: --save-snapshot, then --baseline (snapshot OR raw graph.json) +
  --graph -> drift.json + a PR-friendly drift.md grouped by 🔴/🟠/🔵. --gate-level
  fails (exit 2) on drift at/above a risk; report-only otherwise.
- arch/rules.json: `drift` config block (thresholds + sensitive_targets).
- docs: phase-4 section marked engine-ready with a worked example.
- tests: +10 (identical=empty, new cycle=high, new SQL dep=high, plain dep=medium,
  coupling jump, resolved cycle=info, gate high-only, raw-graph baseline, CLI
  snapshot->gate) -> 41/41, -O safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLHz4cevQyXHKZnjzCqhoa
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 658c7c40-c078-4237-b0c7-3b2e724b4dfa

📥 Commits

Reviewing files that changed from the base of the PR and between 6053d4d and aae1096.

📒 Files selected for processing (3)
  • arch/drift.py
  • arch/drift_cli.py
  • docs/own-net-auditor.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/own-net-auditor.md
  • arch/drift_cli.py
  • arch/drift.py

📝 Walkthrough

Walkthrough

Adds architecture drift snapshots, diffs, gating, and a CLI for report generation. Updates rules, tests, and docs to cover cycle, dependency, coupling, and instability comparisons.

Changes

Architecture Drift Report

Layer / File(s) Summary
Snapshot schema and normalization
arch/drift.py, arch/tests/test_arch.py
Defines the drift snapshot schema and risk ladder, computes component coupling and edge/cycle summaries, and normalizes graph inputs into snapshots.
Diff, thresholds, and gate decisions
arch/drift.py, arch/rules.json, arch/tests/test_arch.py
Compares cycles, dependency edges, coupling, and instability between snapshots, applies drift thresholds and sensitive targets, and adds tests for drift classification and gating.
CLI report flow
arch/drift_cli.py, arch/tests/test_arch.py
Loads graph inputs, supports snapshot saving, writes drift.json and drift.md, prints a summary, and adds end-to-end CLI tests for report-only and gated runs.
Drift docs
docs/own-net-auditor.md
Rewrites the Phase 4 section with the drift workflow, command example, sample output, and CI test references.

Sequence Diagram(s)

sequenceDiagram
  participant Main as arch.drift_cli.main
  participant Loader as JSON loader
  participant Drift as arch.drift.diff
  participant Gate as arch.drift.gate
  participant FS as file system
  participant Stdout as stdout

  Main->>Loader: read baseline, current, rules, snapshot files
  Main->>Drift: compare snapshots
  opt --gate-level set
    Main->>Gate: evaluate blocking risk floor
  end
  Main->>FS: write drift.json and drift.md
  Main->>Stdout: print drift counts
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through graphs with twitchy nose,
and watched new drift in neat rows pose.
Cycles whirred, then gated bright,
while reports printed left and right.
thump

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: phase-4 architecture drift reporting by diffing two graph snapshots.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sts-runtime-analysis-2mo4z9

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17fd6ed9af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread arch/drift.py Outdated
edges.add((a, b))
cycles = []
for comp in g.type_cycles():
cycles.append({"level": "type", "members": sorted(g.name(m) for m in comp)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use full type identities for cycle snapshots

When a solution already has a type cycle between common short names (for example Service/Repository in one namespace), adding another cycle with the same short names in a different namespace produces the same members list here. _cycle_key then collapses the existing and new cycles, so the new high-risk cycle is not reported or gated. Include a fully qualified name or id in the snapshot key instead of only g.name(m).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real bug, @chatgpt-codex-connector — short-name keys would collapse Sts.A.Service↔Repository and Sts.B.Service↔Repository into one, masking the new high-risk cycle from both the report and the gate.

Fixed in 6053d4d: snapshot() now keys type-cycle members by fully-qualified name (namespace.Name) via _fqn — identity, not just display (the report reads cleaner too: Sts.Broker.Services.OrderSync, Sts.Domain.Orders.OrderRepository). Regression test_drift_cycle_key_uses_full_identity adds an identically-named cycle in a second namespace and asserts it's detected as exactly one new cycle.


Generated by Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread arch/drift.py
Comment on lines +53 to +54
if isinstance(data, dict) and data.get("schema") == SCHEMA:
return data

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject snapshots built at a different drift level

When a baseline snapshot was saved with one level and the CLI is later run with rules selecting another level, this returns the saved dict unchanged even though it carries level. The diff then compares incompatible namespace and assembly components/edges and emits bogus new/removed items, which can block a gate for an unchanged graph. Check the saved level against key and fail clearly on mismatches.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, @chatgpt-codex-connector — a namespace snapshot diffed at assembly level compares incompatible component/edge keys and emits bogus new/removed items that could block a clean gate.

Fixed in 6053d4d: as_snapshot() now validates the saved level against the requested one and raises ValueError on mismatch; drift_cli converts that to a clean exit 2 with a message ("re-create the snapshot at level X"). Test test_drift_level_mismatch_rejected covers both the raise and the same-level pass-through.


Generated by Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…hot (Codex)

Two Codex P2 correctness bugs in the drift snapshot:
- _cycle_key used short type names, so two distinct cycles sharing short names
  (Service/Repository in different namespaces) collapsed to one key and a newly
  introduced cycle was masked from the report/gate. Snapshot now keys type-cycle
  members by fully-qualified name (namespace.Name) via _fqn — identity, not just
  display (the report reads cleaner too).
- as_snapshot returned a saved snapshot unchanged even when it was built at a
  different `level` than the diff requested, so a namespace snapshot diffed at
  assembly level emitted bogus new/removed items and could block a clean gate.
  It now validates saved level == requested level and raises; drift_cli converts
  that to a clean exit 2.

Tests: +2 (cross-namespace same-short-name cycles stay distinct; level mismatch
rejected) -> 43/43, -O safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLHz4cevQyXHKZnjzCqhoa

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@arch/drift_cli.py`:
- Around line 111-113: The gate-check result in the drift CLI assigns both
passed and blocking, but only passed is used later, so the unused blocking
binding triggers the Ruff warning. Update the logic around D.gate in
drift_cli.py to discard the second return value and keep only the passed flag,
preserving the existing downstream behavior in _drift_md and the exit code path.

In `@docs/own-net-auditor.md`:
- Around line 170-172: Clarify the baseline snapshot workflow in the
own-net-auditor docs: the current wording around the snapshot/baseline is
ambiguous because it suggests the baseline “lives in the repo” while phase 2
treats baselines as user-generated artifacts kept out of git. Update the text
around the snapshot description to explicitly state where the baseline is
stored, when it is created, and that generated snapshots should not be committed
accidentally, using the snapshot/baseline terminology already present in this
section.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6062a43-51ad-420c-9dfc-bb9acbf60f11

📥 Commits

Reviewing files that changed from the base of the PR and between 057124d and 17fd6ed.

📒 Files selected for processing (5)
  • arch/drift.py
  • arch/drift_cli.py
  • arch/rules.json
  • arch/tests/test_arch.py
  • docs/own-net-auditor.md

Comment thread arch/drift_cli.py Outdated
Comment thread docs/own-net-auditor.md Outdated
… (CodeRabbit)

Two minor nitpicks:
- drift_cli: `passed, blocking = D.gate(...)` left `blocking` unused (report
  sections come from d["items"]) -> Ruff RUF059. Discard it: `passed, _ = ...`.
- docs/drift.py: wording said the baseline snapshot is "committable / lives in
  the repo", contradicting phase 2 where baselines are user artifacts kept out
  of git. Reword: snapshot is a stand artifact, .gitignored, fed to CI like the
  phase-2 baseline; don't commit generated snapshots.

No behavior change; 43/43, -O safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLHz4cevQyXHKZnjzCqhoa
@PhysShell
PhysShell merged commit deb244e into main Jun 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants