From 71d6211c6154bac0248a416d7aae9885b33733b9 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 5 Sep 2026 00:37:11 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=94=A7(checksilent):=20see=20a=20menu?= =?UTF-8?q?=20written=20as=20a=20contract=20MenuSpec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sort-overflow check recognised a SysMenu literal from this repository's model packages and nothing else. An application installed from outside cannot reach that type - it describes the same row as a seed.MenuSpec and hands it to the host's Seeder - so the check went quiet for exactly the author furthest from the schema it protects. Not hypothetical: this repository's own reference application shipped a Sort of 200, past the tinyint sys_menu.sort is built as, and this check passed it. sqlite ignores the width, so it would have surfaced first on a real install, as Error 1264 partway through a migration with everything after it unapplied. The check still cannot see an application in the module cache; that half is the Seeder's runtime validation. This closes the half that is in the tree. Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx --- tools/checksilent/checks.go | 18 ++++++++++++++++-- tools/checksilent/checks_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/tools/checksilent/checks.go b/tools/checksilent/checks.go index f6b6cf72..64b32a39 100644 --- a/tools/checksilent/checks.go +++ b/tools/checksilent/checks.go @@ -525,9 +525,23 @@ func migrationVersion(rel string) (int64, bool) { return v, true } -// isMenuModel reports whether a literal is one of the SysMenu models rather -// than, say, the SysMenu service struct that shares the name. +// isMenuModel reports whether a literal describes a menu row, whichever of +// the two shapes it is written in. +// +// A host module seeds a menu by building the SysMenu model directly. An +// application installed from outside this repository cannot reach that type, +// so it describes the same row as a seed.MenuSpec and hands it to the host's +// Seeder. Both end up in sys_menu and both are subject to its column widths, +// so a check that knew only the first shape would go quiet exactly when the +// author is furthest from the schema it protects. +// +// That is not hypothetical: this repository's own reference application was +// written with a Sort of 200 - past the tinyint sys_menu.sort is built as - +// and this check passed it, because a MenuSpec is not a SysMenu. func (s *snapshot) isMenuModel(lit structLiteral) bool { + if lit.Name == "MenuSpec" && isCoreContractPkg(lit.PkgPath) { + return true + } return lit.Name == "SysMenu" && s.isModelPackage(lit.PkgPath) } diff --git a/tools/checksilent/checks_test.go b/tools/checksilent/checks_test.go index 5707ca81..77e15596 100644 --- a/tools/checksilent/checks_test.go +++ b/tools/checksilent/checks_test.go @@ -578,3 +578,28 @@ func TestShimAliasCoverageCountsTheAliasesItGuards(t *testing.T) { t.Errorf("ScannedShimAliases = %d, want 2", n) } } + +// A menu written as a seed.MenuSpec lands in the same sys_menu.sort column as +// one written as a SysMenu, so the same tinyint bound applies. Until this was +// covered, an application - the one author furthest from the schema - was the +// one the check went quiet for. +func TestMenuSortOverflowIsDetectedInAContractMenuSpec(t *testing.T) { + root := fixture(t, map[string]string{ + "example/app-order/migration/migration.go": `package migration + +import "github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed" + +func menus() []seed.MenuSpec { + return []seed.MenuSpec{ + {Code: "dir", Sort: 200}, + {Code: "ok", Sort: 20}, + } +} +`, + }) + + f := requireOne(t, check(t, root, options{}), checkMenuSort) + if !strings.Contains(f.Message, "200") { + t.Errorf("finding should name the offending value, got: %s", f.Message) + } +}