Skip to content

feat(qualitygate): enforce six-layer package dependency boundaries#2045

Open
sang-neo03 wants to merge 12 commits into
mainfrom
refactor/package-topology
Open

feat(qualitygate): enforce six-layer package dependency boundaries#2045
sang-neo03 wants to merge 12 commits into
mainfrom
refactor/package-topology

Conversation

@sang-neo03

@sang-neo03 sang-neo03 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds machine enforcement for six package dependency layers. This PR installs enforcement only; no business packages are moved, and follow-up changes can remove the registered legacy edges.

Changes

  • Add a data-driven layering test in internal/qualitygate/deptest/layering_test.go. It runs go list -json ./... twice for each of the seven published Linux, Darwin, and Windows GOOS/GOARCH combinations - once untagged and once with -tags authsidecar - and unions the resulting non-test production dependency graphs (14 invocations). The union matters: extension/credential/sidecar and its 8 denied edges only exist under the tag. Standard error remains separate from JSON output so cold-cache module download messages cannot corrupt decoding. The test reads only .Imports and .Deps, so test-only imports stay outside this phase.

  • Enforce seven rules:

    Rule Mode Restriction
    extension-zero-internal Transitive Packages under extension/ must not depend on internal/**; the two wrapper-main demos (examples/audit-observer, examples/readonly-policy) are exempt by exact import path, because their cmd import is the pattern they exist to demonstrate and every internal package they reach is inherited through cmd
    examples-surface-only Direct Packages under extension/platform/examples/ may directly import only cmd and extension/platform from this module; every other repository package is rejected, while standard library and third-party imports stay outside the rule
    events-no-shortcuts Transitive Packages under events/ must not depend on shortcuts/**
    shortcuts-runtime-gate Direct Packages under shortcuts/ must use the shortcuts/common runtime gate instead of importing sensitive runtime packages; shortcuts/common and shortcuts/apps/gitcred are exempt
    cmd-assembly-only Direct Only the cmd assembly root and cmd/auth may import shortcuts/**
    errs-leaf Direct errs must remain a repository leaf
    internal-no-upper Direct Packages under internal/ must not import cmd, shortcuts, or events; the manifest-export command collector is exempt
  • Exempt packages by exact import path only. An earlier revision skipped any package whose path contained /examples/, which meant a directory named examples anywhere under extension/ escaped the rule outright, and the sanctioned demos were exempt from every denial rather than only from the internal packages they inherit through cmd. That substring mechanism is gone, and examples-surface-only now covers the demo tree as an allowlist rather than a few denied prefixes: because the transitive rule no longer inspects these packages, pinning their direct surface to two packages is what keeps the inherited chain bounded. Seeding those inherited edges instead would wedge the ratchet: they track cmd's transitive set, so a new internal package under cmd would demand a new row that check-layering-ratchet.sh refuses by design.

  • Register 39 existing (from, denied) edges in layering-edges.txt. The Go test rejects new unregistered edges and stale rows. CI compares the current key set with the base revision, so keys may only be removed; equal-count replacement also fails. The first-run bootstrap is locked to the exact approved 39-edge snapshot.

  • Add contract tests for unchanged metadata, deletion, addition, equal-count replacement, malformed input, missing files, duplicate keys, bootstrap replacement, invalid base revisions, conditional Shell execution, and separated go list output streams.

  • Remove the circular-dependency report step from arch-audit.yml; Go already rejects import cycles during compilation.

Test Plan

  • make script-test
  • make unit-test
  • go vet ./...
  • gofmt -l .
  • go mod tidy leaves go.mod and go.sum unchanged
  • golangci-lint reports zero issues
  • Fresh GOMODCACHE and GOCACHE run passes all seven package graph targets
  • A temporary forbidden events import fails with from, denied, and rule diagnostics
  • Removing an active seed row fails as a new violation
  • Adding a stale seed row fails with deletion guidance
  • Adding a violation together with a matching seed row passes the Go test but fails the CI ratchet
  • A forbidden internal import inside examples/audit-observer fails examples-surface-only
  • events, errs and a cmd subpackage imported from examples/audit-observer all fail examples-surface-only
  • A new package under any examples/ directory in extension/ fails extension-zero-internal
  • make examples-build still builds both wrapper-main demos
  • Manual local verification - N/A because runtime command behavior is unchanged

Related Issues

  • None

Summary by CodeRabbit

  • Quality Improvements

    • Strengthened dependency layering enforcement using an approved ratchet of denied dependency edges, including strict TSV validation, duplicate detection, and detection of newly added vs stale entries.
    • Added automated coverage to parse, evaluate, and reconcile layering violations against the approved edges list.
  • CI / Automation

    • Lint now enforces the layering ratchet relative to a baseline, failing the build if new violations are introduced.
    • Architecture Audit workflow no longer generates circular-dependency reporting artifacts.
    • Expanded the script-test target to include layering ratchet checks.

@sang-neo03
sang-neo03 requested a review from liangshuo-1 as a code owner July 24, 2026 06:40
@github-actions github-actions Bot added the size/S Low-risk docs, CI, test, or chore only changes label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Go package-layering validation with seeded migration exceptions, deterministic graph evaluation, a Git-based ratchet preventing new denied edges, CI integration, shell tests, and removal of the circular-dependency audit step.

Changes

Layering quality gate

Layer / File(s) Summary
Layering rules and seeded edges
internal/qualitygate/deptest/layering_test.go, internal/qualitygate/deptest/layering-edges.txt
Defines direct and transitive dependency rules and records denied migration-related edges with metadata.
Package graph evaluation and edge parsing
internal/qualitygate/deptest/layering_test.go
Builds multi-target package graphs, evaluates violations and exceptions, parses seeded edges, and detects unseeded or stale entries.
Layering ratchet and validation
scripts/check-layering-ratchet.sh, scripts/check-layering-ratchet.test.sh
Validates ratchet structure, bootstrap snapshots, Git-base comparisons, and addition/removal scenarios.
CI and local test integration
.github/workflows/ci.yml, .github/workflows/arch-audit.yml, scripts/ci-workflow.test.sh, Makefile
Runs the ratchet during lint and script tests, validates workflow wiring, and removes the circular-dependency audit step.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI lint
  participant check-layering-ratchet.sh
  participant layering-edges.txt
  participant Git base
  CI lint->>check-layering-ratchet.sh: pass QUALITY_GATE_CHANGED_FROM
  check-layering-ratchet.sh->>layering-edges.txt: parse and hash current keys
  check-layering-ratchet.sh->>Git base: read base ratchet keys
  Git base-->>check-layering-ratchet.sh: return committed keys
  check-layering-ratchet.sh-->>CI lint: pass or fail on new keys
Loading

Possibly related PRs

  • larksuite/cli#510: Modifies the same architecture-audit workflow and removes the circular-dependency check this change deletes.

Suggested labels: size/L

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title is concise and clearly matches the main change: enforcing package dependency boundaries.
Description check ✅ Passed The description follows the required template and includes Summary, Changes, Test Plan, and Related Issues sections.
✨ 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 refactor/package-topology

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@217f4e55670f78d57c781aab9d456a5e27f32dd9

🧩 Skill update

npx skills add larksuite/cli#refactor/package-topology -y -g

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.14%. Comparing base (5a54bc0) to head (217f4e5).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2045      +/-   ##
==========================================
+ Coverage   75.13%   75.14%   +0.01%     
==========================================
  Files         905      911       +6     
  Lines       96130    96322     +192     
==========================================
+ Hits        72224    72385     +161     
- Misses      18348    18370      +22     
- Partials     5558     5567       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Add a data-driven architecture layering test that builds the full import
graph (go list -json -tags authsidecar) and evaluates six rules:

- extension must not depend on internal (transitive; keeps it extractable
  as a standalone SDK module)
- events must not depend on shortcuts (transitive)
- shortcuts must not directly import auth/keychain/credential/client/vfs
  (direct; must go through the shortcuts/common RuntimeContext gate)
- cmd subpackages must not import shortcuts (assembly point + cmd/auth only)
- errs must stay a leaf
- internal must not depend on cmd/shortcuts/events

Pre-existing violations are seeded into layering-edges.txt (37 rows). The
test rejects any unseeded violation (new debt) and any stale row (removed
debt), and CI locks the effective row count to only ever decrease. Removes
the tautological circular-dependency check from arch-audit.yml, since Go
already forbids import cycles at compile time.
@sang-neo03
sang-neo03 force-pushed the refactor/package-topology branch from 37f4fb7 to 77cf968 Compare July 24, 2026 07:18

@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: 1

🤖 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 `@internal/qualitygate/deptest/layering_test.go`:
- Around line 443-445: Update the parts parsing logic around the loop that trims
each field to reject whitespace-padded exception fields instead of mutating
them: compare each field with its trimmed form, return or report the existing
validation error when they differ, and only retain unchanged fields. Add a test
covering an exception row with padded input and assert it is rejected.
🪄 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: 6b9d69e8-2cbf-4b9c-b4b3-857f6a69aca3

📥 Commits

Reviewing files that changed from the base of the PR and between 37f4fb7 and 77cf968.

📒 Files selected for processing (4)
  • .github/workflows/arch-audit.yml
  • .github/workflows/ci.yml
  • internal/qualitygate/deptest/layering-edges.txt
  • internal/qualitygate/deptest/layering_test.go
💤 Files with no reviewable changes (1)
  • .github/workflows/arch-audit.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ci.yml
  • internal/qualitygate/deptest/layering-edges.txt

Comment thread internal/qualitygate/deptest/layering_test.go Outdated
@github-actions github-actions Bot added size/L Large or sensitive change across domains or core paths and removed size/S Low-risk docs, CI, test, or chore only changes labels Jul 24, 2026

@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: 1

🤖 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 `@scripts/check-layering-ratchet.sh`:
- Around line 16-17: Update the bootstrap defaults in the layering ratchet
script to reflect all 39 seeded edges: change the expected count and replace the
canonical hash with the hash calculated from the actual 39-edge snapshot. Also
update the associated bootstrap test expectations so the snapshot validation
accepts the seeded baseline.
🪄 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: c674911d-4bb3-4501-86a3-2fb7a4d4af01

📥 Commits

Reviewing files that changed from the base of the PR and between 77cf968 and 2a23731.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • Makefile
  • internal/qualitygate/deptest/layering-edges.txt
  • internal/qualitygate/deptest/layering_test.go
  • scripts/check-layering-ratchet.sh
  • scripts/check-layering-ratchet.test.sh
  • scripts/ci-workflow.test.sh
💤 Files with no reviewable changes (1)
  • internal/qualitygate/deptest/layering-edges.txt

Comment thread scripts/check-layering-ratchet.sh Outdated
Check all seven published GOOS and GOARCH combinations, and keep go list diagnostics separate from its JSON output for cold caches.\n\nMake the bootstrap snapshot immutable in CI, propagate shell failures explicitly, isolate sourced execution, and add deterministic regression tests for each contract.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/check-layering-ratchet.sh (1)

23-31: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Whitespace-padded fields are silently accepted instead of rejected.

Each field is trimmed via trim() before validation, so a row with leading/trailing whitespace around otherwise-valid content passes silently rather than being flagged as malformed — the same issue previously raised for the Go-side parseLayeringEdges parser. This can let subtly malformed rows through unnoticed.

🛡️ Proposed fix: reject rather than silently trim
       from = trim($1)
       denied = trim($2)
       owner = trim($3)
       reason = trim($4)
       added_at = trim($5)
-      if (from == "" || denied == "" || owner == "" || reason == "" || added_at !~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
+      if (from != $1 || denied != $2 || owner != $3 || reason != $4 || added_at != $5 || \
+          from == "" || denied == "" || owner == "" || reason == "" || added_at !~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
         printf "Malformed layering ratchet row at %s:%d: fields must be non-empty and added_at must use YYYY-MM-DD.\n", FILENAME, FNR > "/dev/stderr"
         exit 2
       }
🤖 Prompt for 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.

In `@scripts/check-layering-ratchet.sh` around lines 23 - 31, Update the row
validation in the AWK parsing block of check-layering-ratchet.sh to detect and
reject leading or trailing whitespace in every raw field before or alongside
trimming. Preserve the existing non-empty checks and YYYY-MM-DD validation, and
report whitespace-padded rows as malformed rather than accepting the trimmed
values.
🧹 Nitpick comments (1)
scripts/check-layering-ratchet.test.sh (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a shellcheck source directive to silence SC1090.

$script is computed dynamically, so shellcheck can't resolve it statically.

+# shellcheck source=./check-layering-ratchet.sh
 source "$script"
🤖 Prompt for 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.

In `@scripts/check-layering-ratchet.test.sh` at line 11, Add a ShellCheck source
directive immediately before the dynamic `source "$script"` statement to
suppress SC1090 for this intentionally computed path, without changing the
script-loading behavior.

Source: Linters/SAST tools

🤖 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 `@internal/qualitygate/deptest/layering_test.go`:
- Around line 119-129: Update layeringBuildTargets and its surrounding
configuration to derive from or mirror the authoritative release target matrix
defined by goreleaser and the release workflow, including all published
GOOS/GOARCH combinations. Keep the layering test aligned with those release
targets so future matrix changes cannot leave this list stale.

---

Outside diff comments:
In `@scripts/check-layering-ratchet.sh`:
- Around line 23-31: Update the row validation in the AWK parsing block of
check-layering-ratchet.sh to detect and reject leading or trailing whitespace in
every raw field before or alongside trimming. Preserve the existing non-empty
checks and YYYY-MM-DD validation, and report whitespace-padded rows as malformed
rather than accepting the trimmed values.

---

Nitpick comments:
In `@scripts/check-layering-ratchet.test.sh`:
- Line 11: Add a ShellCheck source directive immediately before the dynamic
`source "$script"` statement to suppress SC1090 for this intentionally computed
path, without changing the script-loading behavior.
🪄 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: 9d3bc06e-4a9d-4b3a-a937-6bb5070c615f

📥 Commits

Reviewing files that changed from the base of the PR and between 2a23731 and d3dd5ce.

📒 Files selected for processing (4)
  • internal/qualitygate/deptest/layering_test.go
  • scripts/check-layering-ratchet.sh
  • scripts/check-layering-ratchet.test.sh
  • scripts/ci-workflow.test.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/ci-workflow.test.sh

Comment thread internal/qualitygate/deptest/layering_test.go
@sang-neo03
sang-neo03 force-pushed the refactor/package-topology branch from 37f0a6c to e29119a Compare July 24, 2026 09:06
Layering edge parsing and graph coverage:
- Reject whitespace-padded exception fields instead of silently trimming
  them, so a padded row is a malformed row rather than a coerced identity;
  add a padded-field parse test.
- Fail loud when any release target/tag combination lists zero packages,
  which would otherwise let the layering graph silently under-cover.
- Document the build-tag scope (demo tags excluded), the SkipFrom substring
  semantics, and the toolchain-derived support set behind the drift check.

GoReleaser drift checks:
- Reject custom build commands and per-target overrides as unsupported.
- Detect --tags in addition to -tags when rejecting release build tags.
- Reject any GO* build environment variable (except CGO_ENABLED=0) through a
  single default branch instead of an explicit allowlist.
- Validate the GoReleaser global env block, and make the go-list stderr test
  table-driven across the default and authsidecar graphs.
@sang-neo03
sang-neo03 force-pushed the refactor/package-topology branch from e29119a to abe0d09 Compare July 24, 2026 09:21
The extension rule skipped any package whose import path contained
"/examples/", which let the gate miss two things: a directory named
examples anywhere under extension escaped the rule outright, and the
sanctioned demos were exempt from every denial rather than only from the
internal packages they inherit through cmd.

- Drop SkipFrom (and containsAny) so no rule can exempt by directory name.
- Exempt the two wrapper-main demos from extension-zero-internal by exact
  import path. Their cmd import is the pattern they exist to demonstrate,
  and seeding those edges instead would wedge the ratchet: the edges track
  cmd's transitive set, so a new internal package under cmd would demand a
  new row that check-layering-ratchet.sh refuses by design.
- Add examples-surface-only: demos may consume cmd and extension/platform
  but must not directly import internal or shortcuts. Zero violations today.

layering-edges.txt stays at 39 rows, so the ratchet bootstrap snapshot
still matches.
examples-surface-only promised that demos may consume only the assembled CLI
and the public plugin SDK, but it enforced two denied prefixes instead, so
every tree nobody thought to deny was permitted. A demo importing `events`,
`errs` or a `cmd` subpackage passed the gate, and because
extension-zero-internal exempts these packages from the transitive check,
nothing examined what those imports dragged in either. The exemption was
therefore unbounded in what it covered, the same defect as the directory-name
skip it replaced.

- Add Rule.AllowedRepoDeps, which inverts the check: any dependency inside
  this module that is not listed is a violation. Standard library and
  third-party packages, including same-organisation modules that are not this
  one, stay outside the rule.
- Pin examples-surface-only to exactly `cmd` and `extension/platform`, so the
  rule name matches what it enforces and the inherited chain stays bounded by
  a direct surface of two packages.
- Cover the reproducers as contract cases: other repository trees, `cmd`
  subpackages, other `extension` subtrees, and the module root are rejected,
  while the two allowed packages plus non-module imports are not.

layering-edges.txt stays at 39 rows; the demos already import only the two
allowed packages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant