From d0adca85836777ce40bef8a345df30d2d4b1eab5 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Tue, 7 Jul 2026 23:09:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(2c):=20end=20the=20core/captcha=20carve-ou?= =?UTF-8?q?t=20=E2=80=94=20zero=20block/core=20in=20standalone=20plugins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captcha is now the host-stamped X-Bn-Verified-Captcha trusted header (pluginsdk v0.2.2), so no plugin has a legitimate core import left: isForbiddenPluginImport flags ALL first-party prefixes unconditionally, and a block/core require in a plugin go.mod fails as no-block-core-require (always vestigial — imports are already forbidden). Fleet-verified: no plugin imports or requires core. Co-Authored-By: Claude Fable 5 --- plugin_imports.go | 29 +++++++---------------------- plugin_imports_test.go | 32 +++++++++++++++++++++++++++----- plugin_sdk_versions.go | 18 +++++++++++++++--- plugin_sdk_versions_test.go | 29 ++++++++++++++++++++++++----- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/plugin_imports.go b/plugin_imports.go index b686f28..9e44f04 100644 --- a/plugin_imports.go +++ b/plugin_imports.go @@ -18,34 +18,19 @@ const ( firstPartyImportPrefix = "git.dev.alexdunmow.com/block/" ) -// allowedCorePluginImports are the block/core packages that deliberately -// stayed on core after the pluginsdk extraction (2026-07-07) and remain -// plugin-usable; everything else first-party is host-only. -var allowedCorePluginImports = []string{ - blockCoreImportPrefix + "/captcha", - blockCoreImportPrefix + "/backup", -} - func hasImportPrefix(importPath, prefix string) bool { return importPath == prefix || strings.HasPrefix(importPath, prefix+"/") } // isForbiddenPluginImport reports whether a standalone plugin may not import -// the package: all BlockNinja first-party Go code (cms, orchestrator, core) -// is off-limits except the plugin SDK and the explicitly kept core packages. +// the package: ALL BlockNinja first-party Go code (cms, orchestrator, core) is +// off-limits — block/pluginsdk is the one plugin-facing module. (The former +// core/captcha carve-out ended when captcha moved to the host-stamped +// X-Bn-Verified-Captcha trusted header, pluginsdk v0.2.2.) func isForbiddenPluginImport(importPath string) bool { - if hasImportPrefix(importPath, blockNinjaImportPrefix) || hasImportPrefix(importPath, orchestratorImportPrefix) { - return true - } - if hasImportPrefix(importPath, blockCoreImportPrefix) { - for _, allowed := range allowedCorePluginImports { - if hasImportPrefix(importPath, allowed) { - return false - } - } - return true - } - return false + return hasImportPrefix(importPath, blockNinjaImportPrefix) || + hasImportPrefix(importPath, orchestratorImportPrefix) || + hasImportPrefix(importPath, blockCoreImportPrefix) } type pluginImportViolation struct { diff --git a/plugin_imports_test.go b/plugin_imports_test.go index 7dab6ec..b7ac19d 100644 --- a/plugin_imports_test.go +++ b/plugin_imports_test.go @@ -51,22 +51,20 @@ func Example() { } } -func TestCheckStandalonePluginImportsAllowsSDKAndKeptCoreImports(t *testing.T) { +func TestCheckStandalonePluginImportsAllowsSDKImports(t *testing.T) { root := t.TempDir() writeTestFile(t, filepath.Join(root, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) writeTestFile(t, filepath.Join(root, "main.go"), `package example import ( - "git.dev.alexdunmow.com/block/core/backup" - "git.dev.alexdunmow.com/block/core/captcha" + "git.dev.alexdunmow.com/block/pluginsdk/auth" "git.dev.alexdunmow.com/block/pluginsdk/plugin" "git.dev.alexdunmow.com/block/pluginsdk/render" ) func Example() { _ = plugin.PluginRegistration{} - _ = captcha.Config{} - _ = backup.Config{} + _ = auth.CaptchaVerified _ = render.BlockNoteToHTML } `, 0644) @@ -77,6 +75,30 @@ func Example() { } } +// Captcha moved to the host-stamped X-Bn-Verified-Captcha trusted header +// (pluginsdk/auth), so the former core/captcha carve-out is gone: NO block/core +// package is plugin-importable anymore. +func TestCheckStandalonePluginImportsFlagsFormerlyKeptCoreImports(t *testing.T) { + root := t.TempDir() + writeTestFile(t, filepath.Join(root, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) + writeTestFile(t, filepath.Join(root, "main.go"), `package example + +import "git.dev.alexdunmow.com/block/core/captcha" + +func Example() { + _ = captcha.New +} +`, 0644) + + violations := checkStandalonePluginImports(root) + if len(violations) != 1 { + t.Fatalf("checkStandalonePluginImports() returned %d violations, want 1: %#v", len(violations), violations) + } + if violations[0].importPath != "git.dev.alexdunmow.com/block/core/captcha" { + t.Fatalf("importPath = %q, want forbidden core/captcha import", violations[0].importPath) + } +} + func TestCheckStandalonePluginImportsFlagsOrchestratorImports(t *testing.T) { root := t.TempDir() writeTestFile(t, filepath.Join(root, "plugin.mod"), "[plugin]\nname = \"example\"\nversion = \"1.0.0\"\n", 0644) diff --git a/plugin_sdk_versions.go b/plugin_sdk_versions.go index 7d9f597..1c90b23 100644 --- a/plugin_sdk_versions.go +++ b/plugin_sdk_versions.go @@ -169,13 +169,25 @@ func checkStandalonePluginGoMod(root, requiredSDKVersion string) []pluginGoModVi violations = append(violations, checkGoModForAnyReplaceDirectives(goModPath)...) // The standalone-plugin SDK is block/pluginsdk (2026-07-07 extraction). - // A block/core require may coexist for the packages that stayed core - // (captcha, backup) — allowed, not required. + // block/core is host-only: no plugin package imports survive (check 2c + // imports), so a core require is always vestigial and fails the gate. currentSDKVersion := "" for _, req := range parsed.Require { if req.Mod.Path == pluginSDKImportPrefix { currentSDKVersion = req.Mod.Version - break + continue + } + if req.Mod.Path == blockCoreImportPrefix { + line := 0 + if req.Syntax != nil { + line = req.Syntax.Start.Line + } + violations = append(violations, pluginGoModViolation{ + file: "go.mod", + line: line, + rule: "no-block-core-require", + detail: fmt.Sprintf("requires %s — standalone plugins build against %s only", blockCoreImportPrefix, pluginSDKImportPrefix), + }) } } if currentSDKVersion == "" { diff --git a/plugin_sdk_versions_test.go b/plugin_sdk_versions_test.go index e07256a..fe9b8d3 100644 --- a/plugin_sdk_versions_test.go +++ b/plugin_sdk_versions_test.go @@ -116,7 +116,7 @@ func TestCheckStandalonePluginGoModFlagsMissingPluginSDKRequire(t *testing.T) { go 1.26.2 -require git.dev.alexdunmow.com/block/core v0.2.1 +require github.com/google/uuid v1.6.0 `, 0644) violations := checkStandalonePluginGoMod(root, "v0.2.1") @@ -128,14 +128,30 @@ require git.dev.alexdunmow.com/block/core v0.2.1 } } -// A coexisting block/core require is allowed alongside the required pluginsdk -// require (e.g. calcomblock keeps core for captcha). func TestCheckStandalonePluginGoModAllowsMatchingSDKVersionWithoutReplace(t *testing.T) { root := t.TempDir() writeTestFile(t, filepath.Join(root, "go.mod"), `module example.com/plugin go 1.26.2 +require git.dev.alexdunmow.com/block/pluginsdk v0.2.1 +`, 0644) + + violations := checkStandalonePluginGoMod(root, "v0.2.1") + if len(violations) != 0 { + t.Fatalf("checkStandalonePluginGoMod() returned %d violations, want 0: %#v", len(violations), violations) + } +} + +// The former captcha/backup coexistence is over (captcha = host-stamped +// trusted header since pluginsdk v0.2.2): a block/core require in a +// standalone plugin go.mod is always vestigial and now fails the gate. +func TestCheckStandalonePluginGoModFlagsCoreRequire(t *testing.T) { + root := t.TempDir() + writeTestFile(t, filepath.Join(root, "go.mod"), `module example.com/plugin + +go 1.26.2 + require ( git.dev.alexdunmow.com/block/core v0.5.0 git.dev.alexdunmow.com/block/pluginsdk v0.2.1 @@ -143,8 +159,11 @@ require ( `, 0644) violations := checkStandalonePluginGoMod(root, "v0.2.1") - if len(violations) != 0 { - t.Fatalf("checkStandalonePluginGoMod() returned %d violations, want 0: %#v", len(violations), violations) + if len(violations) != 1 { + t.Fatalf("checkStandalonePluginGoMod() returned %d violations, want 1: %#v", len(violations), violations) + } + if violations[0].rule != "no-block-core-require" { + t.Fatalf("rule = %q, want no-block-core-require", violations[0].rule) } }