Documentation
¶
Overview ¶
Importable rule body for ADAPTER-RETURNS-DECLARED-TYPES-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1639) so the rule is module-path-agnostic — the running module path is derived from go.mod via moduleImportPath, NOT bare "github.com/ghbvf/gocell…" literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The dogfood + good/bad-fixture precision gates live in adapter_returns_declared_types_test.go.
Not registered in StandardCellRules: this rule reads contract.yaml metadata and scans generated contract package imports, which are present in any Cell repo that uses GoCell codegen. Registration + external vacuous/effective semantics verification is a deferred backlog follow-up per #1639 D2 (tracked at #1706). Kept importable + module-path-agnostic.
ADAPTER-RETURNS-DECLARED-TYPES-01 ¶
ADAPTER-RETURNS-DECLARED-TYPES-01: adapter return status ⊆ contract declared. Scope: Ceiling guard only (adapter zero typed return is legal — full framework fallback is permitted). Floor guards land via roadmap GOCELL-INVARIANT-AUDIT-V1.
Algorithm:
- metadata.NewParser(repoRoot).Parse() to load declared status sets: filter kind=="http" && Codegen==true, set = SuccessStatus ∪ Responses keys.
- Glob cells/*/slices/*/handler.go ∪ cells/*/slices/*/service.go ∪ examples/*/cells/*/slices/*/handler.go ∪ examples/*/cells/*/slices/*/service.go.
- For each file: AST parse, walk FuncDecl. Identify adapter methods by first return type name ending in "ResponseObject". Resolve contract ID from imports. Walk ReturnStmt → CompositeLit, extract status from struct name regex. Status ∉ declared → Diagnostic.
- nil / ident (non-CompositeLit) returns → skip (ceiling guard).
Blind spots (BS) — declared per ai-robust.md §"工具选定后强制盲区自检" ¶
This is a CEILING guard: it must never false-positive, so it tolerates the false negatives below (each skips, never mis-flags). A1 (FIXTURE-CELLID) / floor guards (roadmap GOCELL-INVARIANT-AUDIT-V1) close the other direction.
- BS-1 Multi-import ambiguity: contractIDForTypeName resolves the owning contract by best-effort method-name-prefix match across the file's contract imports; a file importing several contracts whose generated struct prefixes collide may resolve to the wrong contract → that return is checked against the wrong declared set (skipped, not mis-flagged).
- BS-2 Dot / aliased contract import: importAlias falls back to the last import-path segment, which can collide; an unresolved alias yields contractID == "" → skip.
- BS-3 Reflection / dynamically-built response value (non-CompositeLit return): out of scope per ai-robust.md §3 — only inline CompositeLit returns are inspected.
ref: goa goagen/codegen/types/types.go ref: connect-go cmd/protoc-gen-connect-go
Package archtest callresolver.go — façade re-exports for the callsite resolution convenience layer (internal/callresolver).
This completes the façade contract for callsite-resolution: business archtest *_test.go files reach the walker + resolution helpers through archtest.{WalkFuncDecls,WalkFuncDeclsAST,IsCallToPkgFunc,HasReceiver}, never importing internal/callresolver directly. PASS-FUNNEL-RESOLVE-01 (pass_funnel_test.go) bans the direct-import path.
The *Pass-taking walker (WalkFuncDecls) lives here rather than in internal/callresolver because Pass is declared in package archtest: an internal package referencing *archtest.Pass would form an import cycle (archtest already imports internal/callresolver to re-export it). Both walker forms delegate to the single decoupled engine internal/callresolver.WalkFuncDecls, so there is exactly one traversal implementation.
Importable rule body for CAPABILITY-PROVIDER-FUNNEL-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1639) so the rule is module-path-agnostic — platform symbol paths are derived from PlatformModulePath (external.go), NOT bare "github.com/ghbvf/gocell…" literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The dogfood + RED-fixture precision gate live in capability_provider_funnel_test.go.
Not registered in StandardCellRules: the sole sanctioned provisioning site (cmd/corebundle/cap_wiring.go) and the banned shared-infra constructors are GoCell-internal composition-root layout; an external Cell repo has no such file, so the allowlist never matches and the rule degrades to a pure ban that would false-red any external module that legitimately constructs its own pool / client. Kept importable + module-path-agnostic but OUT of StandardCellRules (same disposition as the #1632 auth funnels).
CAPABILITY-PROVIDER-FUNNEL-01 ¶
The shared-infrastructure constructors
adapters/postgres.NewPool / NewTxManager / NewOutboxWriter / NewJournalingOutboxWriter adapters/redis.NewClient
may only be invoked from the assembly's single provisioning site (cmd/corebundle/cap_wiring.go). Cell module files (cellmodules/<cell>/module.go) and every other cmd/ file must consume the injected capability.PGProvider / capability.RedisProvider instead of constructing the shared pool / client / the pool-bound TxManager+OutboxWriter themselves.
This is the downstream half of the funnel; the upstream half is the sealed capability.PGProvider / capability.RedisProvider (unexported marker methods + private impls + sole NewPGProvider/NewRedisProvider constructors in runtime/capability), which Go's type system makes unforgeable outside package capability.
Not banned (per-cell / per-role derivations) ¶
adapters/postgres.NewSessionStore / NewRefreshStore / NewLedgerStore / NewOutboxStore and adapters/redis.NewCache / NewRedisDriver / NewIdempotencyClaimer / NewNonceStore take an already-acquired pool handle (*pgxpool.Pool) or *redis.Client and derive a per-cell / per-role object. They are the legitimate downstream of the provider (CLAUDE.md observability §"per-cell 资源:cell 直接构造 NewCache(client, …)") and stay in module files.
AI-robust: upstream Hard + downstream Medium (transition form) ¶
Upstream is Hard (sealed construction, type system). Downstream is Medium: archtest resolves every CallExpr callee via archtest.ResolvePackageRef (owning package import path, not the source Ident), matching by (pkgPath, name). Per ai-robust.md §"Funnel 双向锁评级" a Hard-upstream + Medium-downstream funnel is an allowed transition form; the downstream→Hard upgrade (cmd/corebundle module files moved out of `package main` so the banned constructors are import-unreachable) is tracked at gh issue #988.
The sole sanctioned provisioning site (capWiringRel) is matched by isCapWiringSanctionedSite, which binds the exemption to PLATFORM PACKAGE IDENTITY (isGoCellPlatformPkgPath) — not a bare repo-relative path. This rule is importable: an external Cell repo can wire it via cfg.ExtraRules, and a consumer module could otherwise place its own cmd/corebundle/cap_wiring.go and call the banned constructors to claim the GoCell-internal exemption. A forged site has pkgPath = <consumer-module>/… (not under PlatformModulePath), so it is correctly never exempt and the rule stays a true pure ban there. Same provenance bind as isReconstructionAllowedSite (OUTBOX-RECONSTRUCTION-CALLER-01).
_test.go scope ¶
Run(t, Typed(TypedOpts{Tests: false}, ...)) loads only production-variant packages, so _test.go files are not in pass.Files; the scanner additionally filters by rel suffix as defense-in-depth — so the rule stays correct if a future caller passes Tests: true or runs it over a fixture (Run(t, Fixture(...))) load path where _test.go files would be present.
Blind spots (BS) ¶
- BS-1 Name shadowing: a non-adapter package exporting a function literally named NewPool does NOT match — ResolvePackageRef compares the callee's owning package path via go/types, not the import-site Ident.
- BS-2 Function-value indirection: `var f = adapterredis.NewClient; f(...)` resolves the call's callee to a *types.Var (ok=false) and is not flagged. Accepted: this is exactly how cmd/corebundle/redis.go injects the client factory (redisClientFactory), and it lives in the composition root, not a module file. Same accepted BS as CAS-PROTOCOL-COMPOSITION-ROOT-01 BS-2.
- BS-3 Reflection construction: out of scope per ai-robust.md §3.
Importable rule body for CELLTLS-MATERIAL-FUNNEL-01. Non-test .go file so the rule is module-path-agnostic — platform symbol paths are derived from PlatformFrameworkModulePath (external.go), NOT bare "github.com/ghbvf/gocell/framework…" literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The dogfood + RED-fixture precision gate live in celltls_material_funnel_test.go.
Not registered in StandardCellRules: the sanctioned provisioning sites (cellmodules/celltls, adapters/grpc) and the banned tlsutil mTLS-material constructors are GoCell-internal packages; an external Cell repo has no such files, so the allowlist never matches and the rule degrades to a pure ban that would false-red any external module that legitimately calls the tlsutil helpers from its own provisioning site. Kept importable + module-path-agnostic but OUT of StandardCellRules (same disposition as CAPABILITY-PROVIDER-FUNNEL-01 and the #1632 auth funnels).
CELLTLS-MATERIAL-FUNNEL-01 ¶
The transport mTLS-material constructors
framework/runtime/http/tlsutil.NewClientIdentity framework/runtime/http/tlsutil.NewServerMTLSConfig
may only be called from the following sanctioned sites (verified by repo scan on 2026-06-17, issue #2263):
- cellmodules/celltls (celltls.Resolve) — cell-to-cell HTTP mTLS for the internal listener in split topology (the primary funnel site; topology- gated, fail-closed). Calls both NewClientIdentity and NewServerMTLSConfig.
- adapters/grpc (buildCredentials) — gRPC listener transport-layer mTLS: a separate TLS channel provisioned directly from PEM bytes in the gRPC server TLSConfig, independent of the HTTP cross-cell transport. Calls NewServerMTLSConfig only; NewClientIdentity is NOT used here (the gRPC transport-layer does not build the ClientIdentity sealed bundle).
- cellmodules/deviceidentity (NewDeviceMTLSServerConfig, #1904) — the device-mTLS (renew) listener verifies DEVICE client certificates against the issuing CA trust bundle (RFC 7030 device cert renewal). A third legitimate mTLS server site, device-facing rather than cell-to-cell. Calls NewServerMTLSConfig only; NewClientIdentity is celltls-only.
Every other production caller is a violation.
AI-robust: upstream Hard + downstream Medium (Go-ceiling transition form) ¶
Upstream is Hard: [tlsutil.ClientIdentity] has only unexported fields; the sole constructor is [tlsutil.NewClientIdentity]; outside package tlsutil a zero-value ClientIdentity.IsZero() call is the only shape available — forging a populated value (and therefore bypassing this funnel) is a compile error. Downstream is Medium: archtest resolves every CallExpr callee via ResolvePackageRef (owning package import path from go/types, not the source Ident), so import aliases don't defeat it. Per ai-robust.md §"Funnel 双向锁评级" a Hard-upstream + Medium-downstream funnel is the accepted transition form; the downstream→Hard upgrade path is the same as CAPABILITY-PROVIDER-FUNNEL-01 (gh #988).
_test.go scope ¶
Production(TypedOpts{Tests: false}) loads only production-variant packages, so _test.go files are not in pass.Files; the scanner additionally filters by rel suffix as defense-in-depth — so the rule stays correct if a future caller passes Tests: true or runs it over a fixture.
Blind spots (BS) ¶
- BS-1 Name shadowing: a non-tlsutil package exporting a function literally named NewClientIdentity or NewServerMTLSConfig does NOT match — ResolvePackageRef compares the callee's owning package path via go/types, not the import-site Ident.
- BS-2 Function-value indirection: `var f = tlsutil.NewClientIdentity; f(...)` resolves to *types.Var (ok=false) and is not flagged. Accepted: the sanctioned callers don't do this.
- BS-3 Reflection construction: out of scope per ai-robust.md §3.
clock_invariants.go — importable clock injection rule logic (#1640 M3 PR-9).
Non-test home for KERNEL-CLOCK-LEAF-FALLBACK-01, KERNEL-CLOCK-RESET-RELATIVE-PROD-01, PROD-CLOCK-INJECTION-01, and CLOCK-POSITIONAL-INJECTION-01 scanner logic, so it can be compiled and run by an external Cell repository (Go never compiles a dependency's _test.go, so rule logic external repos must run cannot live in a _test.go file). GoCell's own Test* functions in clock_invariants_test.go dogfood the same Check* — single source, no parallel rule body.
Four rules are implemented here:
- KERNEL-CLOCK-LEAF-FALLBACK-01: leaf-level clock.Real() construction is forbidden outside the composition root.
- KERNEL-CLOCK-RESET-RELATIVE-PROD-01: production code must use the absolute Timer.ResetAt(deadline time.Time) API.
- PROD-CLOCK-INJECTION-01: no direct reference to stdlib time wall-clock entry points in production code; all wall-clock interactions must flow through an injected kernel/clock.Clock.
- CLOCK-POSITIONAL-INJECTION-01: clock.MustHaveClock arg0 must be a positional parameter; no exported clock option-injector functions; no exported Config/Options struct fields of type clock.Clock.
Dogfood tests: TestKernelClockLeafFallback, TestKernelClockResetRelativeProd, TestProdClockInjection, TestClockPositionalInjection (clock_invariants_test.go).
Register status ¶
Not registered in StandardCellRules: allowlist/scan-scope is gocell-hardcoded (kernel/clock layout, composition-root carve-outs) with no ConfigForExternalCell consumer-extension -> vacuous/false-red externally; kept importable + module-path-agnostic + fork-safe.
Package archtest enforces Go source-level import layering rules for the GoCell architecture.
This complements kernel/governance which validates metadata-level dependencies (DEP-01 to DEP-03: cell ownership, cycle detection, L0 co-location) from YAML files. archtest operates on the typed Go package graph supplied by tools/depgraph (single packages.Load shared across LAYER-05/05T/06/06T/07/08/09/09T/10) to catch violations that metadata validation cannot see.
Rules enforced (from CLAUDE.md):
LAYER-01: kernel/ may only import stdlib, pkg/, and kernel/ (allow-list)
[moved to depguard (.golangci.yml linters.settings.depguard.rules)]
LAYER-02: cells/ must not import adapters/
[moved to depguard (.golangci.yml linters.settings.depguard.rules)]
LAYER-03: runtime/ must not import cells/ or adapters/
[moved to depguard (.golangci.yml linters.settings.depguard.rules)]
LAYER-04: adapters/ must not import cells/, cmd/, or examples/
[moved to depguard (.golangci.yml linters.settings.depguard.rules)]
LAYER-05: cells/A must not directly import cells/B/internal/ (cross-cell isolation)
LAYER-05T: cells/A must not transitively import cells/B/internal/ via any
production-edge closure (T = transitive; depgraph.TransitiveImports)
LAYER-06: cell-owned public subpackages (see cellOwnedSubpackages) may
only be imported by their owning cell, cmd/, or examples/
LAYER-06T: same as LAYER-06 but checked against the transitive closure
LAYER-07: cells/ must not import runtime/http/router directly
LAYER-08: the legacy cell-level HTTP route registrar interface must remain
absent — enforced at the type level by walking each module
package's types.Scope() for a top-level TypeName matching the
legacy name. String literals in comments / struct tags are
accepted (type-level scope is precise where text scanning
over-matches into prose)
LAYER-09: cells/A must not directly import cells/B/events
LAYER-09T: cells/A must not transitively import cells/B/events
LAYER-10: cells/<cell> root package exported APIs must not expose concrete
adapter/driver types
PGQUERY-01: PostgreSQL SQL builder/keyset helpers must live in pkg/pgquery;
pkg/query remains limited to generic pagination, cursor,
runmode, and in-memory pagination helpers
In-process cross-cell Go handoff = 0 — guard inventory (epic #1423 / US8) ¶
US8's acceptance signal (SC-003 / FR-009) requires "进程内跨 cell Go 直传 = 0" (zero in-process cross-cell Go handoff) be permanently archtest-guarded at Medium+. The guards that together enforce it, organized by handoff FORM (the inventory deliverable of #1961 / T071 — this is the口径 single source; per-rule symbols/blind spots live in each rule's own godoc):
MODULE-PROVIDE-NO-VALUE-HANDOFF-01 Hard re-opening the ModuleResult
(module_provide_signature_frozen_test.go) value-handoff channel — a cell
handing sibling values back through
Provide (reflect field freeze)
LAYER-05 / LAYER-05T Medium cell A importing cell B/internal/
(archtest_test.go) (direct / transitive)
LAYER-06 / LAYER-06T Medium cell A importing a cell-owned
(archtest_test.go) public subpackage of cell B
LAYER-09 / LAYER-09T Medium cell A importing cell B/events
(archtest_test.go) (direct / transitive)
GRPC-CELL-NO-CLIENT-DIAL-01 Medium a cell constructing OR holding a
(grpc_cell_no_client_dial_test.go) gRPC client (dial / *grpc.ClientConn /
generated <Svc>Client surface) to reach
a sibling cell in-process — incl. the
inject-a-built-client escape (#1961;
closes the #1752 runtime/grpc gap)
Open blind spots (NOT yet guarded — tracked, not silently dropped):
interface{} / any pointer smuggling — a cell holding a sibling cell's concrete
value behind an `any` field/param; type erasure defeats the LAYER-05/06 import
edges and the MODULE-PROVIDE field freeze. Backlog: gh #2002 (Medium+ target).
sync HTTP direct dial — a cell calling a sibling contract via a raw http.Client.
OUT OF SCOPE here: owned by US4's CellTransport funnel task, not #1961.
Themed invariant files & reverse-index anchors ¶
Beyond the LAYER-* / PGQUERY-01 rules above, this package hosts the rest of the invariant gates organized into per-theme `*_invariants_test.go` files (and single-rule `{rule}_test.go` companions for narrowly-scoped invariants). Every file carries a `// INVARIANT: {ID}` anchor in its file-header CommentGroup; `INVENTORY-ANCHOR-REQUIRED-01` enforces this unconditionally, making the anchors the single source of the reverse index from rule ID to asserting test code:
grep -rn 'INVARIANT: <ID>' tools/archtest/
jumps directly to the gate. Multi-rule themed files use list-form continuation (`// - INVARIANT: <ID>`) so every distinct rule the file asserts is grep-discoverable.
assembly_invariants_test.go ASSEMBLY-* / ASSEMBLYREF-*
clock_invariants_test.go CLOCK-* / KERNEL-CLOCK-* / PROD-CLOCK-*
codegen_invariants_test.go CODEGEN-* / SPEC-GEN-*
errcode_invariants_test.go ERRCODE-KIND-LITERAL / MESSAGE-CONST-LITERAL /
ERROR-FIRST-* / DETAILS-SLOG-ATTR / EXPORTED-ERROR-NEW
handler_policy_required_test.go HANDLER-POLICY-REQUIRED-01 (caller-side
wiring scan; the other 4 HANDLER-* rules
were funnel-pinned via handler.tmpl +
golden in tools/codegen/contractgen by
PR-FUNNEL-02)
httputil_invariants_test.go HTTPUTIL-*
outbox_invariants_test.go OUTBOX-*
panic_invariants_test.go PANIC-*
prod_invariants_test.go PROD-DURATION-CONST-01
refresh_invariants_test.go REFRESH-*
rmq_invariants_test.go RMQ-*
Single-rule files retain the `{rule}_test.go` naming (e.g. `adapter_returns_declared_types_test.go`); they convert to `{theme}_invariants_test.go` once the theme accumulates ≥ 3 rules — see CLAUDE.md `## 新增 invariant 决策原则` for the file-naming branch.
On-demand inventory listing (no persisted view):
bash hack/scripts/audit/list-archtests.sh
prints every anchor + file + line + theme to stdout. Persisted `docs/audit/archtest-inventory.md` and its drift gate were removed in PR-A' (2026-05-10); the archtest gate above is the single source.
Importable rule body for TYPESEVAL-EVAL-PREDICATE-CENTRALIZED-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1639) so the rule is module-path-agnostic — platform symbol paths are derived from PlatformModulePath (external.go), NOT bare "github.com/ghbvf/gocell…" literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The dogfood + RED-fixture precision gate live in eval_predicate_centralization_test.go.
Not registered in StandardCellRules: this is a framework-self-referential meta-rule that scans GoCell's own tools/archtest/*_test.go for constraint.Expr.Eval callsites. An external Cell repo has its own archtest package — the scan target (./tools/archtest/...) would resolve to that repo's archtest (if it exists) or yield zero files, making the rule vacuous-green there. Kept importable and module-path-agnostic (all platform paths derived from PlatformModulePath) but OUT of StandardCellRules (same disposition as the #1632 auth funnels and CAPABILITY-PROVIDER-FUNNEL-01).
TYPESEVAL-EVAL-PREDICATE-CENTRALIZED-01 ¶
Every constraint.Expr.Eval(arg) callsite in tools/archtest/*_test.go (top- level package, excluding internal/ subpackages) MUST pass either:
Form A — a *ast.CallExpr whose callee resolves to either
tools/archtest/internal/typeseval.BuildContextPredicate
(qualified cross-package form) OR
tools/archtest.BuildContextPredicate (same-package bare form,
which is a thin delegation to typeseval.BuildContextPredicate).
Any extraTags arguments are fine; the funnel is the call form.
Form B — an inline *ast.FuncLit of shape func(_ string) bool { return false }
— the all-false sentinel — body must be exactly one ReturnStmt
whose single result is the identifier `false`. Indirection via a
var binding (`var fn = func(...) { return false }; expr.Eval(fn)`)
is NOT accepted: the inline FuncLit shape is the Hard surface.
Any other predicate shape — `func(tag string) bool { return tag == "X" }`, a named helper, a variable identifier, a method reference, a composite return expression — fails this rule.
Why a static funnel (Hard) and not a doc convention (Soft):
The toolchain-default tag set (GOOS/GOARCH/cgo/unix/gc/go1.X) drifts every release. A hand-written predicate that hard-codes "linux || darwin || amd64 || ..." silently misses additions. BuildContextPredicate sources implicitDefaults from build.Default.ReleaseTags + a single mirror of internal/syslist, so go.mod floor bumps propagate automatically. Forcing every consumer through the call form makes the drift unreachable.
The all-false sentinel is the canonical "evaluate under empty tag set" form; allowing it explicitly avoids forcing a contrived BuildContextPredicate() call where the intent is to deny every tag.
AI-robust rating: Hard. Per .claude/rules/gocell/ai-robust.md "Hard 范本" — "typed function call as Hard funnel for unbounded operations": form uniqueness + archtest fail-on-deviation is the highest grade reachable in Go for this rule shape. The Go type system does not prevent passing arbitrary func(string)bool to constraint.Expr.Eval at compile time; static archtest at CI is the canonical Hard enforcement.
Covered (NOT a blind spot — listed to preempt the question): indirection through a var binding (`var f = func(...) bool { return false }; expr.Eval(f)`) resolves the arg to an *ast.Ident — neither the canonical BuildContextPredicate(...) call nor an all-false FuncLit — so it is REJECTED, forcing the inline canonical form (intentional strictness, not a gap).
Blind spots (BS) ¶
- BS-1 Reflection / code generation producing constraint.Expr.Eval calls: out of scope per ai-robust.md §3.
- BS-2 Scope: only top-level tools/archtest/ _test.go files are checked (internal/ sub-packages are excluded per the rule intent).
fence_token_mint_funnel.go — caller-allowlist funnel for credentialfence.Mint. Closes the upstream half of the credential-invalidate funnel: combined with the Go type system seal (FenceToken interface has an unexported isCredentialFenceToken marker method, so no external type can implement it) and the runtime nil-guard in the three mutation methods, this archtest yields upstream Hard via call-site form-uniqueness — the .claude/rules/gocell/ai-robust.md §Hard 范本目录 "typed marker funnel for unbounded ops" pattern.
INVARIANT: FENCE-TOKEN-MINT-FUNNEL-01
Rule: every non-test caller of credentialfence.Mint must live in one of the allowlisted production paths (the credentialinvalidate funnel + storetest / conformance suites). Any other caller is a violation: production code that is not the funnel must not be able to construct a FenceToken.
AI-robust grade (Mint-caller dimension): Hard via call-site form-uniqueness per ai-robust.md §Hard 范本目录 "typed marker funnel for unbounded ops". The scanner is form-complete: it flags EVERY reference to credentialfence.Mint (direct call, var-decl / short-var function-value capture, return, pass-through arg, reflect arg) by walking all SelectorExpr and resolving each via ResolvePackageRef (alias-immune). Because credentialfence is type-sealed, a reference to Mint is the only way to obtain a FenceToken, so flagging every reference closes the function-value-capture and reflect blindspots structurally — there is no separate per-form blindspot self-check to keep in sync. Residual blindspots (dot-import, //go:linkname, unsafe) are the universal class that defeats any static analysis; see scanFenceTokenMintRefs.
Grade caveat: enforcement is archtest-bound, not compile-time. Go cannot express "only package X may call function Y", so this is the Hard ceiling the rule's shape can reach (the PANIC-REGISTERED-01 precedent). The type-system Hard guarantee is the *construction* seal (external packages cannot implement FenceToken nor build the unexported impl); see runtime/auth/credentialfence.
Companion archtests (the downstream half of the same funnel):
- CREDENTIAL-INVALIDATE-FUNNEL-01 — RevokeForSubject caller allowlist
- USER-AUTHZ-EPOCH-BUMP-FUNNEL-01 — BumpAuthzEpoch caller allowlist
- REFRESH-REVOKE-USER-FUNNEL-01 — RevokeUser caller allowlist
- CREDENTIAL-INVALIDATE-UPSTREAM-CALLER-01 — Invalidator.Apply caller allowlist
Together these four rules (downstream Hard) plus this rule (upstream Hard) fully close the credential-invalidation safety model. See ADR docs/architecture/202605101400-adr-credential-session-protocol.md §A16 for the closure proof and threat-matrix re-evaluation.
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
Importable rule bodies for FIXTURE-CELLID-TYPED-BUILDER-01 and METADATATEST-IMPORT-SCOPE-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1639) so both rules are module-path-agnostic — platform symbol paths (kernel/metadata, kernel/metadata/metadatatest, the carve-out map key) are derived from PlatformModulePath (external.go), NOT bare "github.com/ghbvf/gocell…" string literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The self-checks (A2 body-shape, A3 negative fixture, A4 ADR-consistency, A5 var-initializer shape) live in fixture_cellid_typed_builder_test.go and call the helpers defined here — single source, no parallel rule body.
Not registered in StandardCellRules: both invariants are GoCell-internal, tied to the kernel/metadata + kernel/metadata/metadatatest layout that no external Cell repo replicates. FIXTURE-CELLID-TYPED-BUILDER-01 scans kernel/-rooted fixtures for bare cell-id literals; an external repo lacks that layout → vacuous-green. METADATATEST-IMPORT-SCOPE-01 guards against importing the GoCell-internal metadatatest package from production code; an external repo that does not import metadatatest at all is trivially clean. Kept importable + module-path-agnostic but OUT of StandardCellRules (same disposition as the #1632 auth funnels and CAPABILITY-PROVIDER-FUNNEL-01).
FIXTURE-CELLID-TYPED-BUILDER-01 ¶
Every cell-id field position in a kernel/metadata.* struct or map composite literal — anywhere in the module's hand-written code (production + tests, all tag combinations) — must be sourced from one of three sanctioned forms:
- kernel/metadata/metadatatest.NewCellID(literal) — typed-builder call.
- One of metadatatest's pre-validated package-level cell-id vars (CellID*).
- metadata.FrameworkOwnerSentinel const (#1939) — the reserved "_framework" owner/provider value. It cannot go through NewCellID (leading underscore is not a legal cell id, so NewCellID would panic) and cannot be a CellID* var (A5 forbids non-NewCellID initialisers). It is therefore accepted directly via TypesInfo identity lock (metadataPkgPath + const name). Accepting it at all cell-id positions is harmless: "_framework" is not a legal cell id per MatchCellID, so inadvertent use at CellMeta.ID is caught by FMT-C1. Bare string literals at
those positions are rejected.
Scope: currently kernel/ only. Test fixtures in runtime/, cells/, cmd/, examples/, and tools/ outside the migrated tools/codegen + tools/generatedverify subset may still embed bare cell-id literals (Soft state). Mirror backlog issue #1201 tracks scope expansion to non-kernel/ packages.
Enforcement is split into sub-rules A1–A5; A1 is the importable rule body supplied by CheckFixtureCellIDTypedBuilder. A2, A3, A4, A5 are self-checks that stay in the _test.go companion and call helpers defined here.
AI-robust: downstream Hard (A1 typed-info callsite identity), upstream Hard (A2 body form-uniqueness + pkg-path identity lock), meta Hard (A3 negative fixture + A4 ADR consistency). See the _test.go package godoc for the full evaluation.
METADATATEST-IMPORT-SCOPE-01 ¶
The metadatatest package (kernel/metadata/metadatatest) must only be imported by *_test.go files or archtest_fixture-tagged code. Importing it from production code drags init-time panics into runtime and contradicts its test-only purpose. CheckMetadatatestImportScope is the importable rule body.
AI-robust: Medium, single-axis (path-based archtest scope — NOT a funnel, so no caller-allowlist / sealed-construction double-lock applies). Go cannot express a "test-only package" at the type level, so the sole Hard-upgrade path is a Go language feature that does not exist — a permanent Go-ceiling won't-do (no GoCell-side issue; same ceiling family as #851 / #893 / #1282). The AST import-scan (production .go importing metadatatest) is the canonical enforcement; dot-import is resolved via importsMetadatatest.
Importable rule body for INSECURE-SKIP-VERIFY-LITERAL-01. Non-test .go file so the rule is module-path-agnostic — platform symbol paths are derived from PlatformFrameworkModulePath (external.go), NOT bare string literals (ARCHTEST-MODULE-PATH-FUNNEL-01). The dogfood + RED-fixture precision gate live in insecure_skip_verify_funnel_test.go.
Not registered in StandardCellRules: the allowed site (framework/runtime/http/tlsutil) is a GoCell-internal package; an external Cell repo has no such file, so the allowlist is vacuous and the rule degrades to a pure repo-wide ban — which is the correct posture for an external consumer that should never set InsecureSkipVerify:true. An ExtraRule wire is the correct way to pick it up. Kept importable + module-path-agnostic but OUT of StandardCellRules.
INSECURE-SKIP-VERIFY-LITERAL-01 ¶
No PRODUCTION .go file may set the composite-literal struct field
InsecureSkipVerify: true
(a KeyValueExpr whose Key ident is "InsecureSkipVerify" and Value is the "true" ident) EXCEPT inside framework/runtime/http/tlsutil (the sole sanctioned home, where the field is paired with a VerifyConnection callback that performs the full SPIFFE-ID + chain check — see tlsutil.NewClientMTLSConfig godoc).
This catches a future fail-open `&tls.Config{InsecureSkipVerify: true}` written without the compensating VerifyConnection verifier. It is DISTINCT from SEC-FAIL-CLOSED-04 (security_defaults.go), which scans only adapters/websocket for the ASSIGNMENT form
opts.InsecureSkipVerify = true
This rule covers the composite-literal FIELD form repo-wide.
AI-robust: Medium ¶
The composite literal `InsecureSkipVerify: true` is expressible anywhere — this is exactly the Go-language ceiling that prevents Hard. The rule uses text-free AST scan (KeyValueExpr key ident + value ident) across all production .go files, so it fires on every future literal regardless of variable name, struct type name, or import alias. Anti-vacuity proven by the RED fixture in insecure_skip_verify_funnel_test.go.
Allowed site ¶
framework/runtime/http/tlsutil (package dir prefix, bound to platform package identity via isGoCellPlatformPkgPath). The single current literal is inside NewClientMTLSConfig where InsecureSkipVerify:true is paired with a VerifyConnection callback. Adding a second literal inside tlsutil requires the same pairing (enforced by code review; the archtest only closes the out-of-package bypass vector).
_test.go scope ¶
_test.go files are skipped by rel-suffix filter — tests may legitimately construct tls.Config{InsecureSkipVerify: true} for local dial without a peer verifier.
Blind spots (BS) ¶
- BS-1 Variable assignment: `cfg.InsecureSkipVerify = true` (AssignStmt) is covered by SEC-FAIL-CLOSED-04 (adapters/websocket scope). This rule covers the composite-literal form only; assignment form outside adapters/websocket is a gap shared with SEC-04's scoped design.
- BS-2 Indirect true: `var t = true; tls.Config{InsecureSkipVerify: t}` — the Value is *ast.Ident with Name != "true", so it is not flagged. Accepted: fabricating a true-alias to circumvent the check requires deliberate evasion, outside the incidental-mistake threat model.
INVARIANT: MQTT-CLIENT-ID-NAMESPACE-01 INVARIANT: MQTT-TOPIC-NAMESPACE-01 INVARIANT: MQTT-CONFIG-SEALED-FIELD-FROZEN-01
mqtt_funnel.go — importable sealed-struct construction funnel logic for adapters/mqtt.ClientID, adapters/mqtt.TopicNamespace, and adapters/mqtt.Config.
This is the non-test home of the MQTT-CLIENT-ID-NAMESPACE-01 and MQTT-TOPIC-NAMESPACE-01 scanner helpers so they can be compiled by external Cell repositories through the CellRule pattern (Go never compiles a dependency's _test.go, so rule logic that external repos must run cannot live in a _test.go file). GoCell's own TestMQTTClientIDNamespace01 / TestMQTTTopicNamespace01 functions (mqtt_funnel_test.go) call the same shared helpers — single source, no parallel rule body.
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
Platform-symbol paths are anchored to PlatformModulePath so a module rename updates exactly one place and no bare literal appears here.
INVARIANT: REASON-NAME-REDACTION-01
mqtt_reason_redaction.go — importable REASON-NAME-REDACTION-01 rule logic.
This is the non-test home of the REASON-NAME-REDACTION-01 scanner so it can be compiled by external Cell repositories through the CellRule pattern (Go never compiles a dependency's _test.go, so rule logic that external repos must run cannot live in a _test.go file). GoCell's own TestMQTTReasonNameRedaction_FunnelOnly / TestMQTTReasonNameRedaction_ScannerNonVacuous (mqtt_reason_redaction_test.go) call the same shared helpers — single source, no parallel rule body.
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — this dogfood-only rule targets a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
Platform-symbol paths are anchored to PlatformModulePath so a module rename updates exactly one place and no bare literal appears here.
notfound_test_strict.go — importable POSTGRES-NOTFOUND-TEST-OTHER-ERROR-MIXUP-ARCHTEST-01 rule logic (#1640 M3 PR-9).
Non-test home for POSTGRES-NOTFOUND-TEST-OTHER-ERROR-MIXUP-ARCHTEST-01 scanner logic, so it can be compiled and run by an external Cell repository.
Dogfooded by tools/archtest/notfound_test_strict_test.go:
TestNotFoundTestStrict → Report(t, "POSTGRES-NOTFOUND-TEST-OTHER-ERROR-MIXUP-ARCHTEST-01", CheckNotFoundTestStrict(...))
Register status ¶
Not registered in StandardCellRules: this rule's scan scope (errcodetest + errcode packages) is gocell-internal layout specific with no ConfigForExternalCell consumer-extension → vacuous/false-red externally.
Importable rule bodies for outbox invariants migrated to non-test .go (M3 #1302 PR-5) so external Cell repos can import and run them via StandardCellRules / RunStandardCellRules. The dogfood + RED-fixture precision gates live in outbox_invariants_test.go.
OUTBOX-HANDLERESULT-FACTORY-PREFERRED-01 ¶
Production code (non-_test.go) must use kernel/outbox factories Ack/Requeue/Reject instead of constructing outbox.HandleResult{...} composite literals, except for the files in handleResultLiteralAllowlist (factories themselves + shared conformance harness).
External Cell repo semantics: the allowlist is consulted by isHandleResultLiteralAllowed, which FIRST requires the constructing package to be a GoCell platform package (isGoCellPlatformPkgPath). The allowlist entries (kernel/outbox/result.go, kernel/outbox/outboxtest/conformance.go) belong to the GoCell platform module; a consumer module that forges one of these rel paths has a non-platform package path and is correctly NOT exempt, so the rule degrades to a PURE BAN on HandleResult{} literals — the intended behavior: business handlers must use the typed factories.
OUTBOX-TOPIC-FAILOPEN-01 ¶
An outbox.Entry composite literal whose Topic or EventType string constant matches one of the security-sensitive prefixes (session.*, user.*, role.*, audit.* and their event.* contract forms) must not set FailurePolicy: outbox.FailurePolicyFailOpen.
NOT registered in StandardCellRules (vacuous-external): kernel/outbox.Entry is a sealed type — all fields are unexported and a populated outbox.Entry{...} composite literal outside kernel/outbox is a COMPILE ERROR (OUTBOX-ENTRY-SEALED-CONSTRUCTION-01, kernel/outbox/outbox.go). No production package — in GoCell or in any external Cell repo — can construct the literals this rule scans for, so the production scan is vacuous everywhere; the rule's real coverage is the fixturetest/outbox fake-package fixtures under testdata/. It is migrated here for PlatformModulePath parameterization + fork-safety only (analogous to SCAFFOLD-LISTENER-MARKER-TYPED-CONST-01), enforced in GoCell via the dogfood + fixture sub-tests in outbox_invariants_test.go.
AI-robust ratings ¶
Both rules are Medium downstream (archtest type-aware caller-allowlist / type-identity scan); Hard upstream is not reachable — Go cannot express "callers in cells/ must use factory names" without unexporting HandleResult (which breaks kernel-internal plumbing), nor can Go prevent an outbox.Entry composite literal from setting any public field. This permanent upstream ceiling is the same Go-visibility limit tracked for the #851 / #893 / #1282 holder-seal family (won't-do).
outbox_reconstruction_caller.go — importable rule body for OUTBOX-RECONSTRUCTION-CALLER-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1302 / #1635) so external Cell repos can import and run it via StandardCellRules / RunStandardCellRules. The dogfood + anti-vacuity reverse self-check live in outbox_reconstruction_caller_test.go.
OUTBOX-RECONSTRUCTION-CALLER-01 ¶
outbox.Entry is sealed construction (OUTBOX-ENTRY-SEALED-CONSTRUCTION-01): a populated composite literal outside kernel/outbox is a compile error. That makes Entry FORGERY-BY-LITERAL unrepresentable (type-system Hard upstream). But Entry has two cross-package RECONSTRUCTION funnels that legitimately must exist — and both accept attacker-controllable Principal / OccurredAt without any provenance check (Entry.Validate only checks well-formedness, never "where did this Principal come from"):
- outbox.UnmarshalEnvelope(topic, raw []byte) — decodes arbitrary wire bytes into a sealed Entry. A caller that crafts the JSON controls the principal.
- outbox.EntryScan{...}.ToEntry() — rebuilds a sealed Entry from exported scan-target fields. A caller that fills EntryScan controls the principal.
If a business producer (cells/* or examples/*) could call either funnel, it would have a second path — alongside ctxkeys-forgery (CTXKEYS-PRINCIPAL-WRITE- CALLER-01) — to fabricate an Entry with a forged audit identity and Emit it, defeating the "NewEntry-from-ctx is the single injection trust boundary" claim. This archtest pins the callsite identity of both funnels to the sanctioned framework-infrastructure files: storage adapters (which reconstruct from already-persisted DB rows) and wire/consumer decoders (which decode inbound broker bytes). Neither funnel is reachable from a producer.
Architectural note (why no runtime guard on Emit): the relay and the consumer path never feed a reconstructed Entry back into Emitter.Emit — the relay MarshalEnvelopes the claimed Entry straight to Publisher.Publish, and the consumer dispatches the decoded Entry to a handler. So locking the two reconstruction callsites is sufficient; Emit needs no extra check.
AI-robust rating (charter §"Funnel 双向锁评级") ¶
- Downstream: HARD by archtest caller-allowlist. The callee is resolved via go/types (ResolvePackageRef / ResolveMethodCall / types.Info.Uses), so import aliases (kout "…/kernel/outbox"), dot-imports (bare-identifier UnmarshalEnvelope), and method-expression forms are all resolved to the same symbol — there is no "looks like but isn't" gap. The allowlist is bound to PLATFORM PACKAGE IDENTITY (isGoCellPlatformPkgPath), not a bare repo-relative path, so a consumer module that forges an allowlisted rel path (e.g. its own "runtime/eventbus/eventbus.go") is NOT exempt — the rule stays a true pure ban for external repos. Any callsite outside the allowlist fails in CI.
- Upstream: MEDIUM, and this is a GO-LANGUAGE CEILING, not a deferred TODO. Hard upstream would require the two funnels to be unreachable outside the sanctioned packages. They cannot be: reconstruction is inherently cross-package (adapters/postgres, runtime/eventbus, adapters/rabbitmq are all distinct packages from kernel/outbox), so Go visibility cannot express "only these packages may call an exported func/method". This is the same permanent ceiling documented for SPAN-SETATTR-HOLDER-SEAL (#851) and HEALTHZ-HOLDER-SEAL (#893 won't-do); tracked here as #1282. The downstream archtest is the enforcement; the ceiling is documented, not silently accepted.
Detection is REFERENCE-based, not call-based ¶
The scanner walks BOTH SelectorExpr and bare *ast.Ident references that go/types resolves to a funnel symbol — whether the reference is the callee of a call OR passed as a function/method value. This deliberately closes the "indirection through a function value" gap: a file that does `f := outbox.UnmarshalEnvelope; f(b)` references the symbol at the `outbox.UnmarshalEnvelope` SelectorExpr and is caught. The bare-Ident walk additionally closes the dot-import form (import . "…/kernel/outbox"; UnmarshalEnvelope(b)), which references the package func as a bare *ast.Ident — resolved via types.Info.Uses, alias/dot-import-proof. The mirror of scaffold_derived_forceoverwrite.go's dual SelectorExpr+Ident walk. (The (EntryScan).ToEntry method funnel is always a SelectorExpr — a method call on a receiver value — even under a dot-import of kernel/outbox, so only the package func has a bare-Ident form.)
Tool blind spots (charter §"强制盲区自检") ¶
- A bypass added in a //go:build-gated PRODUCTION file under a tag not in the default build context would be missed by the default-tags scan. The funnel callers today are all default-build; the integration-tagged code is _test.go (excluded from production load anyway). Mitigated by the default+tagged double scan when cfg.BuildTags is supplied. Documented.
- The anti-vacuity guard (every allowlisted file must reference its funnel ≥1×) lives in the _test.go dogfood (checkReconstructionAntiVacuity) — NOT in the importable Check (which returns only forward caller-allowlist violations). It proves the scanner actually resolves the real references (not a vacuous pass) AND forbids stale allowlist rot — a dead allowlist entry is a latent bypass slot, so an entry that no longer corresponds to a real reference fails the dogfood (charter "no silent carve-over / empty steady state").
External Cell repo semantics ¶
This rule is registered in StandardCellRules. The allowlisted infra files live in GoCell's own adapters/ and runtime/ packages, which are dependencies of an external Cell repo — not in the consumer's own module. Therefore in a clean external Cell repo there are zero references to either funnel in the scanned source, and the rule degrades to a PURE BAN (vacuous-green). That is intended: business / consumer code must never call UnmarshalEnvelope or EntryScan.ToEntry. importable non-test home; external repos run it via StandardCellRules; GoCell dogfoods it via the _test.go.
pg_repo_ambient_tx.go — importable PG-REPO-AMBIENT-TX-01 rule logic (#1640 M3 PR-9).
Non-test home for PG-REPO-AMBIENT-TX-01 scanner logic, so it can be compiled and run by an external Cell repository.
Dogfooded by tools/archtest/pg_repo_ambient_tx_test.go:
TestPGRepoAmbientTx → Report(t, "PG-REPO-AMBIENT-TX-01", CheckPGRepoAmbientTx(...)) TestPGRepoApprovedSealed → Report(t, "PG-REPO-APPROVED-SEALED", CheckPGRepoApprovedSealed(...))
Register status ¶
Not registered in StandardCellRules: this rule's allowlists and scan scope (postgres repo layout / pgrepoapproved package) are gocell-internal layout specific with no ConfigForExternalCell consumer-extension → vacuous/false-red externally.
prod_main_wiring_noop_reject.go — importable rule body for PROD-MAIN-WIRING-NOOP-REJECT-01. Lives in a non-test .go (like outbox_reconstruction_caller.go) so an external Cell repo can import and run it via StandardCellRules / RunStandardCellRules. The dogfood, detector reds, green fixture, anti-vacuity and matcher unit tests live in prod_main_wiring_test.go.
PROD-MAIN-WIRING-NOOP-REJECT-01 ¶
A production composition-root (main) package — those a consumer declares in cfg.ProductionMainPkgs — must not DIRECTLY construct a raw "noop / degraded" outbox event sink. Raw noop sinks silently drop events (or lose L2 atomicity); in a production binary that is data loss, not a missing feature. The five forbidden symbols are the complete raw-noop sink surface of kernel/outbox:
kernel/outbox.NoopWriter — Writer that validates then discards every entry kernel/outbox.NewNoopEmitter — Emitter backed by NoopWriter (discards) kernel/outbox.NewDirectEmitter — DirectEmitter ctor that bypasses ResolveEmitter kernel/outbox.DiscardPublisher — Publisher that logs+discards (the "noop publisher") kernel/outbox.DemoTxRunner — pass-through TxRunner (no real transaction)
All five implement (or back) cell.Nooper and are rejected at runtime by outbox.CheckNotNoop in DurabilityDurable mode. This rule is the STATIC, pre-Init counterpart: a composition root could mint one before any cell Init runs, where no pool/cell probe can see it.
Sanctioned — deliberately NOT forbidden (the rule pushes raw noop THROUGH these; banning them would break legitimate wiring). Split by altitude so a composition-root author and a cell author each see their own sanctioned set:
Composition-root (what a main package SHOULD call instead of a raw sink):
outbox.DemoCellEmitter() — sealed demo emitter funnel (sanctioned way to
inject a noop emitter into a cell Option)
outbox.DemoCellTxManager() — sealed demo tx-manager funnel
outbox.ResolveEmitter — durable-vs-direct emitter resolver
runtime/eventbus.New / InMemoryEventBus — the in-process DEMO publisher. It is
not a raw noop sink, so this rule does not forbid
it. The "in-memory publisher in postgres mode" gap
that option A of #1303 deliberately left open is
now CLOSED by #1940: postgres topology wires a real
broker via cellmodules/eventtransport.Resolve, and
cmd/corebundle is additionally barred from
direct-importing runtime/eventbus by depguard
COREBUNDLE-EVENTBUS-FUNNEL-01 (a transport-funnel
guard, orthogonal to this noop-sink ban; eventbus
stays legal in demo mains / examples). See ADR
202606131500 (#1940).
Cell-level (used inside a cell's Init, not a composition root — listed so the rule's relationship to them is unambiguous, NOT as main-package guidance):
outbox.ResolveCellEmitter — cell-level durable-vs-direct resolver outbox.NewDirectCellEmitter — intended L4 direct-publish-by-design production
Rule semantic: raw noop sinks must route through the sealed demo funnels or wire real infra.
Non-redundant failure domain (contract-fanout) ¶
Three existing guards touch noop outbox; none scans main-package wiring:
- outbox.CheckNotNoop — RUNTIME, inside a cell's Init, durable mode only.
- OUTGUARD-01 — METADATA, over cell.yaml durabilityMode.
- CELL-L2-INIT-CHECKNOTNOOP-CALLED-01 — archtest over a CELL package's Init.
This rule's domain is the COMPOSITION-ROOT (main) package, statically, before any cell Init.
# AI-robust rating (charter §"Funnel 双向锁评级"), same shape as OUTBOX-RECONSTRUCTION-CALLER-01
- Downstream HARD. The reference is resolved via go/types (ResolvePackageRef over both SelectorExpr and dot-import bare *ast.Ident), so import alias, dot-import, and function-value forms all resolve to the same symbol — no "looks-like-but-isn't" gap. There is NO allowlist (unlike reconstruction): within the scanned main pkgs this is a PURE BAN — zero exemption surface, zero allowlist-rot. The anti-vacuity self-test (TestProdMainWiringNoopReject_AntiVacuity_CorebundleInScope) proves the workspace Production scan reaches the separate cmd/corebundle module, so a 0-files-scanned vacuous-green cannot pass as a clean dogfood (a downstream Medium→Soft regression guard).
- Upstream MEDIUM — permanent Go ceiling, NOT a deferred TODO. Go visibility cannot forbid a main package from calling a PUBLIC func/type; the five symbols MUST stay public (tests / demos / examples legitimately construct them). Sealing them is unreachable without breaking those callers. The consumer's ProductionMainPkgs declaration completeness is also consumer-side (same ceiling as BuildTags). Same permanent-ceiling family as #851/#893/#1282.
- Charter single-grade floor = Medium (weakest leg governs).
强制盲区自检 (reverse self-test backs each claim) ¶
- import alias — CAUGHT (go/types). Backed by red_aliasimport fixture.
- dot-import bare ident — CAUGHT (ast.Ident walk). Backed by red_dotimport.
- function value (`f := outbox.NewNoopEmitter`) — CAUGHT (reference walk, not call-only). Backed by the NewDirectEmitter function-value line in red_qualified.
- cross-package call-graph transitivity / factory wrapping — NOT CAUGHT: a main pkg that calls a helper in ANOTHER (non-scanned) package which constructs the sink is invisible to this callsite scan. Permanent Go-language ceiling (a callsite scan cannot follow arbitrary cross-package call graphs; same family as reconstruction's upstream Medium). Mitigation: the established wiring goes through composition.NewSharedDeps / the sealed funnels, and a durable assembly still trips runtime CheckNotNoop. Documented, not silently accepted.
- //go:build-tagged production files under a non-default tag — covered by the default + cfg.BuildTags double scan (scanner.Canonical dedups the overlap).
External Cell repo semantics ¶
Registered in StandardCellRules. Opt-in by declaration: an external repo lists its composition roots in cfg.ProductionMainPkgs; an empty slice means "do not scan composition roots" (mirrors BuildTags) — no false positives on a repo that has not opted in. The forbidden symbols live in GoCell's kernel/outbox, imported as a dependency, so the go/types resolution works identically in a consumer module.
Importable rule body for PROJECTION-APPLY-HOOK-FUNNEL-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1302 / issue #1635) so external Cell repos can import and run it via StandardCellRules / RunStandardCellRules. The dogfood + RED/blind-spot reverse fixtures live in the non-test companion projection_apply_hook_funnel_test.go.
PROJECTION-APPLY-HOOK-FUNNEL-01 ¶
kernel/projection.Coordinator.Subscribe registers an Apply hook with the cell.Registrar (it calls reg.Subscribe internally). This is the per-projection wiring callsite; it must only appear in:
- kernel/projection/ itself (the method body calls reg.Subscribe internally)
- _test.go files (tests of the Coordinator API — explicitly allowed)
- the single sanctioned bootstrap projection drain file runtime/bootstrap/phases_projection.go — the ONE place that constructs a Coordinator from framework-owned deps and drives Coordinator.Subscribe.
Relocation (PR-04a, Option A — ADR §7 amendment 2026-05-31): the sanctioned callsite moved from the cellgen-generated cell_gen.go to the bootstrap drain. Reason: Coordinator.Subscribe must be fed framework-owned raw infrastructure (CheckpointStore / TxRunner / Cursor / ReplaySource), which cell code may never hold (sealed-marker architecture). So cellgen emits the record-only reg.RegisterProjection (guarded by PROJECTION-REGISTER-FUNNEL-01) into cell_gen.go, and bootstrap — which legally holds the raw deps — constructs the Coordinator and calls Subscribe. The Subscribe terminal is now a single hand-written kernel/runtime file under tight review, a TIGHTER sanctioned set than "any cell_gen.go".
Any other production callsite bypasses the drain funnel and constitutes a hand-rolled wiring that diverges from the single source of truth.
PR-04a status: genuinely-green active guard (NOT t.Skip). The only production callsite is runtime/bootstrap/phases_projection.go; any other fires.
External Cell repo semantics (this rule is registered in StandardCellRules) ¶
The sanctioned bootstrap drain (runtime/bootstrap/phases_projection.go) lives in the GoCell platform module, NOT in a consumer repo. The production-file exemptions are bound to platform package identity (isProjectionApplyHookAllowed → isGoCellPlatformPkgPath), so a consumer module CANNOT claim them by forging the kernel/projection/… or drain rel path — its package path is not under PlatformModulePath. The rule therefore degrades to a PURE BAN for external Cell repos: any Coordinator.Subscribe call in consumer code fires immediately. That is the intended semantics — consumers must declare projections via reg.RegisterProjection (cellgen-generated from slice.yaml) and never wire the Coordinator directly. A clean external repo has zero Coordinator.Subscribe calls (vacuous-green).
AI-robust grading (Funnel 双向锁评级) ¶
- Downstream: Medium archtest caller allowlist. Go has no type-system mechanism to restrict who calls a public method (Go visibility applies to packages, not callers), so this is the strongest achievable form for a method-call restriction. Archtest enforcement is CI fail-closed. The sanctioned set is a single named file, tighter than the prior "any cell_gen.go".
- Upstream: Medium — and permanently so (won't-do, gh #1372). A "cellgen-only sealed token" that would make a hand-written Subscribe call uncompilable is NOT expressible in Go: cellgen emits this call into the cell's OWN package (cell_gen.go sits beside the hand-written cell.go), and Go has no compile-time identity for "generated code". Any token constructor the generated file can call, a hand-written sibling in the same package can call too. Same permanent Go-language ceiling as Subscribe / RegisterWebhookReceiver and the holder-seal family #851 / #893 / #1282.
Because both sides are Medium and the upstream side cannot be Hard-ized, this is NOT a closed-loop Hard funnel and will not become one (gh #1372 closed won't-do). The raw-infra-stays-in-bootstrap property IS Hard (type system): cells hold sealed markers, never the CheckpointStore/TxRunner constructed in the drain — that is what makes Option A sound vs emitting NewCoordinator into generated cell code.
Blind spots (forms *types.Info / ResolveMethodCall cannot see) ¶
B1. Function-value / method-expression indirection: `f := coord.Subscribe; f(ctx, spec, id, apply)` — the CallExpr's Fun is an *ast.Ident resolving to a *types.Var, not a *types.Func, so ResolveMethodCall returns (nil, false) and the call is invisible to R1. Reverse self-check TestProjectionApplyHookFunnel01_ReverseBlindSpot_NoFuncValue asserts no production code holds a Coordinator.Subscribe function value.
B2. Wrapper method indirection: a wrapper struct that embeds *Coordinator and calls Subscribe inside its own method body — the wrapper's callsite IS caught (it's a direct method call on the embedded receiver), but the wrapper struct itself could be in a non-allowlisted package. Covered by the regular rule scan; not a blind spot.
B3. Dot-import: `import . "…/projection"` — ResolveMethodCall handles dot-import via *types.Info.Uses, so this form is caught. Not a blind spot.
ref: tools/archtest/healthz_invariants_test.go (HEALTHZ-WRITE-01/A2 caller-allowlist pattern) ref: docs/architecture/202605261620-adr-cqrs-projection-lifecycle-harness.md §3 PR-01
Package archtest resolve.go — façade helper re-exports for business archtest rules.
This file completes the façade contract: after Stage 1.5, all business archtest *_test.go files need only import "…/tools/archtest" — zero direct imports of internal/scanner, internal/typeseval, or x/tools/go/packages.
Deliberately excluded: loader symbols ¶
The six loader symbols (LoadPackages, SharedResolver, LoadProductionPackages, Resolver, ProductionResolver, EachFileInPackage) are intentionally NOT re-exported here. They are reachable only via archtest.Run with a typed scope (Run(t, Typed(...)) / Production(...) / Fixture(...) / StandaloneModule(...)), which wraps the SharedResolver cache and constructs a *Pass with *types.Package (not *packages.Package). This is the Hard defensive layer described in docs/architecture/202605141519-adr-archtest-pass-funnel.md: a business *_test.go that receives a *Pass cannot reach .Syntax or reconstruct a fresh packages.Load, so the INV-1 bug class (pairing AST nodes from one load with a *types.Info from a different load) is inexpressible at the compiler level.
Why helper re-exports are necessary (D2 rationale) ¶
Business archtest rules frequently need to:
- resolve a SelectorExpr / Ident to its (pkgPath, symbolName) pair across qualified / alias / dot-import forms — ResolvePackageRef;
- identify the *types.Func a method call resolves to — ResolveMethodCall;
- evaluate a cross-package constant string — EvaluateConstString;
- enumerate build-tag groups for multi-tag SharedResolver loops — FlatNonDefaultTags / KnownNonDefaultTags;
- extract a file's build constraint expression for 3-way evaluation under custom tag sets — ParseBuildConstraint.
Generated-path detection is NOT a free re-export: use the Pass.IsGenerated(f) method (#1037 removed the IsGeneratedRelPath free function).
Hand-rolling these patterns via raw go/types in each rule is error-prone (missed dot-import bare-Ident path, missed alias form, missed untyped const folding). The façade re-exports ensure all rules use the same INV-1-safe implementations that have been tested against the three import shapes in typeseval's own test suite.
PASS-FUNNEL-RESOLVE-01 (enforcement) ¶
The meta-archtest PASS-FUNNEL-RESOLVE-01 in pass_funnel_test.go bans business *_test.go files from calling the nine typeseval helpers or scanner.ImportBan directly. Files in passFunnelPermanentExempt (3 framework files) are permanently exempt; LegacyAllowlist deleted in Stage 4.
INVARIANT: RMQ-CHANNEL-DESTRUCTION-VIA-CONN-01
- INVARIANT: RMQ-CHANNEL-MAX-PER-CONN-01
- INVARIANT: RMQ-PUBLISHER-FAILURE-HANDLING-01
- INVARIANT: RMQ-PUBLISHER-RELEASES-CHANNEL-01
- INVARIANT: RMQ-STOPINTAKE-INFLIGHT-WAIT-01
rmq_invariants.go — importable RabbitMQ adapter rule logic.
This is the non-test home of the RMQ-* scanner helpers so they can be compiled by external Cell repositories through the CellRule pattern (Go never compiles a dependency's _test.go, so rule logic that external repos must run cannot live in a _test.go file). GoCell's own TestRMQ* functions (rmq_invariants_test.go) call the same shared helpers — single source, no parallel rule body.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests.
Platform-symbol paths are anchored to PlatformModulePath so a module rename updates exactly one place and no bare literal appears here. The scan SCOPE (./adapters/rabbitmq/...) is the running module's own adapter package; this is intentionally gocell-internal-layout and dogfood-only — in an external repo with no such adapter tree the rule does not get a clean pass.
RMQ-CHANNEL-DESTRUCTION-VIA-CONN-01 ¶
Enforces that every AMQPChannel destruction site in adapters/rabbitmq/ goes through Connection.CloseEphemeralChannel. Direct ch.Close() calls outside of CloseEphemeralChannel or waitAndClose bypass the inUseChannels.Add(-1) decrement and permanently leak MaxChannelsPerConn slots, causing spurious ERR_ADAPTER_AMQP_CHANNEL_MAX_EXCEEDED false-positives after enough reconnect cycles or subscription teardowns.
AI-robust grading:
- Downstream: Medium — go/types receiver classification via types.Implements; naming-immune (renaming the receiver var does not weaken the gate); allowlisted funcs are string-name matched (Soft residual — renaming CloseEphemeralChannel bypasses without scanner update; tracked as known blind spot: function-name allowlist is not type-qualified).
- Upstream: N/A — this is a single-package restriction rule, not a funnel-type constraint.
RMQ-CHANNEL-MAX-PER-CONN-01 ¶
Enforces that adapters/rabbitmq.Config declares MaxChannelsPerConn with a setDefaults guard (`<= 0`) and AcquireChannel references the inUseChannels counter. Without the cap, pool-miss paths can silently exceed broker channel_max and cause a connection-level shutdown.
AI-robust grading (all sub-rules: A/B/C):
- Medium — pure AST struct-field and selector scan; not type-qualified. Hard upgrade path: generate a typed golden that locks the Config struct field presence and setDefaults assignment form.
RMQ-PUBLISHER-FAILURE-HANDLING-01 ¶
Enforces that Publisher.Publish in adapters/rabbitmq/publisher.go:
- references ErrAdapterAMQPNack (distinct from ErrAdapterAMQPConfirmTimeout)
- calls slog.Warn at least 3 times (NACK / timeout / confirmCh-closed)
- calls RecordPublishFailure at least once
- pairs every error-returning if-block with a RecordPublishFailure call
AI-robust grading:
- Medium — AST identifier and call-expression scan; string-name matching (renaming slog.Warn or RecordPublishFailure bypasses without scanner update). The -D sub-rule (AllReturnsMustRecord) uses a block-recursive walker that covers ForStmt / RangeStmt / SwitchStmt / TypeSwitchStmt / SelectStmt containers (Wave 4 extension). Blind spot: the string-name gate means a renamed callee bypasses silently (documented in RED fixture).
RMQ-PUBLISHER-RELEASES-CHANNEL-01 ¶
Enforces that Publisher.Publish acquires a channel via AcquireChannel and pairs it with a deferred CloseEphemeralChannel or ReleaseChannel call. Without this pairing, each Publish leaks one inUseChannels slot; after MaxChannelsPerConn (=256) publishes all subsequent calls fail with ErrAdapterAMQPChannelMaxExceeded.
AI-robust grading:
- Medium — AST DeferStmt + SelectorExpr name scan; not type-qualified.
RMQ-STOPINTAKE-INFLIGHT-WAIT-01 ¶
Enforces that Subscriber.StopIntake waits for in-flight processDelivery goroutines before returning, and that drainRemaining uses a detached context (context.WithoutCancel) with no bare ctx.Done case. Also enforces that StopIntake does NOT call localWg.Wait() directly (Add-after-Wait race).
AI-robust grading:
- Medium — AST call-expression and CommClause name scan; not type-qualified.
Importable rule body for SCAFFOLD-DERIVED-FORCEOVERWRITE-01. Migrated from the legacy _test.go form to a non-test .go (M3 #1302) so external Cell repos can import and run it via StandardCellRules / RunStandardCellRules. The dogfood + RED-fixture precision gate live in scaffold_derived_forceoverwrite_test.go.
SCAFFOLD-DERIVED-FORCEOVERWRITE-01 ¶
Every production reference to pathsafe.DerivedOverwrite must occur inside tools/codegen/cellgen/stage_render.go::planDerivedArtifact — the sole site that restores the governance.IsGoCellGenerated overwrite gate. planDerivedArtifact is the SOLE production caller of the typed pathsafe.DerivedOverwrite constructor in the entire repository.
AI-robust: Hard (compile-time + archtest funnel) ¶
- Upstream Hard (compile-time): pkg/pathsafe.PlannedFile.forceOverwrite is package-private and the only public path that produces a force-overwrite PlannedFile is pathsafe.DerivedOverwrite. A composite literal outside the pathsafe package cannot set forceOverwrite — the Go compiler rejects the field-name reference (PATHSAFE-FORCEOVERWRITE-TYPED-CTOR-01).
- Downstream Hard (archtest): every CallExpr that resolves via *types.Info to pkg/pathsafe.DerivedOverwrite in any production package must occur inside planDerivedArtifact. Tests are excluded so fixture code can still exercise DerivedOverwrite directly.
Recognition: type-aware (Ident + SelectorExpr unified) ¶
pathsafe.DerivedOverwrite(...) parses as *ast.SelectorExpr whose .Sel is the function name; alias imports keep the same shape. Dot-import collapses to a bare *ast.Ident. Both are resolved through *types.Info.Uses to the underlying *types.Func by resolveDerivedOverwriteIdent.
Blind spots (declared per ai-robust §载体决策原则) ¶
- Indirect call through a function-typed variable: covered by the reverse scan (derivedIndirectViolations) which rejects any non-CallExpr reference.
- A future caller written as a direct CallExpr could pass the type check but produce content that bypasses the upstream governance gate planDerivedArtifact runs — the archtest accepts planDerivedArtifact as the trusted gatekeeper.
Scan scope is the running module (Production → findModuleRoot), supplied by the driver; cfg.BuildTags adds tag-gated production files. The platform symbol path is derived from PlatformModulePath (ARCHTEST-MODULE-PATH-FUNNEL-01).
Importable rule body for SCAFFOLD-LISTENER-MARKER-TYPED-CONST-01. Migrated from the deleted scaffold_bundle_invariants_test.go (M3 #1302). The dogfood + template precision gate live in scaffold_listener_marker_invariants_test.go.
SCAFFOLD-LISTENER-MARKER-TYPED-CONST-01 ¶
cellgen.ListenerMarker must exist as an exported string const carrying the canonical K#05 marker literal, and the scaffold-cell template must reference it via {{.ListenerMarker}} rather than hand-typing the literal. This keeps the marker→cell.yaml drift guard (MARKERGEN-DRIFT-VERIFY-01) anchored to a single typed-const source instead of a free-floating string.
AI-robust: Medium (typed const + typeseval cross-validation) ¶
The const half resolves cellgen.ListenerMarker via the typed Run façade and compares its constant.StringVal; the template half is a pure string check (checkListenerTemplate) that the precision gate table-tests directly.
Scope: gocell-internal-layout ¶
It scans the platform's own tools/codegen/cellgen package + the templates/scaffold-cell.tmpl asset, neither of which exists in an external Cell repo — so it is NOT enrolled in StandardCellRules (it would be vacuous-green there). It gets the unified PlatformModulePath parameterization (ARCHTEST-MODULE-PATH-FUNNEL-01) + fork-safety only.
Package archtest is the single-entry façade for GoCell architectural tests. Authors write rules as Rule closures and dispatch them through the single Run entry point, selecting the mode with a sealed RunScope constructor: AST (AST-only, no go/types), Typed (with go/types info), Production (typed, generated/ excluded), Fixture (typed, archtest_fixture-tagged), or StandaloneModule (typed, standalone fixture module). Direct access to the underlying internal/scanner and internal/typeseval primitives is forbidden by the PASS-FUNNEL-* meta-archtest and the depguard archtest-no-direct-packages-load rule.
Hard-line defense layering:
- Pass.Pkg is *types.Package (go/types stdlib), NOT *packages.Package (golang.org/x/tools/go/packages). Authors that receive a *Pass cannot reach .Syntax + an out-of-scope *types.Info pair — the INV-1 root cause class becomes inexpressible at the compile level.
- depguard rule archtest-no-direct-packages-load bans tools/archtest/*_test.go from importing packages, internal/scanner, internal/typeseval.
- Meta-archtest PASS-FUNNEL-EACHFILE-01 / LOADPACKAGES-01 / PACKAGES-IMPORT-01 re-detect any bypass via type-aware *types.Info resolution.
See docs/architecture/202605141519-adr-archtest-pass-funnel.md for the full rationale and migration path.
test_polling_external_reason_literal.go — importable TEST-POLLING-EXTERNAL-REASON-LITERAL-01 rule logic (#1640 M3 PR-9).
Non-test home for TEST-POLLING-EXTERNAL-REASON-LITERAL-01 scanner logic, so it can be compiled and run by an external Cell repository.
Dogfooded by tools/archtest/test_polling_external_reason_literal_test.go:
TestExternalReasonLiteral → Report(t, "TEST-POLLING-EXTERNAL-REASON-LITERAL-01", CheckTestwaitExternalReasonLiteral(...))
Register status ¶
Not registered in StandardCellRules: this rule's scan scope (testwait package) is gocell-internal layout specific with no ConfigForExternalCell consumer-extension → vacuous/false-red externally.
Importable rule body for TLS-TEST-MATERIAL-FUNNEL-01. Non-test .go file so the dogfood + RED-fixture precision gate (tls_test_material_funnel_test.go) share a single scanner — no parallel rule body. The sanctioned set is matched by workspace-relative directory prefix (not import path), which is robust against go/packages test-variant path mangling (foo / foo_test / foo.test) and needs no module-path literal.
TLS-TEST-MATERIAL-FUNNEL-01 ¶
crypto/x509.CreateCertificate — the act of minting an X.509 certificate — may only be called from a closed set of sanctioned packages:
- framework/runtime/http/tlsutil/tlsutiltest — the single sanctioned source of self-signed mTLS cert material for tests (issue #2287). Before it, 9 near-equivalent cert-gen helpers (8 "self-signed CA + cell leaf" chains + 1 CA-only pool fixture) were copy-pasted across the test tree (the mqtt copy even carried a "Ported from adapters/grpc/cert_test.go" comment). cert-gen detail — curve, validity, EKU, SAN — is sensitive to mTLS correctness, so a bug in one copy is invisible to the others; centralizing the mint makes the shape a single source and this funnel keeps it centralized.
- framework/runtime/certsigning/request_test.go, framework/runtime/certlifecycle/testsupport_test.go — the CSR→certificate issuance pipeline tests. They fabricate certificates as system-under-test INPUT (the cert is what the signing/lifecycle code consumes), not reusable cell-identity fixtures, so routing them through tlsutiltest would be circular. These are sanctioned as EXACT FILES (not whole package): the production code in those packages does not mint, so a whole-package allowlist would fail-open a future production CreateCertificate (#2414 F1).
- adapters/softca — the production soft-CA implementation. It is the real certificate authority (not test material); the scan covers production packages too, so it is allowlisted here.
Every other caller — production or test — is a violation.
AI-robust: Medium (downstream type-aware scan; no Hard ceiling) ¶
This is a Medium funnel, NOT Hard, and there is no low-cost Hard path: crypto/x509.CreateCertificate is a stdlib free function, so there is no sealed type or unexported field that could make an unsanctioned call unrepresentable at compile time (contrast CELLTLS-MATERIAL-FUNNEL-01, whose upstream is Hard because tlsutil.ClientIdentity is sealed). The downstream scan is type-aware: ResolvePackageRef resolves each callee to its owning package via go/types, so import aliases (x509alias.CreateCertificate) do not defeat it.
Not registered in StandardCellRules: the sanctioned packages are GoCell- internal; an external Cell repo has no such files, so the allowlist would never match and the rule would degrade to a pure ban that false-reds any repo legitimately minting test certs from its own helper. Same disposition as CELLTLS-MATERIAL-FUNNEL-01.
Scope ¶
Scanned via Production(Tests:true) under both the default build context and FlatNonDefaultTags() (so integration / mqtt_tls-gated test files are covered), union-deduped. generated/ is excluded by Production; the archtest_fixture tag is deliberately absent from FlatNonDefaultTags (#944), so the RED fixture is never compiled into the dogfood.
CI lane (nightly-only, like CELLTLS-MATERIAL-FUNNEL-01) ¶
This rule runs Production(Tests:true) + FlatNonDefaultTags() — a heavy whole-tree packages.Load (two passes) — so it belongs to the nightly archtest bucket (verify-archtest.sh / `go test -tags=archtest ./tools/archtest/...`), NOT the cheap PR-time fast lane (hack/verify-archtest-invariants.sh, reserved for non-packages.Load invariants). Same disposition + cost class as CELLTLS-MATERIAL-FUNNEL-01.
Blind spots (BS) ¶
- BS-1 Reverse build constraints (//go:build !tag): files excluded from a -tags=...,tag,... union load are not scanned. No such cert-gen file exists today; the nil-tags companion pass covers the default context.
- BS-2 CreateCertificateRequest (CSR creation) is intentionally NOT banned — it is a distinct concern used legitimately by the pipeline tests and is not cell mTLS cert material.
- BS-3 Function-value indirection (var f = x509.CreateCertificate; f(...)) resolves to *types.Var (ok=false) and is not flagged. Accepted: no sanctioned or migrated caller does this.
- BS-4 Reflection construction: out of scope per ai-robust.md §3.
- BS-5 Rel-prefix coupling: tlsTestMaterialSanctionedDirs matches Pass.Rel's framework-stripped, module-relative form (runtime/… for the framework module, <module>/… otherwise). If newPackageRel / stripFrameworkPrefix changes, this list AND TestIsTLSTestMaterialSanctioned (which feeds pre-stripped literals) must be updated together. A strip-behavior change would silently false-RED (a sanctioned caller stops matching) — fail-safe, not fail-open, so it surfaces as CI red rather than a missed violation.
INVARIANT: WEBHOOK-HMAC-FUNNEL-01
webhook_hmac_funnel.go — importable webhook HMAC signing funnel rule logic.
WEBHOOK-HMAC-FUNNEL-01 — kernel/webhook HMAC signing funnel (KERNEL-WEBHOOK-01).
- A1 (downstream Hard): crypto/hmac.New has exactly one callsite in the kernel/webhook package — the computeMAC function. The check is FUNCTION-IDENTITY level via go/types FullName (file-independent; #1493): an hmac.New whose enclosing func FullName != computeMAC's fails, and a same-named function in another package cannot inherit the allowance (mirrors WEBHOOK-SSRF-GUARD-01/A2). Detection: ResolvePackageRef(callee) == crypto/hmac.New AND ResolveEnclosingFunc(call).FullName() != hmacSanctionedComputeMACFunc.
- A2 (downstream Hard): signature comparison must use crypto/hmac.Equal or crypto/subtle.ConstantTimeCompare. The non-constant-time comparison callees bytes.Equal / bytes.Compare / slices.Equal / reflect.DeepEqual are banned in the package. This makes the constant-time invariant an AST lock rather than a flaky timing test. Detection: ResolvePackageRef(callee) ∈ the banned set.
- A3 (upstream Hard external / Medium internal): the Signer and Verifier interfaces each carry an unexported sealed() marker method, so package-external implementations are a compile error (Hard external). Package-internal new holders are NOT blocked by sealing — and contrary to a prior overclaim, A1 does NOT transitively cover them: A1 locks only crypto/hmac.New, not reuse of the package-level computeMAC helper, so a rogue in-package struct could call computeMAC and produce a valid signature without its own hmac.New. That internal axis is instead covered by A4 (computeMAC caller-allowlist, Medium). True type-system Hard for the internal axis is a PERMANENT Go ceiling (in-package code can always declare sealed(), read Source.secret, and call computeMAC) — same family as #851 / #893 / #1282 / #1375; #1243 is relabeled won't-do and named here as that ceiling's tracker. Detection: the Signer/Verifier interface type decls must contain an unexported method.
- A4 (Medium, internal axis; #1243): every USE of the package-internal computeMAC symbol — direct call, parenthesized call, OR function-value capture (`macFn := computeMAC`; the bypass review #1733 F4 flagged) — must have an enclosing func whose go/types FullName ∈ {hmacSigner.Sign, hmacVerifier.Verify}. This is the actual enforcement of "only the sanctioned signer/verifier may compute a MAC", closing the A3 internal axis at Medium (Hard is the permanent ceiling above). Detection: walk every ident, match info.Uses FullName == computeMAC's, require enclosing FullName ∈ allowlist (the computeMAC definition is in info.Defs, not info.Uses, so it never fires).
Blind spots (ai-robust 强制反向自检; each has a reverse self-test in the _test.go):
B-A1/A2 — rule-logic regression: a reverse fixture module (testdata/webhook_hmac_violate) calls hmac.New outside computeMAC (both outside signer.go and inside signer.go in a non-computeMAC func) and the banned comparison callees; TestWebhookHMACFunnel_ReverseFixture asserts A1/A2 fire on each form. B-A4 — rule-logic regression (computeMAC is unexported, so no standalone-module RED fixture can call it): TestWebhookHMACFunnel_ComputeMACCallerAntiVacuity re-runs the A4 scan over real production with an EMPTY allowlist and asserts it fires on the ≥2 real computeMAC callers (Sign + Verify) — proving the scan detects computeMAC calls and the allowlist is what suppresses them (non-vacuous). B6 — Source.Secret leak: slog of the raw unexported secret field would leak it (Source.LogValue + slog.LogValuer covers slog.Any of a whole Source, but not slog of src.secret directly). scanWebhookSecretSlog covers BOTH the package-function form (slog.Info(...)) and the method form (logger.Info(...)). TestWebhookFunnel_NoRawSecretSlog asserts no such call in the package references a `.secret` selector. B7 — non-AST-detectable constant-time bypass: comparing the base64 signature STRINGS with `==` (e.g. expectedB64 == presentedB64) is non-constant-time but is a plain *ast.BinaryExpr with no resolvable callee, so the A2 callee scan cannot see it. Detecting it would need type-level data-flow analysis. Bounded response: production matchAnySignature compares raw MAC bytes via hmac.Equal (A2-clean), and this blind spot is documented here so a future reviewer knows the AST scan does not cover string-`==`.
This is the non-test home of the WEBHOOK-HMAC-FUNNEL-01 scanner helpers so they can be compiled by external Cell repositories through the CellRule pattern (Go never compiles a dependency's _test.go, so rule logic that external repos must run cannot live in a _test.go file). GoCell's own TestWebhookHMACFunnel function (webhook_hmac_funnel_test.go) calls the same shared helpers — single source, no parallel rule body.
register=no — gocell-internal-layout (scans kernel/webhook), NOT in StandardCellRules() and NOT promised to run externally — this dogfood-only rule targets a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestWebhookHMACFunnel).
Platform-symbol paths are anchored to PlatformModulePath so a module rename updates exactly one place and no bare literal appears here.
ref: docs/architecture/202605291200-adr-webhook-signing-algorithm.md ref: tools/archtest/healthz_invariants_test.go (callsite-allowlist template)
Index ¶
- Constants
- func BuildContextPredicate(extraTags ...string) func(string) bool
- func EachContentFile(t *testing.T, s Scope, suffixes []string, fn func(*testing.T, ContentContext))
- func EachInChildren[S any, N interface{ ... }](root ast.Node, fn func(N))
- func EachInSubtree[S any, N interface{ ... }](root ast.Node, fn func(N))
- func EachInSubtreeStopAt[S any, N interface{ ... }](root ast.Node, stopAt func(ast.Node) bool, fn func(N))
- func EvaluateConstString(info *types.Info, expr ast.Expr) (string, bool)
- func FindFirstChild[S any, N interface{ ... }](root ast.Node, predicate func(N) bool) (N, bool)
- func FindFirstInSubtree[S any, N interface{ ... }](root ast.Node, predicate func(N) bool) (N, bool)
- func FindFirstInSubtreeStopAt[S any, N interface{ ... }](root ast.Node, stopAt func(ast.Node) bool, predicate func(N) bool) (N, bool)
- func FlatNonDefaultTags() []string
- func HasReceiver(fn *ast.FuncDecl, typeName string) bool
- func IsCallToPkgFunc(info *types.Info, call *ast.CallExpr, pkgPath, name string) bool
- func KnownNonDefaultTags() [][]string
- func ParseBuildConstraint(filePath string) (constraint.Expr, error)
- func ReceiverTypeName(expr ast.Expr) string
- func Report(t *testing.T, ruleID string, diags []Diagnostic)
- func ResolveEnclosingFunc(info *types.Info, file *ast.File, node ast.Node) (*types.Func, bool)
- func ResolveMethodCall(info *types.Info, sel *ast.SelectorExpr) (*types.Func, bool)
- func ResolvePackageRef(info *types.Info, expr ast.Expr) (pkgPath, name string, ok bool)
- func RunStandardCellRules(t *testing.T, cfg ConfigForExternalCell)
- func StringLitValue(lit *ast.BasicLit) (string, bool)
- func WalkFuncDecls(p *Pass, fn func(ctx FuncDeclContext))
- func WalkFuncDeclsAST(files []*ast.File, fset *token.FileSet, rel func(*ast.File) string, ...)
- type CellRule
- type ConfigForExternalCell
- type ContentContext
- type CrossModuleViolation
- type Diagnostic
- func Canonical(diags []Diagnostic) []Diagnostic
- func CheckAdapterReturnsDeclaredTypes(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAfterCommitHookPureTransient(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckAuditHashInputFrozenA2(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthAuthtestBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthKeystestBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthPlanCellsMustNotConstructAuthPlans(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthPlanNoCellPolicyTypeUsage(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthPlanNoLegacyPolicySelectorExpressions(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthPlanNoLegacyPolicyStringLiterals(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckAuthzMutationApplyFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckBcryptCostFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCASProtocolCompositionRoot01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckCapabilityProviderFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCellTLSMaterialFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCelltestImportBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCelltestImportScope(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCelltransportSelectFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCertdepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckClockPositionalInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCompositionNewForRoleFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckContractPathQueryCoverage01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckContractPathQueryParamNameLiteral01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckContracttestLoadByIDLiteral01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCredentialAuthorityAssertFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckCredentialInvalidateApplierCanonical01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckCredentialInvalidateFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckCredentialInvalidateUpstreamCaller01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckCtxkeysPrincipalWriteCaller01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckDeadCodeCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckDeadContractCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckDetailsSealedFieldFrozen01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckDistlockLockNotContext01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckDomainAuthzFieldPrivate01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckEmitDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckErrcodeKindLiteralBanned(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckErrcodeMessageConstLiteral(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckErrorFirstAPI01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckErrorFirstTypedNil01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckEvalPredicateCentralization01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckExportedErrorNew(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckFenceTokenMintFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckFixtureCellIDTypedBuilder(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGenerateRSAKeypairFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGovernanceEmitterConstructorsNeverFuncValue(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGovernanceRuleCodeConstSingleSource(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGovernanceRuleCodeDetectBinding(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGovernanceRuleErrorFixField(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckGovernanceRulesRegistrationGuard(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckHandlerDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckImplDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckInsecureSkipVerifyLiteral(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckKernelCellDoesNotImportRuntime(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckKernelCellRegistrarDefinedHere(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckKernelClockLeafFallback(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckKernelClockResetRelativeProd(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckMQTTClientIDNamespace(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckMQTTConfigSeal(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckMQTTReasonNameRedaction(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckMQTTTopicNamespace(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckMetadatatestImportScope(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckNoDeletedAuthSymbols01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckNotFoundTestStrict(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckOutboxHandleResultFactoryPreferred01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckOutboxReconstructionCaller01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckOutboxTopicFailopen01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckPGRepoAmbientTx(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckPGRepoApprovedSealed(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckPanicRegistered(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckParserMatcherExamplesSymmetry01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckProdClockInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckProdMainWiringNoopReject(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckProjectionApplyHookFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckProjectionEventJournalAppendCaller01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRMQChannelDestructionViaConn(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRMQChannelMaxPerConn(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRMQPublisherFailureHandling(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRMQPublisherReleasesChannel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRMQStopIntakeInflightWait(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckReconstituteUserCallerAllowlist01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckRefreshAmbientTX01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckRefreshCrossStoreTX01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckRefreshInvalidIndexSingleSource01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckRefreshRevokeUserFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckReplaydepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRequiredDepNilGuardA2(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRequiredDepNilGuardA3(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckRequiredDepNilGuardA4(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckSafeIDUpstreamFunnelHard01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckSafeIDWireMessageUsage01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaConstructorNilGuard(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaCoordinatorNoHeartbeatLoop(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaDriveBehindLeaderGate(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaExecutorRandInjected(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaJournalHolderSeal(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaMetricLabelValuesFrozen(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaProjectionDepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckSagaSlogInstanceFieldsCaller(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaStepCompensatePure(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaStepRunOutsideTx(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaTailerCheckpointAdvancerCaller(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaTailerDeadLetterWriter(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckScaffoldDerivedForceOverwrite(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckScaffoldListenerMarkerTypedConst(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSecurityDefaults(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckSeedRoleIface01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckServiceownedHandlerOwnerCheck01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckSessionProtocolCompositionRoot01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSessionrefreshNoSessionCreate01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSvctokenCallerCellRequired01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckTLSTestMaterialFunnel(t *testing.T) []Diagnostic
- func CheckTagGroupLoopForbidsTypedRun(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckTestwaitExternalReasonLiteral(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckUserAuthzEpochBumpFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckWebhookHMACFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckYAMLQuoteFunnel(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func Run(t testing.TB, scope RunScope, rule Rule) []Diagnostic
- type FixtureOpts
- type FuncDeclContext
- type ImportBan
- type Pass
- type Rule
- type RunScope
- type RuntimeScopeConfig
- func (c RuntimeScopeConfig) FrameworkModulePath() string
- func (c RuntimeScopeConfig) PlatformCellScanDirs() []string
- func (c RuntimeScopeConfig) PlatformCellsModulePath() string
- func (c RuntimeScopeConfig) PlatformModulePath() string
- func (c RuntimeScopeConfig) TargetModulePath() string
- func (c RuntimeScopeConfig) WorkspaceRoot() string
- type Scope
- type ScopeOption
- type TypedOpts
Constants ¶
const PlatformCellsDir = "corecells"
PlatformCellsDir is the repo-relative top-level directory and on-disk scan root that holds GoCell's own platform cells (accesscore / auditcore / configcore). Since the go.work P5 split (#1560) those cells live in their own nested module rooted at this directory, NOT under the repo root. It is the ONE sanctioned site for the "corecells" string literal across archtest: every cell-scan helper and layer/classification rule that needs the platform-cells dir derives it from here (or from PlatformCellsModulePath) so a future rename touches exactly one place.
This is the platform-MODULE dir, distinct from the framework-general "cells/" convention (project-local cells in consumer repos + GoCell's own examples/*/cells/): those are NOT this token and must not be rewritten to it.
const PlatformCellsModulePath = PlatformModulePath + "/" + PlatformCellsDir
PlatformCellsModulePath is the Go module path of GoCell's platform-cells module. Derived from PlatformModulePath (never a bare literal) so ARCHTEST-MODULE-PATH-FUNNEL-01 proves no rule reintroduces the hardcoded path; layer classification and import-path golden key off this value.
const PlatformFrameworkModulePath = PlatformModulePath + "/framework"
PlatformFrameworkModulePath is the Go module path of the GoCell FRAMEWORK module (kernel/runtime/pkg), separated out of the repo root by the #1565 split. It is the ONE sanctioned base for framework-internal symbol paths: every rule that resolves a kernel/runtime/pkg package derives it as PlatformFrameworkModulePath+"/kernel/…" (etc.), so the funnel stays single-source.
Derived from PlatformModulePath (never a bare literal): the "+/framework" fragment is not itself a platform value, and ARCHTEST-MODULE-PATH-FUNNEL-01's reconstruction detector exempts a "+" chain whose provenance reaches the sanctioned PlatformModulePath const — directly here, and transitively through every PlatformFrameworkModulePath+"/x" derivation. So this const, and all paths built on it, are funnel-clean.
const PlatformModulePath = "github.com/ghbvf/gocell"
PlatformModulePath is the GoCell org/repo path prefix shared by EVERY GoCell module: the framework module (PlatformFrameworkModulePath) plus all siblings — adapters/*, cmd/*, examples/*, tools, generated, corecells, tests. Since the framework split (#1565) the framework's own go.mod is github.com/ghbvf/gocell/ framework, so this value is no longer any single module's path; it is the common ancestor used to (a) classify a path as "inside GoCell" and (b) compose SIBLING-module symbol paths (PlatformModulePath+"/adapters/…", "/tools/…", "/corecells", …). Framework-internal symbol paths (kernel/runtime/pkg) are composed from PlatformFrameworkModulePath instead.
It is the ONE sanctioned site for the "github.com/ghbvf/gocell" string literal in this package: every rule that resolves a GoCell symbol path must derive it from PlatformModulePath (or PlatformFrameworkModulePath, itself derived) rather than hardcoding the literal, so a module rename / /v2 bump updates exactly one place and the ratchet meta-archtest ARCHTEST-MODULE-PATH-FUNNEL-01 can prove no rule reintroduces a bare literal. It is NOT the scan target: the module being analyzed is supplied by the driver (the Run typed scopes Typed/Production → findModuleRoot), which resolves the consumer's own go.mod for external repos.
Variables ¶
This section is empty.
Functions ¶
func BuildContextPredicate ¶
BuildContextPredicate returns a tag predicate suitable for constraint.Expr.Eval. It returns true for any tag the Go toolchain sets implicitly under a standard CI context, plus any extraTags supplied by the caller.
Use this when Pass.IsFileInScope is insufficient — e.g. when you need to evaluate a build constraint under a custom tag set such as "integration". Pass.IsFileInScope uses the default (no extra tags) predicate; if you need custom extra tags, call BuildContextPredicate("integration") and evaluate the constraint expression directly via archtest.ParseBuildConstraint:
expr, err := archtest.ParseBuildConstraint(path)
if err != nil || expr == nil { ... }
withTag := expr.Eval(archtest.BuildContextPredicate("integration"))
withoutTag := expr.Eval(archtest.BuildContextPredicate())
Thin delegation to typeseval.BuildContextPredicate. See that function's godoc for the full implicit-defaults catalog (GOOS/GOARCH/cgo/unix/gc/go1.X).
func EachContentFile ¶
EachContentFile iterates every file in scope whose path ends in any of suffixes (case-sensitive, must include the dot). Validation / walk / read errors fail-loud via t.Fatalf; fn is invoked per file with the bytes. Calling t.Errorf inside fn does not stop iteration.
Wrapper around scanner.EachContentFile.
func EachInChildren ¶
EachInChildren iterates only the DIRECT children of root (depth = 1). Use for "container's immediate elements" semantics (KeyValueExpr from a CompositeLit, CaseClause from SwitchStmt.Body, etc.). See EachInSubtree for the recursive variant.
Wrapper around scanner.EachInChildren.
func EachInSubtree ¶
EachInSubtree iterates every node of kind N in the sub-tree rooted at root (preorder, recursive). N is constrained to a concrete pointer type via `interface { *S; ast.Node }`, so callers write
archtest.EachInSubtree[ast.CallExpr](pass.Files[0], func(call *ast.CallExpr) { ... })
and Go's type inference fills N=*ast.CallExpr from S=ast.CallExpr. Wrong N (e.g. an interface like ast.Expr) is a compile-time error.
See EachInChildren for depth-1 traversal. The depth choice is a compile- time API selection (different function names express different semantics) rather than a runtime parameter.
Do NOT instantiate this (or EachInSubtreeStopAt) with ast.FuncDecl: a *ast.FuncDecl is always a direct *ast.File child (Go forbids nested func declarations — nested funcs are *ast.FuncLit), so the recursive descent is pure waste and the wrong depth semantic. Use EachInChildrenast.FuncDecl. Enforced by WALK-DEPTH-API-EACHCHILDREN-01 (walk_depth_funcdecl_children_test.go).
Wrapper around scanner.EachInSubtree — the only call path to scanner for archtest authors. Pure delegation, no behavior change.
func EachInSubtreeStopAt ¶
func EachInSubtreeStopAt[S any, N interface { *S ast.Node }](root ast.Node, stopAt func(ast.Node) bool, fn func(N))
EachInSubtreeStopAt traverses root's subtree like EachInSubtree, invoking fn for each *N encountered, but stops descending into any non-root node for which stopAt returns true. The boundary node itself is NOT visited as N even if its type matches. Third depth-semantic member of the typed-function-choice Hard template, alongside EachInSubtree / EachInChildren (see ai-robust.md §"Hard 范本目录" #1).
Wrapper around scanner.EachInSubtreeStopAt.
func EvaluateConstString ¶
EvaluateConstString returns the compile-time string constant value of expr, or ("", false) when expr is not a constant string.
Thin delegation to typeseval.EvaluateConstString. Covers BasicLit / Ident / SelectorExpr / BinaryExpr via go/types constant folding.
info must come from the same packages.Load result that produced expr.
func FindFirstChild ¶
func FindFirstChild[S any, N interface { *S ast.Node }](root ast.Node, predicate func(N) bool) (N, bool)
FindFirstChild scans the DIRECT children of root and returns the first node of kind N satisfying predicate. ok=false when no child matches.
Compared to the manual `EachInChildren + done sentinel` idiom, FindFirstChild internalizes the early-return state: there is no caller-held flag, the wrong N is a compile error (interface{*S; ast.Node}), and the find-first semantic is encoded in the function name itself. This is the only allowed depth-1 early-return shape in archtest rules — enforced by SCANNER-FRAMEWORK-USAGE-02.
Wrapper around scanner.FindFirstChild — the only call path to scanner for archtest authors. Pure delegation, no behavior change. After 040 Stage 4 seals internal/scanner, this façade is the only reachable path.
func FindFirstInSubtree ¶
func FindFirstInSubtree[S any, N interface { *S ast.Node }](root ast.Node, predicate func(N) bool) (N, bool)
FindFirstInSubtree walks root's entire subtree (preorder, recursive) and returns the first node of kind N satisfying predicate. ok=false when no node matches. Subtree-depth twin of FindFirstChild; the depth choice is a typed function-name selection per ai-robust.md AI-robust Hard 范本 #1 "typed function choice for walk depth".
Compared to the manual `EachInSubtree + closure sentinel` idiom, FindFirstInSubtree internalizes the early-return state: no caller-held flag, wrong N is a compile error (interface{*S; ast.Node}), find-first semantic encoded in the function name. Root IS included in the search (mirrors scanner.EachInSubtree preorder semantics), contrasting FindFirstChild which excludes root. This is the only allowed subtree-depth early-return shape in archtest rules — the closure-sentinel idiom over `EachInSubtree` is banned by SCANNER-FRAMEWORK-USAGE-02 (allowlist 0), alongside its depth-1 sibling over EachInChildren. The FINDFIRSTINSUBTREE-API-01 work-stream label refers to the subtree-axis migration that folded into this single rule; there is no separate rule ID.
Wrapper around scanner.FindFirstInSubtree — the only call path to scanner for archtest authors. Pure delegation, no behavior change.
func FindFirstInSubtreeStopAt ¶
func FindFirstInSubtreeStopAt[S any, N interface { *S ast.Node }](root ast.Node, stopAt func(ast.Node) bool, predicate func(N) bool) (N, bool)
FindFirstInSubtreeStopAt walks root's subtree like FindFirstInSubtree but stops descending into any non-root node for which stopAt returns true — the boundary-aware sibling of FindFirstInSubtree, completing the typed function choice matrix {EachIn*, FindFirst*} × {Children, Subtree, SubtreeStopAt}. Use this when the rule needs first-match semantics AND must respect a closure / scope boundary (e.g. don't credit a match inside a nested *ast.FuncLit because that lives in its own iteration scope).
Picking FindFirstInSubtreeStopAt over the boundary-less FindFirstInSubtree is a typed function name selection per ai-robust.md AI-robust Hard 范本 #1 "typed function choice for walk depth"; archtest authors must NOT fall back to the manual `EachInSubtreeStopAt + closure-sentinel` idiom — that shape is banned by SCANNER-FRAMEWORK-USAGE-02 (allowlist 0) on the same grounds as the boundary-less variant.
Wrapper around scanner.FindFirstInSubtreeStopAt — the only call path to scanner for archtest authors. Pure delegation, no behavior change.
func FlatNonDefaultTags ¶
func FlatNonDefaultTags() []string
FlatNonDefaultTags returns the union of all distinct non-empty build tags appearing in KnownNonDefaultTags, sorted. It carries only production build tags — the archtest_fixture build tag is deliberately NOT in KnownNonDefaultTags (#944), so this union never activates fixture-tagged code. Use it as TypedOpts.Tags for the single Run(t, Typed(...), ...) call that scans hand-written production code under all tag-gated activations at once — the compliant idiom for TAGGROUP-LOOP-FORBIDS-TYPED-RUN-01.
**Always pair with** a second Run(t, Typed(TypedOpts{}, ...), ...) call (tags=nil) to cover reverse build directives (//go:build !X) which are silently excluded from a -tags=...,X,... union load. Omitting the nil-tags call is safe only if no //go:build !<tag> files exist in the scanned tree — prefer the two-call idiom by default. See go/build matchTag and ADR docs/architecture/202605190000-adr-archtest-in-process-warmup.md §2.
Thin delegation to typeseval.FlatNonDefaultTags.
func HasReceiver ¶
HasReceiver reports whether fn declares a receiver whose base type name equals typeName (handles *T / T / T[P] / T[P,Q]). Thin delegation to callresolver.HasReceiver. AST-only; no *types.Info needed.
func IsCallToPkgFunc ¶
IsCallToPkgFunc reports whether call's callee resolves (import-alias / dot-import / generic-instantiation proof) to the package-level function (pkgPath, name). Thin delegation to callresolver.IsCallToPkgFunc.
info must come from the same packages.Load result that produced call — guaranteed when info is pass.TypesInfo. Returns false for method calls (use ResolveMethodCall), nil info, or nil call. In particular, a nil info yields false, so this is safe to call from an AST-only WalkFuncDeclsAST callback where ctx.Info == nil (it will simply never match).
func KnownNonDefaultTags ¶
func KnownNonDefaultTags() [][]string
KnownNonDefaultTags returns the build tag combinations that gate test or production files in this repo. Each entry is a []string as accepted by TypedOpts.Tags. The nil entry represents the default build context.
Thin delegation to typeseval.KnownNonDefaultTags.
func ParseBuildConstraint ¶
func ParseBuildConstraint(filePath string) (constraint.Expr, error)
ParseBuildConstraint extracts the file's build constraint expression so it can be evaluated under a custom tag set. Returns (nil, nil) when the file has no //go:build or // +build directive. Returns (nil, err) on parse failure (fail-closed).
Typical 3-way evaluation pattern (mirrors build_constraint_test.go and ci_integration_discovery_invariants_test.go):
expr, err := archtest.ParseBuildConstraint(path)
if err != nil || expr == nil { ... }
withTag := expr.Eval(archtest.BuildContextPredicate("integration"))
withoutTag := expr.Eval(archtest.BuildContextPredicate())
withNone := expr.Eval(func(_ string) bool { return false })
Use Pass.IsFileInScope when you only need the default-context (no extra tags) boolean result — it is simpler and does not expose the raw constraint.Expr. Use this function when you need the raw Expr for multi- predicate evaluation.
This stays a free function (NOT folded into a Pass method) on purpose: its callers run outside a Pass over files that are *out of default build scope* (e.g. *_integration_test.go gated by //go:build integration) and evaluate the constraint under several tag-set predicates. A Pass iterates only the default-context in-scope files, so it structurally cannot reach those files nor express the multi-predicate evaluation. #1037 deliberately kept this and BuildContextPredicate exported for that reason.
Thin delegation to typeseval.ParseBuildConstraint. filePath must be an absolute OS-native path (pass.Abs(f) is a suitable source).
func ReceiverTypeName ¶
ReceiverTypeName extracts the base type name from a method-receiver type expression. Handles *T / T / T[P] / T[P,Q]; returns "" for any other form.
Wrapper around scanner.ReceiverTypeName.
func Report ¶
func Report(t *testing.T, ruleID string, diags []Diagnostic)
Report formats and emits each diagnostic as a t.Errorf call, sorted and deduplicated. An empty diags slice is a no-op. ruleID is prefixed to every emitted message: "<ruleID>: <rel>:<line>: <message>".
func ResolveEnclosingFunc ¶
ResolveEnclosingFunc returns the OUTERMOST top-level *ast.FuncDecl enclosing node in file, mapped to its *types.Func identity (use fn.FullName() to obtain the canonical caller-side identity, e.g. "pkg.Func" or "(*pkg.Recv).Method").
Thin delegation to typeseval.ResolveEnclosingFunc. Returns (nil, false) for package-level var / const init, import blocks, or any other location outside a FuncDecl body — callers funneling callsite-level allowlists treat that as an automatic violation.
Nested *ast.FuncLit inherits the outer FuncDecl identity by design (FuncLit author = FuncDecl author; allowlisting the outer FuncDecl implicitly trusts any FuncLit inside it).
The signature is asymmetric to ResolveMethodCall / ResolvePackageRef (which take only info + expression): the lookup iterates `file.Decls` for *ast.FuncDecl, because typesInfo.Defs has no file-scope index from which to recover the enclosing FuncDecl by position alone. Pass the same *ast.File from `pass.Files` that produced node.
info must come from the same packages.Load result that produced file.
func ResolveMethodCall ¶
ResolveMethodCall returns the *types.Func that a method-call SelectorExpr resolves to, using info.Selections. Handles direct/pointer/promoted/alias receivers and method expressions.
Thin delegation to typeseval.ResolveMethodCall. Returns (nil, false) for non-method selectors, field selectors, or nil inputs.
info must come from the same packages.Load result that produced sel.
func ResolvePackageRef ¶
ResolvePackageRef returns the (pkgPath, name) tuple for a reference to a package-level symbol, covering two AST shapes:
- Qualified selector `pkg.Name` (e.g., scanner.EachFile)
- Bare identifier after a dot-import (e.g., EachFile after import . "…/scanner")
Thin delegation to typeseval.ResolvePackageRef. See that function's godoc for nil-guard and return-false cases (method receivers, builtins, local funcs).
info must come from the same packages.Load result that produced the AST nodes you are inspecting — this is guaranteed when info is pass.TypesInfo.
func RunStandardCellRules ¶
func RunStandardCellRules(t *testing.T, cfg ConfigForExternalCell)
RunStandardCellRules runs the curated StandardCellRules plus cfg.ExtraRules against the running module, reporting each rule's diagnostics through Report. It is the single entry point an external Cell repository calls:
func TestGoCellArchitecture(t *testing.T) {
archtest.RunStandardCellRules(t, archtest.ConfigForExternalCell{
BuildTags: []string{"prod"},
ProductionMainPkgs: []string{"./cmd/yourbinary"}, // enables PROD-MAIN-WIRING-NOOP-REJECT-01
})
}
Declare every composition-root (main) package in ProductionMainPkgs: omitting it (empty slice) SKIPS PROD-MAIN-WIRING-NOOP-REJECT-01 entirely (opt-in by declaration, mirroring BuildTags), so a copy-pasted config without it silently runs without the composition-root noop guard. A declared pattern that matches no package fails loud rather than passing — see the field godoc.
The scan target is the consumer's own module (resolved by the drivers from its go.mod), so the same call works unchanged in GoCell and in an external repo. Each rule's failures surface independently via Report(t, rule.ID, …), so one failing rule does not mask another.
GoCell itself does NOT need to call this in addition to its per-rule Tests: the per-rule Tests already dogfood the same Check* functions. This entry exists for external consumers and is exercised in-repo by TestRunStandardCellRules.
Beyond the curated set, additional module-path-agnostic Check* functions are exported for direct use (e.g. CheckAdapterReturnsDeclaredTypes, CheckCapabilityProviderFunnel, CheckEvalPredicateCentralization01, CheckFixtureCellIDTypedBuilder) — they are intentionally NOT in StandardCellRules (see its godoc for the register-vs-not rationale; an external repo can still wire any of them in via cfg.ExtraRules).
func StringLitValue ¶
StringLitValue returns the unquoted value of a STRING-kind *ast.BasicLit. Returns ok=false for nil, non-STRING, or malformed quoted literals.
Wrapper around scanner.StringLitValue.
func WalkFuncDecls ¶
func WalkFuncDecls(p *Pass, fn func(ctx FuncDeclContext))
WalkFuncDecls iterates p.Files; for each top-level *ast.FuncDecl with a non-nil Body it invokes fn with a FuncDeclContext bound to p.TypesInfo, p.Fset and p.Rel(file). Works in both AST-only mode (ctx.Info == nil, e.g. a Pass from Run(t, AST(...))) and typed mode (Pass from Run(t, Typed(...))).
It does NOT auto-skip _test.go / generated files — the callback decides (callers already branch on ctx.Rel suffix and p.IsGenerated). See callresolver.WalkFuncDecls for the depth-1 / bodiless-skip contract.
func WalkFuncDeclsAST ¶
func WalkFuncDeclsAST(files []*ast.File, fset *token.FileSet, rel func(*ast.File) string, fn func(ctx FuncDeclContext))
WalkFuncDeclsAST is the no-Pass, AST-only walker for rules that parse files standalone (go/parser, no driver / no *types.Info) — e.g. golden-source scans. ctx.Info is always nil. fn and the same depth-1 / bodiless-skip contract as WalkFuncDecls apply.
Types ¶
type CellRule ¶
type CellRule struct {
// ID is the stable rule identifier (e.g. "PANIC-REGISTERED-01"). It is the
// ruleID passed to Report and must be unique within a rule set.
ID string
// Run executes the rule and returns its diagnostics. Must be non-nil.
Run func(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
}
CellRule is a reusable, importable architecture invariant — the GoCell analog of golang.org/x/tools/go/analysis.Analyzer. ID is the stable rule identifier reported via Report (e.g. "PANIC-REGISTERED-01"); Run executes the rule against the module described by cfg and returns the diagnostics it observed (it does NOT call t.Errorf itself — RunStandardCellRules funnels every rule's diagnostics through Report uniformly).
Run takes *testing.T because the underlying driver (Run with its AST / Typed / Production scopes) fails loud via t.Fatalf on load errors and needs the test handle. A rule whose scan is module-wide under the default build config may ignore cfg; rules that must see build-tagged files read cfg.BuildTags.
func StandardCellRules ¶
func StandardCellRules() []*CellRule
StandardCellRules returns the platform's curated set of portable architecture invariants — the rules that apply to ANY repository building Cells on the GoCell platform (they reason about how the consumer uses platform APIs like errcode / panicregister, not about GoCell's own internal package layout).
Registered saga rule: SAGA-STEP-COMPENSATE-PURE-01 (CheckSagaStepCompensatePure).
This is the GoCell analog of the []*analysis.Analyzer slice handed to multichecker.Main: a flat, registry-free list. The set grows as more rules are migrated from their legacy _test.go form into importable CellRules (M3 PR-2..N, tracked at issue #1302); the ratchet meta-archtest ARCHTEST-MODULE-PATH-FUNNEL-01 guarantees that migration converges.
Classification (form follows function): a rule migrates to a Check* + a StandardCellRules entry only when it yields a meaningful PURE BAN for an external repo — its allowlist / sanctioned sites are GoCell-internal packages absent from a consumer module, so the constraint genuinely fires on consumer code. A rule whose scan targets GoCell-specific paths / sealed types / floors / waivers (vacuous-green or false-red externally) is NOT registered: it either stays a Check* that GoCell dogfoods but does not register (listed below), or — for a pure self-check with no portable value — keeps its logic in its _test.go with only platform paths derived from PlatformModulePath (e.g. this PR's L2 atomicity, publisher/checkpoint conformance enrollment, outboxtest import boundary, relay isolation, checkpoint tx-bound rules).
Rules intentionally NOT registered (register=no) because they reason about GoCell's own internal package layout / source, not about how a consumer uses platform APIs — so they are vacuous-green or false-red in an external module:
- ERROR-FIRST-API-01 (CheckErrorFirstAPI01): gated by errorFirstEnforcedFiles, a hardcoded 22-path positive allowlist of GoCell-specific files. An external module has no files matching those paths → zero scan → vacuous green-pass with no safety signal. The invariant is still enforced in GoCell itself via TestErrorFirstAPI01.
- ERROR-FIRST-TYPED-NIL-01 (CheckErrorFirstTypedNil01): similarly gated by errorFirstEnforcedFiles via errorFirstPackagePatterns(). Same false-safety concern for external modules. Enforced in GoCell via TestErrorFirstTypedNil01.
- DETAILS-SEALED-FIELD-FROZEN-01 (CheckDetailsSealedFieldFrozen01): a platform-source self-check — it reflects on errcode.PublicDetail/InternalDetail and AST-parses pkg/errcode/details.go. The reflect half is tautological for a consumer (it inspects the imported GoCell dependency type, which the consumer cannot alter); the AST half resolves details.go under the CONSUMER module root (findModuleRoot), where that file does not exist → false-red in a clean external repo. It constrains GoCell's own errcode package shape, so it stays a GoCell-internal self-check enforced via TestDetailsSealedFieldFrozen01.
- SCAFFOLD-LISTENER-MARKER-TYPED-CONST-01 (CheckScaffoldListenerMarkerTypedConst): scans the platform's own tools/codegen/cellgen package + the templates/scaffold-cell.tmpl asset (neither exists in an external Cell repo) → vacuous-green there. Migrated for the unified PlatformModulePath parameterization + fork-safety only; enforced in GoCell via TestScaffoldListenerMarkerTypedConst.
- OUTBOX-TOPIC-FAILOPEN-01 (CheckOutboxTopicFailopen01): kernel/outbox.Entry is sealed — a populated outbox.Entry{...} composite literal outside kernel/outbox is a compile error (OUTBOX-ENTRY-SEALED-CONSTRUCTION-01), so the production scan for fail-open security-topic literals is vacuous in GoCell AND in any external repo; the rule's real coverage is the fixturetest/outbox fake-package fixtures under testdata/. Migrated for PlatformModulePath parameterization + fork-safety only; enforced in GoCell via the dogfood + fixture sub-tests in outbox_invariants_test.go.
- SAGA-COORDINATOR-NO-HEARTBEAT-LOOP-01 (CheckSagaCoordinatorNoHeartbeatLoop): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
- SAGA-EXECUTOR-RAND-INJECTED-01 (CheckSagaExecutorRandInjected): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/ false-red in an external module.
- SAGA-JOURNAL-CONFORMANCE-ENROLLMENT-01 / SAGA-GLOBALREADER-CONFORMANCE-ENROLL-01 / SAGA-OWNER-CHECKPOINT-CONFORMANCE-ENROLL-01 (checkSagaConformanceEnrollment in conformance_enrollment.go, #2249): reason about GoCell-internal saga/projection layout → vacuous/false-red in an external module; GoCell-internal, never registered, driven only by the Test* functions (no importable Check*).
- SAGA-JOURNAL-HOLDER-SEAL-01 (CheckSagaJournalHolderSeal): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/ false-red in an external module.
- SAGA-DRIVE-BEHIND-LEADER-GATE-01 (CheckSagaDriveBehindLeaderGate): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
- SAGA-STEP-RUN-OUTSIDE-TX-01 (CheckSagaStepRunOutsideTx): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/ false-red in an external module.
- SAGA-CONSTRUCTOR-NIL-GUARD-01 (CheckSagaConstructorNilGuard): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/ false-red in an external module.
- SAGA-SLOG-INSTANCE-FIELDS-CALLER-01 (CheckSagaSlogInstanceFieldsCaller): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
- SAGA-METRIC-LABEL-VALUES-FROZEN-01 (CheckSagaMetricLabelValuesFrozen): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
An external consumer gets the rules migrated so far plus any cfg.ExtraRules they add. The set expands as additional portable rules land in #1302.
Membership single source (#2331) ¶
The framework-scope MEMBERSHIP — which rule IDs belong to this curated set — lives in exactly one place: scoperules.FrameworkRuleIDs (a zero-dependency leaf). This function DERIVES its slice by iterating that list and pairing each ID with its Run via runByID, so the set is never enumerated twice. The `gocell verify archtest --scope=framework` runner reads the identical leaf, so the CLI scope and this registry cannot drift (AI-robust Hard). runByID only supplies the Run binding: a leaf ID with no Run here yields a nil-Run CellRule, which validateCellRule rejects in RunStandardCellRules and TestStandardCellRulesComposition asserts absent (Run-binding completeness).
type ConfigForExternalCell ¶
type ConfigForExternalCell struct {
// BuildTags lists the consumer's production build tags (e.g.
// []string{"prod", "amqp"}). Rules that must see code behind build
// directives scan twice — once under the default build config and once with
// these tags — so a violation hidden behind `//go:build prod` is not missed.
// An empty slice means "scan the default build configuration only". GoCell's
// own dogfood passes FlatNonDefaultTags() (its full non-default tag union);
// an external repo passes whatever tags gate its production files.
BuildTags []string
// ProductionMainPkgs lists the consumer's composition-root (main) package
// patterns — e.g. []string{"./cmd/corebundle"}. PROD-MAIN-WIRING-NOOP-REJECT-01
// scans ONLY these packages and rejects direct construction of a raw kernel/outbox
// noop event sink (see prod_main_wiring_noop_reject.go). Entries are Go-style
// relative package patterns matched against each package's SCAN-ROOT-relative dir
// (the go.work workspace root, or the module root for a single-module repo):
// "./cmd/x" (exact package) or "./cmd/x/..." (recursive). An empty slice means
// "do not scan composition roots" — opt-in by declaration, mirroring BuildTags;
// a repo that has not opted in gets no main-pkg scan (no false positives). A
// declared pattern that matches NO package is rejected loud (a silently-0-match
// pattern is a false green), so a typo or wrong-root pattern fails CI instead of
// quietly disabling the rule. An external repo lists whatever packages build its
// production binaries.
ProductionMainPkgs []string
// ExtraRules are consumer-owned custom rules appended to the standard set —
// the minimal plugin surface. They use the identical CellRule type (no
// separate registration mechanism), mirroring ArchUnit's custom
// DescribedPredicate/ArchCondition plugging into the same fluent API.
//
// Trust boundary: each ExtraRule's Run receives the same *testing.T as the
// standard rules. A rule that finds violations MUST return them as
// []Diagnostic (funneled through Report → t.Errorf) and MUST NOT call
// t.Fatal / t.FailNow itself — FailNow aborts the entire
// RunStandardCellRules loop via runtime.Goexit, masking every standard rule
// that would have run after it.
ExtraRules []*CellRule
}
ConfigForExternalCell is the consumer-supplied description of the module that RunStandardCellRules should analyze. The module IMPORT PATH and ROOT are not fields the consumer must get right: the drivers resolve them from the running module's go.mod (findModuleRoot walks up from the test process cwd), which is fail-closed by construction — an unreadable/absent go.mod fails the underlying driver loudly. This mirrors M2's render.go contract (module path is derived, never a silently-defaulted input). ref: arch-go config.Load.
Fields are deliberately minimal (YAGNI): every field is read by a rule that actually ships in StandardCellRules. ref: arch-go config.Load.
type ContentContext ¶
type ContentContext = scanner.ContentContext
ContentContext is the non-Go counterpart of the scanner's per-file payload for YAML / JSON / Markdown / SQL etc. Plain bytes only — no AST, no FileSet.
type CrossModuleViolation ¶
type CrossModuleViolation struct {
// Pkg is the base-module package performing the import.
Pkg string
// Import is the imported package owned by a satellite module.
Import string
// Module is the satellite module that owns Import.
Module string
}
CrossModuleViolation is one base→satellite import edge: a package owned by the base (core) module importing a package owned by a different workspace member. Such an edge inverts the workspace dependency direction (Plan D: "顶层 = core"; satellites depend on core, never the reverse).
func CheckCrossModuleImportDirection ¶
func CheckCrossModuleImportDirection(g *kerneldepgraph.Graph, coreModule string) []CrossModuleViolation
CheckCrossModuleImportDirection enforces CROSS-MODULE-IMPORT-DIRECTION-01: no package owned by coreModule may import a package owned by a DIFFERENT workspace member module. The base (core) module is the workspace root module (go.work `use .` — github.com/ghbvf/gocell for the platform); satellites (github.com/ghbvf/gocell/mdm, /zerotrust, separate-module examples) may import core, but core must never import a satellite, or extracting a satellite would drag the whole base with it.
The live workspace already holds satellite modules (adapters/*, corecells, tools, generated, …), so this is an active gate: it reports no violation only because no core-module package imports a satellite (correct layering), not because the workspace is single-module. The synthetic fixture exercises the firing path.
AI-robust grade: Medium (archtest type-aware via g.Modules + the kerneldepgraph.Classifier longest-prefix owner resolution on each import edge; import-path strings come from the typed packages.Load, not a string anchor). A Hard form for the GENERIC "core ⊀ any satellite" shape is unreachable — Go's type system / package visibility cannot express "a package in module A must not import any package in module B" (same permanent ceiling as #851 / #893 / #1282). The Hard COMPLEMENT is realized in `.golangci.yml` as the `core-no-satellite-import` depguard rule (#1590): a path-level compile-fast ban of each top-level satellite import-path prefix from the production core (gocell.go + kernel/pkg/runtime). This generic archtest stays the catch-all — it auto-covers every future satellite, INCLUDING ones not yet enumerated in depguard (the depguard list is intentionally partial: appended per Plan D P1+ extraction, never a completeness gate).
Blind spots (see cross_module_import_direction_test.go for the reverse self-checks): a base package reaching a satellite via a string-built import path (not a real edge) is not modeled — depgraph edges come from real packages.Load Imports, so a non-importing reference cannot create an edge.
type Diagnostic ¶
type Diagnostic = scanner.Diagnostic
Diagnostic represents a single rule violation. Rules accumulate Diagnostics in their return slice; the caller passes the slice to Report together with the rule ID.
func Canonical ¶
func Canonical(diags []Diagnostic) []Diagnostic
Canonical deduplicates diags and returns them sorted by (Rel, Line, Message), the same order Report and the golden harness use. Rules that accumulate diagnostics across multiple passes (e.g. a build-tag×N scan) use it to return a stable, duplicate-free slice from their importable Check* body.
func CheckAdapterReturnsDeclaredTypes ¶
func CheckAdapterReturnsDeclaredTypes(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAdapterReturnsDeclaredTypes runs ADAPTER-RETURNS-DECLARED-TYPES-01 over the running module and returns its diagnostics (single source). It is the importable entry point: GoCell's TestAdapterReturnsDeclaredTypes calls it directly — no parallel rule body.
func CheckAfterCommitHookPureTransient ¶
func CheckAfterCommitHookPureTransient(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckAfterCommitHookPureTransient runs AFTERCOMMIT-HOOK-PURE-TRANSIENT-01 (A1+A2+A3) over the running module's production code and returns its diagnostics. cfg.BuildTags is passed to the production scan so files behind //go:build directives are covered.
This function is intentionally NOT registered in StandardCellRules(): rule A3's drain-caller allowlist names GoCell-internal TxRunner files, so running it against an external module's production code would false-positive on any TxRunner implementation outside that allowlist. GoCell's own dogfood calls this directly from TestAfterCommitHookPureTransient.
The B2/B3 blind-spot escape scan (scanRegisterAfterCommitEscapes) is intentionally absent here — it is the dedicated reverse self-check owned by TestAfterCommitHookPureTransient_BlindSpots_NoEscapeInProduction, not part of the forward rule enforcement.
func CheckAuditHashInputFrozenA2 ¶
func CheckAuditHashInputFrozenA2(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuditHashInputFrozenA2 verifies that within runtime/audit/ledger production source any call to crypto/hmac.New is located inside the body of the Protocol.ComputeHash method. The callee identification uses go/types via IsCallToPkgFunc, so import aliases cannot bypass the lock.
Not registered in StandardCellRules: see file godoc.
func CheckAuthAuthtestBoundary ¶
func CheckAuthAuthtestBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthAuthtestBoundary runs AUTH-AUTHTEST-BOUNDARY-01 (sub-rules A + C) over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestAuthAuthtestBoundary calls it directly — single source, no parallel rule body.
func CheckAuthKeystestBoundary ¶
func CheckAuthKeystestBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthKeystestBoundary runs AUTH-KEYSTEST-IMPORT-BOUNDARY-01 (sub-rules A/B/C/D) over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestAuthKeystestBoundary calls it directly — single source, no parallel rule body.
func CheckAuthPlanCellsMustNotConstructAuthPlans ¶
func CheckAuthPlanCellsMustNotConstructAuthPlans(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthPlanCellsMustNotConstructAuthPlans enforces AUTH-PLAN-04 (LAYER-09): AuthPlan values (AuthJWT, AuthJWTFromAssembly, AuthMTLS, etc.) are composition-root concerns and must only be constructed in cmd/ and examples/. Cells and runtime (except runtime/bootstrap/ which is the wiring layer) must not instantiate the concrete types — that would couple business logic to listener topology decisions.
Scanned packages:
- all production packages under cells/ and runtime/ (the Production scope already excludes generated/)
- runtime/bootstrap/... is the authorized wiring layer and is skipped
Resolution model — typed (Hard upgrade, PR #615 review fix):
Both branches resolve the symbol's defining package via go/types (*types.Info.Uses / TypeOf), NOT via the AST package-qualifier identifier:
Constructor calls (`foo.NewAuthJWT(...)`): Uses[sel.Sel] returns a *types.Func; we check fn.Pkg().Path() == authPlanPkgPath and fn.Name() in {NewAuth*}.
Composite literals (`foo.AuthMTLS{}`, `AuthMTLS{}` inside the auth package itself): we walk the type-AST (SelectorExpr or Ident) to its *ast.Ident, look up Uses[ident] to get a *types.TypeName, and check tn.Pkg().Path() == authPlanPkgPath and tn.Name() in authPlanConstructorNames.
Why typed: parser-only + import-alias allowlist (the previous form) went vacuously empty after the kernel/cell → kernel/auth move because the check was `pkg.Name == "cell"`. Maintaining an explicit alias allow-list ({auth, kauth, …}) makes any new alias a silent bypass — the AI-robust gap the reviewer flagged. Resolving through go/types is alias-agnostic by construction: `import myauth "github.com/ghbvf/gocell/framework/kernel/auth"` still resolves Uses[*ast.Ident{Name:"myauth"}] back to the kernel/auth package object whose Path() is authPlanPkgPath.
Blind spots (documented per ai-robust.md "工具选定后强制盲区自检"):
- reflective construction via reflect.New(reflect.TypeOf(auth.AuthMTLS{})) — the typed walk catches the auth.AuthMTLS{} composite literal, so this form is covered transitively.
- method values / function pointers (e.g. `f := auth.NewAuthJWT`) — the Uses[*ast.Ident] resolution still fires on the SelectorExpr.Sel of the RHS, so the rule still flags the assignment.
- dot-import (`import . "github.com/ghbvf/gocell/framework/kernel/auth"` then `_ = AuthMTLS{}`) — the type-AST is then a bare *ast.Ident, and Uses[ident] still resolves to the kernel/auth *types.TypeName. Covered.
- cgo / unsafe-pointer construction — not expressible for sealed interface implementations like AuthPlan. Out of scope.
func CheckAuthPlanNoCellPolicyTypeUsage ¶
func CheckAuthPlanNoCellPolicyTypeUsage(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthPlanNoCellPolicyTypeUsage enforces AUTH-PLAN-03: cell.Policy is a deleted type. Neither `cell.Policy{…}` composite literals nor `cell.Policy` identifier references should appear in any .go file.
func CheckAuthPlanNoLegacyPolicySelectorExpressions ¶
func CheckAuthPlanNoLegacyPolicySelectorExpressions(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthPlanNoLegacyPolicySelectorExpressions enforces AUTH-PLAN-02: the seven deleted bootstrap.Policy* factory functions must not be referenced anywhere in the codebase. This catches accidental re-introduction of the old API.
func CheckAuthPlanNoLegacyPolicyStringLiterals ¶
func CheckAuthPlanNoLegacyPolicyStringLiterals(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckAuthPlanNoLegacyPolicyStringLiterals enforces AUTH-PLAN-01: the string values that were used as cell.Policy.Name discriminators ("jwt", "mtls", "service-token", "stack[") must not appear as bare string literals in production .go files outside the canonical allowlist.
Allowlisted files (see authPlanStringAllowlist) may contain these strings because they own the canonical Describe() definitions or use them as observability labels rather than dispatch keys.
func CheckAuthzMutationApplyFunnel01 ¶
func CheckAuthzMutationApplyFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckAuthzMutationApplyFunnel01 runs Rule (a) of AUTHZ-MUTATION-APPLY-FUNNEL-01: every call to domain.User.SetStatus or domain.User.SetPasswordResetRequired in non-test production code must originate from an enclosing FuncDecl whose canonical identity (types.Func.FullName) is listed in setMutatorCallsiteAllowlist.
func CheckBcryptCostFunnel01 ¶
func CheckBcryptCostFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckBcryptCostFunnel01 runs both A1 and A2 sub-rules of BCRYPT-COST-FUNNEL-01 over the running module and returns their combined diagnostics. It is the importable rule body and the single source for the rule: GoCell's TestBCRYPT_COST_FUNNEL_01 dogfoods it via Report(...), so the exact scan an external cell would import is the one GoCell enforces (no parallel rule body).
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
func CheckCASProtocolCompositionRoot01 ¶
func CheckCASProtocolCompositionRoot01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckCASProtocolCompositionRoot01 runs CAS-PROTOCOL-COMPOSITION-ROOT-01 over the running module and returns its diagnostics.
cas.NewProtocol may only be invoked from cmd/* (composition root) or runtime/state/cas/* (the package itself). Cells, runtime/* (non-cas), and adapters/* must receive an injected *cas.Protocol — not construct one.
cmd/ and examples/ are intentionally outside the scan scope:
- cmd/* is the composition root by definition.
- examples/* each carry their own composition root; allowing them mirrors the AUTH-PLAN-04 / LAYER-09 carve-out for example projects.
func CheckCapabilityProviderFunnel ¶
func CheckCapabilityProviderFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCapabilityProviderFunnel enforces CAPABILITY-PROVIDER-FUNNEL-01: the shared-infra adapter constructors may only be called from cmd/corebundle/cap_wiring.go. Cell module files must consume the injected capability.PGProvider / capability.RedisProvider. It scans the running module's cmd/ production packages and returns the diagnostics it observes; GoCell's TestCapabilityProviderFunnel_CompositionRootOnly calls it directly — single source, no parallel rule body.
func CheckCellTLSMaterialFunnel ¶
func CheckCellTLSMaterialFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCellTLSMaterialFunnel enforces CELLTLS-MATERIAL-FUNNEL-01: the tlsutil mTLS-material constructors NewClientIdentity and NewServerMTLSConfig may only be called from the sanctioned sites (cellmodules/celltls and adapters/grpc). It scans the running module's full production tree (all packages, generated/ excluded) and returns the diagnostics it observes; GoCell's TestCellTLSMaterialFunnel calls it directly — single source, no parallel rule body.
func CheckCelltestImportBoundary ¶
func CheckCelltestImportBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCelltestImportBoundary enforces CELLTEST-IMPORT-BOUNDARY-01 (A/B/C) across the full module:
- A: no non-_test.go file (except celltest's own sources) imports celltest.
- B: no kernel/ file (except celltest itself and kernel/cell/ _test.go) imports celltest.
- C: no examples/ non-_test.go file imports celltest.
t.Fatalf is used only for infrastructure failures (collectGoFiles, parseImports, moduleImportPath). Violations are returned as []Diagnostic; callers use Report to surface them.
func CheckCelltestImportScope ¶
func CheckCelltestImportScope(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCelltestImportScope enforces CELLTEST-IMPORT-SCOPE-01:
No production Go file (i.e. NOT *_test.go and NOT in a test-infrastructure directory) may import a package whose import path matches cells/[a-z]+/[a-z]+test$. Those packages are cell-level testutil packages and importing them from production code would embed test fixtures and t-bound helpers in a release binary, signaling a layering mistake.
The check is discovery-based: any new cells/{X}/{X}test/ package is automatically covered without further edits to this file.
t.Fatalf is used only for infrastructure load failures (collectGoFiles, parseImports, moduleImportPath). Violations are returned as []Diagnostic and never cause t.Errorf — callers use Report to surface them.
func CheckCelltransportSelectFunnel01 ¶
func CheckCelltransportSelectFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCelltransportSelectFunnel01 scans the wiring-layer roots for direct calls to transport.NewRemoteHTTP and returns one Diagnostic per offending callsite. It is the single rule body — the Test* dogfoods it via Report — so the exact scan is the one enforced.
The allowlist excludes cellmodules/celltransport (the sole sanctioned caller): because celltransport lives under cellmodules/ (a scanned root), its path is explicitly allowlisted here rather than excluded from the root set, so that other packages under cellmodules/ are still scanned correctly.
func CheckCertdepsInmemFunnel01 ¶
func CheckCertdepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCertdepsInmemFunnel01 scans the real module tree and returns one Diagnostic per offending callsite. It is the single rule body — the Test* dogfoods it via Report — so the exact scan is the one enforced.
func CheckClockPositionalInjection ¶
func CheckClockPositionalInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckClockPositionalInjection runs CLOCK-POSITIONAL-INJECTION-01 against the go.work workspace production scope and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
func CheckCompositionNewForRoleFunnel01 ¶
func CheckCompositionNewForRoleFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCompositionNewForRoleFunnel01 scans the wiring-layer roots for direct calls to composition.New and returns one Diagnostic per offending callsite. It is the single rule body — the Test* dogfoods it via Report — so the exact scan is the one enforced.
func CheckContractPathQueryCoverage01 ¶
func CheckContractPathQueryCoverage01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckContractPathQueryCoverage01 runs CONTRACT-PATH-QUERY-COVERAGE-01 over the running module and returns diagnostics. It does NOT call t.Errorf; the caller should funnel results through Report(t, "CONTRACT-PATH-QUERY-COVERAGE-01", ...).
Not registered in StandardCellRules: gocell-internal-layout funnel; vacuous- green/false-red in an external module; enforced in GoCell via TestContractPathQueryCoverage01.
func CheckContractPathQueryParamNameLiteral01 ¶
func CheckContractPathQueryParamNameLiteral01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckContractPathQueryParamNameLiteral01 runs CONTRACT-PATH-QUERY-PARAM-NAME-LITERAL-01 over the running module and returns diagnostics. It does NOT call t.Errorf; the caller should funnel results through Report(t, "CONTRACT-PATH-QUERY-PARAM-NAME-LITERAL-01", ...).
Not registered in StandardCellRules: gocell-internal-layout funnel; vacuous- green/false-red in an external module; enforced in GoCell via TestContractPathQueryParamNameLiteral01.
func CheckContracttestLoadByIDLiteral01 ¶
func CheckContracttestLoadByIDLiteral01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckContracttestLoadByIDLiteral01 runs CONTRACTTEST-LOADBYID-LITERAL-01 over the running module and returns diagnostics. It does NOT call t.Errorf; the caller should funnel results through Report(t, "CONTRACTTEST-LOADBYID-LITERAL-01", ...).
Not registered in StandardCellRules: gocell-internal-layout funnel; vacuous-green in an external module; enforced in GoCell via TestContracttestLoadByIDLiteral01.
func CheckCredentialAuthorityAssertFunnel01 ¶
func CheckCredentialAuthorityAssertFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckCredentialAuthorityAssertFunnel01 runs CREDENTIAL-AUTHORITY-ASSERT-FUNNEL-01 over the running module and returns its diagnostics.
This is the importable CellRule body. GoCell's own Test* functions in credential_authority_assert_funnel_test.go call the same detectors — single source, no parallel rule body.
The rule is intentionally NOT registered in StandardCellRules: it reasons about GoCell's own internal package layout (credentialauthority / domain / session slices), making it vacuous-green or false-red for an external module.
func CheckCredentialInvalidateApplierCanonical01 ¶
func CheckCredentialInvalidateApplierCanonical01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckCredentialInvalidateApplierCanonical01 runs CREDENTIAL-INVALIDATE-APPLIER-INTERFACE-CANONICAL-01: the Apply signature matching credentialinvalidate.Applier must only appear in the credentialinvalidate package.
func CheckCredentialInvalidateFunnel01 ¶
func CheckCredentialInvalidateFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckCredentialInvalidateFunnel01 runs CREDENTIAL-INVALIDATE-FUNNEL-01: session.Store.RevokeForSubject must only be called from the credentialinvalidate funnel or store implementations.
func CheckCredentialInvalidateUpstreamCaller01 ¶
func CheckCredentialInvalidateUpstreamCaller01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckCredentialInvalidateUpstreamCaller01 runs CREDENTIAL-INVALIDATE-UPSTREAM-CALLER-01: every reference to credentialinvalidate.(*Invalidator).Apply must originate from an enclosing FuncDecl listed in upstreamCallerCallsiteAllowlist.
func CheckCtxkeysPrincipalWriteCaller01 ¶
func CheckCtxkeysPrincipalWriteCaller01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckCtxkeysPrincipalWriteCaller01 runs CTXKEYS-PRINCIPAL-WRITE-CALLER-01 over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestCtxkeysPrincipalWriteCaller01 calls it directly — single source, no parallel rule body.
Not registered in StandardCellRules: the allowlist is gocell-hardcoded (runtime/auth/middleware.go, kernel/outbox/principal.go); an external cell's own auth code would false-red.
func CheckDeadCodeCover ¶
func CheckDeadCodeCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckDeadCodeCover enforces DEAD-CODE-01: No production Go file may import a lifecycle: deprecated contract's generated package, nor contain a string literal equal to a deprecated contract.id. Today 0 deprecated → vacuous pass.
Not registered (register=none): gocell-internal-layout/schema funnel; vacuous-green/false-red in an external module; enforced in GoCell via TestDeadCodeCover.
func CheckDeadContractCover ¶
func CheckDeadContractCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckDeadContractCover enforces DEAD-CONTRACT-01: Every lifecycle: active contract.yaml must have an entry point appropriate for its kind.
Not registered (register=none): gocell-internal-layout/schema funnel; vacuous-green/false-red in an external module; enforced in GoCell via TestDeadContractCover.
func CheckDetailsSealedFieldFrozen01 ¶
func CheckDetailsSealedFieldFrozen01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckDetailsSealedFieldFrozen01 runs DETAILS-SEALED-FIELD-FROZEN-01 against the platform errcode package's PublicDetail/InternalDetail structs and returns its diagnostics.
Importable for GoCell dogfood / direct invocation, but intentionally NOT registered in StandardCellRules: it is a platform-source self-check, not a consumer-API rule. The reflect half inspects errcode.PublicDetail/InternalDetail — for a consumer that is the imported GoCell dependency type, which they cannot alter (tautological); the AST half (checkPublicDetailInvariants) parses pkg/errcode/details.go resolved under the CONSUMER module root via findModuleRoot, where that file does not exist → false-red in a clean external repo. It constrains GoCell's own errcode package shape, so it stays a GoCell-internal self-check enforced via TestDetailsSealedFieldFrozen01. See external.go's StandardCellRules godoc.
func CheckDistlockLockNotContext01 ¶
func CheckDistlockLockNotContext01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckDistlockLockNotContext01 verifies that *runtime/distlock.Lock does not implement context.Context. Returns anti-vacuity diagnostics when the Lock type or context.Context interface cannot be found, and a violation diagnostic when Lock implements the interface.
Not registered in StandardCellRules: see file godoc.
func CheckDomainAuthzFieldPrivate01 ¶
func CheckDomainAuthzFieldPrivate01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckDomainAuthzFieldPrivate01 runs DOMAIN-AUTHZ-FIELD-PRIVATE-01: domain.User must NOT expose exported fields named Status, PasswordResetRequired, or AuthzEpoch, and must NOT have exported setter methods matching the Set*/Mark*/Clear*/Lock*/Unlock* pattern beyond the two sanctioned ones (SetStatus / SetPasswordResetRequired).
func CheckEmitDeclCover ¶
func CheckEmitDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckEmitDeclCover enforces EMIT-DECL-COVER-01: Every outbox.Emit call's topic arg const string must be declared in an active contract (event publisher or triggers entry) for the caller's cell. Non-const topic args also produce a diagnostic.
Not registered (register=none): gocell-internal-layout/schema funnel; vacuous-green/false-red in an external module; enforced in GoCell via TestEmitDeclCover.
func CheckErrcodeKindLiteralBanned ¶
func CheckErrcodeKindLiteralBanned(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckErrcodeKindLiteralBanned runs ERRCODE-KIND-LITERAL-01 over the running module and returns its diagnostics. It is the importable CellRule body wrapped by StandardCellRules; GoCell's TestErrcodeLiteralConstructionBanned calls it directly — single source, no parallel rule body.
func CheckErrcodeMessageConstLiteral ¶
func CheckErrcodeMessageConstLiteral(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckErrcodeMessageConstLiteral runs MESSAGE-CONST-LITERAL-01 over the running module and returns its diagnostics. It is the importable CellRule body wrapped by StandardCellRules. The default build config plus the consumer's cfg.BuildTags are scanned (see runErrcodeTypedScan) so a violation behind a `//go:build` directive in any repo is not missed.
func CheckErrorFirstAPI01 ¶
func CheckErrorFirstAPI01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckErrorFirstAPI01 runs ERROR-FIRST-API-01 over the enforced file list and returns its diagnostics. Importable for GoCell dogfood / direct invocation, but intentionally NOT registered in StandardCellRules: it is gated by the GoCell-specific errorFirstEnforcedFiles allowlist and so is vacuous-green in an external module (see external.go). Enforced in GoCell via TestErrorFirstAPI01.
func CheckErrorFirstTypedNil01 ¶
func CheckErrorFirstTypedNil01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckErrorFirstTypedNil01 runs ERROR-FIRST-TYPED-NIL-01 over the enforced file list and returns its diagnostics. Importable for GoCell dogfood / direct invocation, but intentionally NOT registered in StandardCellRules: it is gated by the GoCell-specific errorFirstEnforcedFiles allowlist and so is vacuous-green in an external module (see external.go). Enforced in GoCell via TestErrorFirstTypedNil01.
func CheckEvalPredicateCentralization01 ¶
func CheckEvalPredicateCentralization01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckEvalPredicateCentralization01 enforces TYPESEVAL-EVAL-PREDICATE-CENTRALIZED-01: every constraint.Expr.Eval callsite in tools/archtest/*_test.go (top-level package, excluding internal/ sub- packages) must pass either typeseval.BuildContextPredicate(...) (Form A) or an inline `func(_ string) bool { return false }` sentinel (Form B). It returns the diagnostics it observes; GoCell's TestEvalPredicateCentralization01 calls it directly — single source, no parallel rule body.
func CheckExportedErrorNew ¶
func CheckExportedErrorNew(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckExportedErrorNew runs EXPORTED-ERROR-NEW-01 over the running module and returns its diagnostics. It is the importable CellRule body wrapped by StandardCellRules. The default build config plus the consumer's cfg.BuildTags are scanned (see runErrcodeTypedScan) so an exported `Err* = errors.New(...)` behind a `//go:build` directive in any repo is not missed.
func CheckFenceTokenMintFunnel ¶
func CheckFenceTokenMintFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckFenceTokenMintFunnel enforces FENCE-TOKEN-MINT-FUNNEL-01.
Every reference to credentialfence.Mint outside an allowlisted path is a violation — not just a direct call, but any function-value capture or reflect arg as well (see scanFenceTokenMintRefs for the form-complete scan). Combined with the upstream type-system seal (unexported marker method on FenceToken) the only way to obtain a non-nil FenceToken is through Mint; constraining every reference to Mint therefore constrains every non-nil FenceToken's origin.
Scanner: ResolvePackageRef + EachInSubtree[ast.SelectorExpr]. credentialfence.Mint is a package-level function (not a method), so its reference is recorded in types.Info.Uses as a (PkgName, FuncName) pair — ResolveMethodCall would silently miss it (Selections only holds method selectors). The resolver returns the (pkgPath, name) tuple; a selector is a violation only when pkgPath == credentialfence package path and name == "Mint" — exact identity, no name-collision possible across packages, alias-immune.
RED fixture verification: testdata/fence_token_fixtures/external_mint_red/ is loaded separately and the scanner must detect all five reference forms (wantMin=5), proving the rule is form-complete and not a permanently-passing no-op (the reverse RED self-check mandated by ai-robust.md §"工具选定后强制盲区自检").
func CheckFixtureCellIDTypedBuilder ¶
func CheckFixtureCellIDTypedBuilder(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckFixtureCellIDTypedBuilder enforces FIXTURE-CELLID-TYPED-BUILDER-01 (A1): every kernel/metadata-typed cell-id field position must be sourced from metadatatest. It is the importable rule body — GoCell's TestFixtureCellIDTypedBuilder calls it via dogfood, single source.
func CheckGenerateRSAKeypairFunnel01 ¶
func CheckGenerateRSAKeypairFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGenerateRSAKeypairFunnel01 scans the hardened composition roots for direct calls to auth.GenerateRSAKeyPair and returns one Diagnostic per offending callsite. It is the single rule body — the Test* dogfoods it via Report — so the exact scan is the one enforced.
func CheckGovernanceEmitterConstructorsNeverFuncValue ¶
func CheckGovernanceEmitterConstructorsNeverFuncValue(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGovernanceEmitterConstructorsNeverFuncValue runs GOVERNANCE-RULE-EMITTER-CONSTRUCTORS-NEVER-FUNCVALUE-01 and returns diagnostics. It does NOT call t.Errorf.
Verifies that kernel/governance contains ZERO references to the emitter constructors (newError/newWarning/newScopedError/newErrorAt) that are not the direct callee of a call expression (i.e. no func-value indirections).
Not registered (register=none): gocell-internal-layout funnel; vacuous-green/ false-red in an external module; enforced in GoCell via TestGovernanceEmitterConstructorsNeverFuncValue.
func CheckGovernanceRuleCodeConstSingleSource ¶
func CheckGovernanceRuleCodeConstSingleSource(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGovernanceRuleCodeConstSingleSource runs GOVERNANCE-RULE-CODE-CONST-SINGLE-SOURCE-01 and returns diagnostics. It does NOT call t.Errorf.
Every newError/newWarning/newScopedError/newErrorAt call in kernel/governance (excluding *_test.go and rulecodes.go) must pass a RuleCode-typed constant declared in rulecodes.go as its first argument. Every ValidationResult{} composite literal must use a RuleCode const from rulecodes.go as Code: value.
Not registered (register=none): gocell-internal-layout funnel; vacuous-green/ false-red in an external module; enforced in GoCell via TestGovernanceRuleCodeConstSingleSource.
func CheckGovernanceRuleCodeDetectBinding ¶
func CheckGovernanceRuleCodeDetectBinding(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGovernanceRuleCodeDetectBinding runs GOVERNANCE-RULE-CODE-DETECT-BINDING-01 and returns diagnostics. It does NOT call t.Errorf.
For every entry in kernel/governance allRules, the Rule.Code value must appear as the first argument of at least one newError/newWarning/newScopedError/ newErrorAt call reachable from the entry's Detect method (BFS over same-receiver *Validator method calls).
Not registered (register=none): gocell-internal-layout funnel; vacuous-green/ false-red in an external module; enforced in GoCell via TestGovernanceRuleCodeDetectBinding.
AI-robust grading: Medium (type-aware archtest AST walk). Hard path does not exist — Rule.Code is structurally separate from the detect body's newError argument; no type-system mechanism can enforce their equality at compile time.
func CheckGovernanceRuleErrorFixField ¶
func CheckGovernanceRuleErrorFixField(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGovernanceRuleErrorFixField runs GOVERNANCE-RULE-ERROR-FIX-FIELD-01 and returns diagnostics. It does NOT call t.Errorf.
Two properties:
- Fix-arg non-empty: every finding-constructor call must pass a resolvable, non-empty remediation string as its LAST positional argument.
- Construction funnel: no raw ValidationResult{} composite literal may appear in the governance package outside locator.go.
Not registered (register=none): gocell-internal-layout funnel; vacuous-green/ false-red in an external module; enforced in GoCell via TestGovernanceRuleErrorFixField.
func CheckGovernanceRulesRegistrationGuard ¶
func CheckGovernanceRulesRegistrationGuard(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckGovernanceRulesRegistrationGuard runs GOVERNANCE-RULES-REGISTRATION-GUARD-01 and returns diagnostics. It does NOT call t.Errorf.
Verifies set equality: declared == registered, where:
- declared = *Validator methods with signature func() []ValidationResult AND name prefix in {validate, checkDEP, checkCH}.
- registered = method names extracted from the allRules package-level var in rules_registry.go.
Not registered (register=none): gocell-internal-layout funnel; vacuous-green/ false-red in an external module; enforced in GoCell via TestGovernanceRulesRegistrationGuard.
AI-robust grading (INV-1):
- downstream Hard: every allRules Detect must resolve to a real *Validator method expression — a typo'd method expression fails to compile.
- upstream Medium: declared==registered set equality is archtest. The naming prefix {validate,checkDEP,checkCH} is a naming convention (Soft tier); the prefix filter is kept at the same tier as the pre-M3 validate* filter.
func CheckHandlerDeclCover ¶
func CheckHandlerDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckHandlerDeclCover enforces HANDLER-DECL-COVER-01: Every concrete type in cells/* + examples/* that implements a generated contracts/http/.../Service interface must trace to an existing contracts/<id-path>/contract.yaml with lifecycle: active.
Not registered (register=none): gocell-internal-layout/schema funnel; vacuous-green/false-red in an external module; enforced in GoCell via TestHandlerDeclCover.
func CheckImplDeclCover ¶
func CheckImplDeclCover(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckImplDeclCover enforces IMPL-DECL-COVER-01: Production Go files under cells/<A>/... must not import packages from a different cell cells/<B>/... unless the import path is under cells/<B>/<B>test/ (the public test helper boundary).
Portable cell-architecture rule (cross-cell import boundary). NOT registered in StandardCellRules in this batch to preserve behavior-zero-change (registration adds new external-consumer runtime behavior beyond the module-path-agnostic migration); StandardCellRules registration is tracked holistically by epic #1302 (checklist item "凡适用外部仓库的规则进入 StandardCellRules()"). Enforced in GoCell via TestImplDeclCover.
func CheckInsecureSkipVerifyLiteral ¶
func CheckInsecureSkipVerifyLiteral(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckInsecureSkipVerifyLiteral enforces INSECURE-SKIP-VERIFY-LITERAL-01: no production file outside framework/runtime/http/tlsutil may use the composite-literal field InsecureSkipVerify: true. It scans the running module's full production tree and returns the diagnostics it observes; GoCell's TestInsecureSkipVerifyLiteral calls it directly — single source, no parallel rule body.
func CheckKernelCellDoesNotImportRuntime ¶
func CheckKernelCellDoesNotImportRuntime(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckKernelCellDoesNotImportRuntime scans the kernel/cell package to confirm it imports neither runtime/* nor adapters/*. Returns diagnostics for each forbidden import found. Not registered in StandardCellRules (gocell-internal layout check, vacuous for external repos).
func CheckKernelCellRegistrarDefinedHere ¶
func CheckKernelCellRegistrarDefinedHere(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckKernelCellRegistrarDefinedHere confirms that the Registrar interface type is declared in kernel/cell (not aliased from another package). Not registered in StandardCellRules (gocell-internal layout check).
func CheckKernelClockLeafFallback ¶
func CheckKernelClockLeafFallback(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckKernelClockLeafFallback runs KERNEL-CLOCK-LEAF-FALLBACK-01 against the go.work workspace production scope and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
func CheckKernelClockResetRelativeProd ¶
func CheckKernelClockResetRelativeProd(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckKernelClockResetRelativeProd runs KERNEL-CLOCK-RESET-RELATIVE-PROD-01 against the go.work workspace production scope and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
func CheckMQTTClientIDNamespace ¶
func CheckMQTTClientIDNamespace(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckMQTTClientIDNamespace runs the MQTT-CLIENT-ID-NAMESPACE-01 A2 (composite literal) + A2b (field write) + A3 (alias) scans (the A1 field-freeze is reflect-based and lives in the dogfood Test).
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
func CheckMQTTConfigSeal ¶
func CheckMQTTConfigSeal(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckMQTTConfigSeal runs the MQTT-CONFIG-SEALED-FIELD-FROZEN-01 A2 (composite literal) + A2b (field write) construction scans (the A1 reflect field-freeze lives in the dogfood Test). Together they lock in-package non-zero Config construction to NewConfig + the With* option constructors; outside-package construction is already impossible at compile time via the unexported fields the A1 freeze pins.
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
func CheckMQTTReasonNameRedaction ¶
func CheckMQTTReasonNameRedaction(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckMQTTReasonNameRedaction runs the REASON-NAME-REDACTION-01 scan and returns its diagnostics.
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — this dogfood-only rule targets a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
func CheckMQTTTopicNamespace ¶
func CheckMQTTTopicNamespace(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckMQTTTopicNamespace runs the MQTT-TOPIC-NAMESPACE-01 A2 (composite literal) + A2b (field write) + A3 (alias) scans (the A1 field-freeze is reflect-based and lives in the dogfood Test).
register=no — gocell-internal-layout (scans adapters/mqtt), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via the per-rule Tests).
func CheckMetadatatestImportScope ¶
func CheckMetadatatestImportScope(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckMetadatatestImportScope enforces METADATATEST-IMPORT-SCOPE-01: no production (.go non-_test.go) file may import metadatatest. It is the importable rule body — GoCell's TestMetadatatestImportScope calls it via dogfood, single source.
func CheckNoDeletedAuthSymbols01 ¶
func CheckNoDeletedAuthSymbols01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckNoDeletedAuthSymbols01 runs NO-DELETED-AUTH-SYMBOLS-01 over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestNO_DELETED_AUTH_SYMBOLS_01 calls it directly — single source, no parallel rule body.
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
func CheckNotFoundTestStrict ¶
func CheckNotFoundTestStrict(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckNotFoundTestStrict enforces POSTGRES-NOTFOUND-TEST-OTHER-ERROR-MIXUP-ARCHTEST-01 module-wide and returns diagnostics for every violation found. The caller should pass the results to Report(t, "POSTGRES-NOTFOUND-TEST-OTHER-ERROR-MIXUP-ARCHTEST-01", diags).
func CheckOutboxHandleResultFactoryPreferred01 ¶
func CheckOutboxHandleResultFactoryPreferred01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckOutboxHandleResultFactoryPreferred01 enforces OUTBOX-HANDLERESULT-FACTORY-PREFERRED-01 downstream: production code (non-_test.go) in the running module must not construct outbox.HandleResult{...} composite literals outside the files in handleResultLiteralAllowlist.
It scans the running module's production code (Production → findModuleRoot), covering tag-gated files via cfg.BuildTags, and returns the diagnostics it observes. It does NOT call t.Errorf — the caller (Report / RunStandardCellRules) does that.
scanner.Canonical deduplicates the overlap between the default-tag pass and the tagged pass (identical to CheckScaffoldDerivedForceOverwrite's contract).
func CheckOutboxReconstructionCaller01 ¶
func CheckOutboxReconstructionCaller01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckOutboxReconstructionCaller01 enforces the FORWARD leg of OUTBOX-RECONSTRUCTION-CALLER-01: every production reference to outbox.UnmarshalEnvelope and (outbox.EntryScan).ToEntry must occur in a sanctioned site — a GoCell platform package (isGoCellPlatformPkgPath) whose module-relative path is listed in reconstructionFunnelAllowlist. This is the portable, externally-runnable half of the rule.
The REVERSE leg — anti-vacuity / no-stale-allowlist (every allowlisted file must still host a live reference) — is a GoCell-internal self-check (checkReconstructionAntiVacuity, exercised by the _test.go dogfood), NOT part of this Check: it reasons about GoCell's own allowlist hygiene, which is meaningless in an external consumer module.
It scans the running module's production code (Production → findModuleRoot), covering tag-gated files via cfg.BuildTags, and returns the diagnostics it observes without calling t.Errorf (the caller — typically Report or RunStandardCellRules — funnels diagnostics uniformly).
External Cell repo semantics: the allowlisted infra files live in GoCell's own adapters/runtime packages, not in a consumer's own source. A clean consumer module has zero references to either funnel in its own code → vacuous-green, no false positives.
func CheckOutboxTopicFailopen01 ¶
func CheckOutboxTopicFailopen01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckOutboxTopicFailopen01 enforces OUTBOX-TOPIC-FAILOPEN-01: an outbox.Entry composite literal whose Topic or EventType string constant matches one of the security-sensitive prefixes must not set FailurePolicy: outbox.FailurePolicyFailOpen.
It scans the running module's production code (Production → findModuleRoot), covering tag-gated files via cfg.BuildTags, and returns the diagnostics it observes. It does NOT call t.Errorf — the caller (Report / RunStandardCellRules) does that.
External Cell repo: this rule is deliberately NOT registered in StandardCellRules (see the OUTBOX-TOPIC-FAILOPEN-01 section of this file's package godoc). kernel/outbox.Entry is sealed — a populated outbox.Entry{...} composite literal outside kernel/outbox is a compile error (OUTBOX-ENTRY-SEALED-CONSTRUCTION-01) — so the production scan is vacuous in GoCell AND in any external repo; the rule's real coverage is the fixturetest/outbox fixtures, exercised by the dogfood sub-tests. This exported Check exists for that single-source fixture reuse + PlatformModulePath parameterization, NOT to gate external consumer production code.
func CheckPGRepoAmbientTx ¶
func CheckPGRepoAmbientTx(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckPGRepoAmbientTx enforces PG-REPO-AMBIENT-TX-01 module-wide and returns diagnostics for every violation found. The caller should pass the results to Report(t, "PG-REPO-AMBIENT-TX-01", diags).
R1/R2 are file-extension-scoped global predicates; R3/R4 are global predicates over all production files. See package godoc for full rule specification.
func CheckPGRepoApprovedSealed ¶
func CheckPGRepoApprovedSealed(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckPGRepoApprovedSealed enforces the pgrepoapproved.Approval interface seal and returns diagnostics for any violation. The caller should pass the results to Report(t, "PG-REPO-APPROVED-SEALED", diags). Seal violations are returned as []Diagnostic (via scanSealedInterface) rather than emitted with t.Errorf, so the whole rule routes through Report — file:line anchored, sorted, deduped.
func CheckPanicRegistered ¶
func CheckPanicRegistered(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckPanicRegistered runs PANIC-REGISTERED-01 over the workspace production package set and returns its diagnostics. It is the importable CellRule body wrapped by StandardCellRules; GoCell's TestPanicRegistered calls it directly so the gate has a single source. The scan SCOPE is Production(...) — the workspace-aware scope that expands to one ./<dir>/... per go.work member (typeseval.LoadProductionPackages), so satellite modules (cmd/, adapters/, examples/, corecells/, cellmodules/) are scanned; for an external single-module cell Production() reduces to that module via workspace.Modules' single-module fallback. cfg.BuildTags supplies the build tags for the second pass so panics behind the consumer's //go:build directives are scanned too.
Before #2148 this used Typed(./...), which at GoCell's module-less workspace root resolves to zero packages — leaving the entire production tree silently unscanned (vacuous). Converged onto the same Production() scope its sibling TestPanicLogRedact already uses.
The default build config is always scanned; when cfg.BuildTags is non-empty a second Production(...) load covers files behind those tags, deduped by "rel:line:reason". GoCell's dogfood passes FlatNonDefaultTags(); an external repo passes its own production tags. See the long note in the prior TestPanicRegistered body / ADR 202605190000 §Alternatives for why a single union load is insufficient.
func CheckParserMatcherExamplesSymmetry01 ¶
func CheckParserMatcherExamplesSymmetry01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckParserMatcherExamplesSymmetry01 runs PARSER-MATCHER-EXAMPLES-SYMMETRY-01 over the running module and returns diagnostics. It does NOT call t.Errorf; the caller should funnel results through Report(t, "PARSER-MATCHER-EXAMPLES-SYMMETRY-01", ...).
Not registered in StandardCellRules: gocell-internal-layout funnel; vacuous- green/false-red in an external module; enforced in GoCell via TestParserMatcherExamplesSymmetry01.
func CheckProdClockInjection ¶
func CheckProdClockInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckProdClockInjection runs PROD-CLOCK-INJECTION-01 against the go.work workspace production scope and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
func CheckProdMainWiringNoopReject ¶
func CheckProdMainWiringNoopReject(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckProdMainWiringNoopReject enforces PROD-MAIN-WIRING-NOOP-REJECT-01: no production composition-root package listed in cfg.ProductionMainPkgs may directly construct a forbidden raw-noop sink. It returns the diagnostics it observes without calling t.Errorf (the caller — Report / RunStandardCellRules — funnels diagnostics uniformly).
It scans the workspace Production package set (go.work-aware, so the separate cmd/corebundle module is reachable) and keeps only Passes whose package dir matches a declared main-pkg pattern. cfg.BuildTags drives a second tagged scan so a sink behind //go:build <tag> is not missed; scanner.Canonical dedups the overlap.
Fail-closed on a no-op gate: every declared pattern that matched NO production package across BOTH scans yields a diagnostic (prodMainUnmatchedPatternDiags). A 0-match pattern — a typo, a path a refactor moved, a pattern written relative to the wrong root, or a deliberately-unsupported whole-module ("." / "./...") or absolute form (see matchesMainPkg) — would otherwise be a SILENT false green: the consumer believes the guard is active while it scanned nothing. This closes the vacuous-green hole (#1942 review cluster C1).
func CheckProjectionApplyHookFunnel01 ¶
func CheckProjectionApplyHookFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckProjectionApplyHookFunnel01 enforces PROJECTION-APPLY-HOOK-FUNNEL-01 downstream: Coordinator.Subscribe in any production package may be called only from kernel/projection/ itself, _test.go files, or the single bootstrap projection drain (runtime/bootstrap/phases_projection.go).
It scans the running module's production code (Production → findModuleRoot), covering tag-gated files via cfg.BuildTags, and returns the diagnostics it observes.
External Cell repo semantics (this rule is registered in StandardCellRules): the sanctioned drain and kernel/projection/ live in GoCell's platform module, not in a consumer module — the rule degrades to a pure ban there. A clean external repo with no Coordinator.Subscribe calls is vacuous-green.
func CheckProjectionEventJournalAppendCaller01 ¶
func CheckProjectionEventJournalAppendCaller01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckProjectionEventJournalAppendCaller01 runs the rule over the running module and returns its diagnostics (production dogfood single source).
func CheckRMQChannelDestructionViaConn ¶
func CheckRMQChannelDestructionViaConn(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRMQChannelDestructionViaConn runs the RMQ-CHANNEL-DESTRUCTION-VIA-CONN-01 production scan and returns all diagnostics.
cfg is unused: adapters/rabbitmq has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestRMQChannelDestructionViaConn01).
func CheckRMQChannelMaxPerConn ¶
func CheckRMQChannelMaxPerConn(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRMQChannelMaxPerConn runs the RMQ-CHANNEL-MAX-PER-CONN-01 production scan (sub-rules A/B/C) and returns all diagnostics.
cfg is unused: adapters/rabbitmq has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestRMQChannelMaxPerConn01_*).
func CheckRMQPublisherFailureHandling ¶
func CheckRMQPublisherFailureHandling(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRMQPublisherFailureHandling runs the RMQ-PUBLISHER-FAILURE-HANDLING-01 production scan (sub-rules A/B/C/D) and returns all diagnostics.
cfg is unused: adapters/rabbitmq has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestRMQPublisherFailureHandling01_*).
func CheckRMQPublisherReleasesChannel ¶
func CheckRMQPublisherReleasesChannel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRMQPublisherReleasesChannel runs the RMQ-PUBLISHER-RELEASES-CHANNEL-01 production scan and returns all diagnostics.
cfg is unused: adapters/rabbitmq has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestRMQPublisherReleasesChannel01).
func CheckRMQStopIntakeInflightWait ¶
func CheckRMQStopIntakeInflightWait(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRMQStopIntakeInflightWait runs the RMQ-STOPINTAKE-INFLIGHT-WAIT-01 production scan and returns all diagnostics.
cfg is unused: adapters/rabbitmq has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans adapters/rabbitmq), NOT in StandardCellRules() and NOT promised to run externally — these dogfood-only rules target a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestRMQStopIntakeInflightWait01_*).
func CheckReconstituteUserCallerAllowlist01 ¶
func CheckReconstituteUserCallerAllowlist01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckReconstituteUserCallerAllowlist01 runs RECONSTITUTE-USER-CALLER-01 over the running module and returns its diagnostics.
This is the importable CellRule body. GoCell's own Test* functions in reconstitute_user_caller_allowlist_test.go call the same detectors — single source, no parallel rule body.
The rule is intentionally NOT registered in StandardCellRules: it reasons about GoCell's own internal package layout (accesscore domain/mem/postgres), making it vacuous-green or false-red for an external module.
func CheckRefreshAmbientTX01 ¶
func CheckRefreshAmbientTX01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckRefreshAmbientTX01 runs REFRESH-AMBIENT-TX-01 over the running module and returns its diagnostics. GoCell's own TestRefreshAmbientTX01 calls it directly — single source.
func CheckRefreshCrossStoreTX01 ¶
func CheckRefreshCrossStoreTX01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckRefreshCrossStoreTX01 runs REFRESH-CROSS-STORE-TX-01 over the running module and returns its diagnostics. GoCell's own TestRefreshCrossStoreTX01 calls it directly — single source.
func CheckRefreshInvalidIndexSingleSource01 ¶
func CheckRefreshInvalidIndexSingleSource01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckRefreshInvalidIndexSingleSource01 runs REFRESH-INVALID-INDEX-SINGLE-SOURCE-01 over the running module and returns its diagnostics. GoCell's own TestRefreshInvalidIndexSingleSource01 calls it directly — single source.
func CheckRefreshRevokeUserFunnel01 ¶
func CheckRefreshRevokeUserFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckRefreshRevokeUserFunnel01 runs REFRESH-REVOKE-USER-FUNNEL-01: refresh.Store.RevokeUser must only be called from the credentialinvalidate funnel or store implementations.
func CheckReplaydepsInmemFunnel01 ¶
func CheckReplaydepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckReplaydepsInmemFunnel01 scans the hardened composition roots for direct calls to the in-memory replay constructors and returns one Diagnostic per offending callsite. It is the single rule body — the Test* dogfoods it via Report — so the exact scan is the one enforced.
func CheckRequiredDepNilGuardA2 ¶
func CheckRequiredDepNilGuardA2(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRequiredDepNilGuardA2 verifies that in every service.go (in slices/ or internal/ directories) whose Service struct has a gocell:"required" field, every NewXxx constructor calls validateRequired() exactly once, after the options loop, consuming the error canonically.
Not registered in StandardCellRules: see file godoc.
func CheckRequiredDepNilGuardA3 ¶
func CheckRequiredDepNilGuardA3(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRequiredDepNilGuardA3 verifies that no hand-written service.go calls validation.IsNilInterface to guard a gocell:"required" field.
Not registered in StandardCellRules: see file godoc.
func CheckRequiredDepNilGuardA4 ¶
func CheckRequiredDepNilGuardA4(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckRequiredDepNilGuardA4 verifies that all gocell struct tag values in production source are in {"", "required"}.
Not registered in StandardCellRules: see file godoc.
func CheckSafeIDUpstreamFunnelHard01 ¶
func CheckSafeIDUpstreamFunnelHard01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckSafeIDUpstreamFunnelHard01 runs SAFEID-UPSTREAM-FUNNEL-HARD-01 over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestSAFEIDUpstreamFunnelHard01 calls it directly — single source, no parallel rule body.
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
func CheckSafeIDWireMessageUsage01 ¶
func CheckSafeIDWireMessageUsage01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSafeIDWireMessageUsage01 runs SAFEID-WIREMESSAGE-USAGE-01 over the running module and returns its diagnostics. It is the importable rule body; GoCell's TestSAFEIDWireMessageUsage01 calls it directly — single source, no parallel rule body.
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
func CheckSagaConstructorNilGuard ¶
func CheckSagaConstructorNilGuard(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaConstructorNilGuard is the importable form of SAGA-CONSTRUCTOR-NIL-GUARD-01. Not registered in StandardCellRules.
func CheckSagaCoordinatorNoHeartbeatLoop ¶
func CheckSagaCoordinatorNoHeartbeatLoop(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaCoordinatorNoHeartbeatLoop is the importable form of SAGA-COORDINATOR-NO-HEARTBEAT-LOOP-01. It scans runtime/saga/ production files (excluding runtime/saga/executor/) for any .Heartbeat SelectorExpr whose receiver implements the Heartbeater-shape signature. Not registered in StandardCellRules (targets GoCell's own runtime/saga).
func CheckSagaDriveBehindLeaderGate ¶
func CheckSagaDriveBehindLeaderGate(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaDriveBehindLeaderGate is the importable form of SAGA-DRIVE-BEHIND-LEADER-GATE-01. Not registered in StandardCellRules.
func CheckSagaExecutorRandInjected ¶
func CheckSagaExecutorRandInjected(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaExecutorRandInjected is the importable form of SAGA-EXECUTOR-RAND-INJECTED-01. It scans runtime/saga/executor/ production files for package-level global rand calls. Not registered in StandardCellRules (targets GoCell's own runtime/saga/executor).
func CheckSagaJournalHolderSeal ¶
func CheckSagaJournalHolderSeal(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaJournalHolderSeal is the importable form of SAGA-JOURNAL-HOLDER-SEAL-01. Not registered in StandardCellRules.
func CheckSagaMetricLabelValuesFrozen ¶
func CheckSagaMetricLabelValuesFrozen(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaMetricLabelValuesFrozen is the importable form of SAGA-METRIC-LABEL-VALUES-FROZEN-01. It covers the A2 callsite/assignment guard only: it enforces that no production code reaches a metric label via an inline literal or a raw string(reason) conversion. The A1 enum-value-set- frozen check (enumerating declared consts vs the sagaLabelEnumWant golden) is GoCell-internal (golden-bound via sagaLabelEnumWant) and intentionally stays in TestSagaMetricLabelValuesFrozen01, not exported. Not registered in StandardCellRules.
func CheckSagaProjectionDepsInmemFunnel01 ¶
func CheckSagaProjectionDepsInmemFunnel01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckSagaProjectionDepsInmemFunnel01 scans the running module's wiring roots for direct calls to distlock.NewInProcessDriver outside the sanctioned resolver dir and returns one Diagnostic per offending callsite. It is a thin wrapper over scanSagaProjectionDepsInmemFunnel rooted at findModuleRoot(t); the same scan core is exercised against a synthetic temp tree by the funnel tests so scope + allowlist + diagnostic assembly are proven (not just the selector helper). Dogfooded by the Test* via Report so the exact scan is the one enforced.
func CheckSagaSlogInstanceFieldsCaller ¶
func CheckSagaSlogInstanceFieldsCaller(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaSlogInstanceFieldsCaller is the importable form of SAGA-SLOG-INSTANCE-FIELDS-CALLER-01. It runs both sub-checks:
- A1: guarded keys (instance_id, lease_id) must only appear inside the sagalog.InstanceFields carrier body (scanSagaSlogInstanceFieldsFile).
- B4: slog.Attr composite literals with guarded keys are banned outside the carrier body (scanSagaSlogAttrLiteralsFile).
Not registered in StandardCellRules.
func CheckSagaStepCompensatePure ¶
func CheckSagaStepCompensatePure(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaStepCompensatePure is the importable, registered form of SAGA-STEP-COMPENSATE-PURE-01. It scans the running module's production code and enforces the COMPLETE invariant (not just the direct forbidden-call scan), so an external Cell repo gets the same escape-hatch guards GoCell dogfoods:
- A1: CompensateFunc bodies must not call forbidden persistent interfaces (outbox.Writer, outbox.Emitter, persistence.TxRunner, *sql.Tx, pgx.Tx).
- B1: a CompensateFunc literal body must not exceed sagaMaxCompensateStmts top-level statements (long bodies can hide helper-indirected side effects).
- B2: no reflect MethodByName dispatch inside a CompensateFunc body.
- B3: a CompensateFunc value must not be passed as a function argument (outside the sanctioned safeRunCompensate transport funnel).
Consumer-scan contract (matches the other registered CellRules, e.g. errcode): it scans the consumer's DEFAULT build config first, then re-scans with cfg.BuildTags when non-empty, de-duplicating by (Rel, Line, Message). This catches a violation behind a //go:build tag without false-red on tag-only files, and a false-green on default-build files that a hardcoded foreign tag union would miss. GoCell's dogfood passes FlatNonDefaultTags() as cfg.BuildTags.
Registered in StandardCellRules.
func CheckSagaStepRunOutsideTx ¶
func CheckSagaStepRunOutsideTx(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaStepRunOutsideTx is the importable form of SAGA-STEP-RUN-OUTSIDE-TX-01. Not registered in StandardCellRules.
A1 (StepFunc-call confinement to safeRun) runs per file; A2 (no call transitively reaching safeRun inside a RunInTx closure) is built over all loaded units together, so the typed taint set spans the coordinator and executor packages in a single load.
Residuals (manual taint, no go/ssa): a func literal passed as a function PARAMETER and called via that parameter, a method-value-valued var, and RunInTx detection by method name. All documented in the SAGA-STEP-RUN-OUTSIDE -TX-01 INVARIANT godoc; the true-Hard alternative (StepContext/TxContext capability split) is tracked + rejected in gh #1997.
func CheckSagaTailerCheckpointAdvancerCaller ¶
func CheckSagaTailerCheckpointAdvancerCaller(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaTailerCheckpointAdvancerCaller is the importable form of SAGA-TAILER-CHECKPOINT-ADVANCER-CALLER-01.
Locks OwnerCheckpointStore.AdvanceIfOwner to a sanctioned caller set in runtime/saga/tailer: (*Tailer).commitEvent (clean apply) and (*Tailer).skipPoisonEvent (poison-event dead-letter skip, #2110). The scan runs over the WHOLE production tree (Production scope), so a different production package that holds an OwnerCheckpointStore and advances the checkpoint directly is caught — the declared scope (repo-wide runtime production) now matches the execution scope (F4; the previous form scanned only runtime/saga/tailer). EachInSubtree descends into FuncLit closures (the actual call is inside a RunInTx closure), attributing enclosed calls to the enclosing FuncDecl. Test-support packages whose purpose is to exercise the method (conformance suites) are excluded via sagaTailerAdvancerExemptPkgs; _test.go files are skipped inside the scan.
AI-robust rating:
- Downstream Hard: go/types caller-allowlist — pkg path + method identity resolved via info.ObjectOf; import alias cannot bypass the pkg path check; the package-qualified sanctioned-caller key means a same-named type/method in another package cannot inherit the exemption.
- Upstream Medium: Go visibility ceiling — AdvanceIfOwner is a public method on a public interface; Go cannot prevent non-Tailer types from holding an OwnerCheckpointStore and calling AdvanceIfOwner directly. Hard upgrade path: sealed OwnerCheckpointStore handle that only Tailer can hold, tracked at gh #1612.
Not registered in StandardCellRules (targets GoCell-internal tailer package).
func CheckSagaTailerDeadLetterWriter ¶
func CheckSagaTailerDeadLetterWriter(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaTailerDeadLetterWriter is the importable form of SAGA-TAILER-DEAD-LETTER-WRITER-01.
Locks projection.DeadLetterStore.Record so the sole runtime callsite is (*Tailer).skipPoisonEvent in runtime/saga/tailer (#2110). This keeps the dead-letter record bound to the same RunInTx as the checkpoint advance ("skipped ⟺ recorded"): a record written from anywhere else would not be paired with the fenced advance.
AI-robust rating (mirrors SAGA-TAILER-CHECKPOINT-ADVANCER-CALLER-01):
- Downstream Hard: go/types caller-allowlist — pkg path + method identity via info.ObjectOf; package-qualified key, alias-proof.
- Upstream Medium: Go visibility ceiling — Record is a public method on a public interface; Go cannot prevent another type from holding a DeadLetterStore and recording directly. Hard upgrade path shares the sealed OwnerCheckpointStore-handle track (gh #1612).
Not registered in StandardCellRules (targets GoCell-internal tailer package).
func CheckScaffoldDerivedForceOverwrite ¶
func CheckScaffoldDerivedForceOverwrite(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckScaffoldDerivedForceOverwrite enforces SCAFFOLD-DERIVED-FORCEOVERWRITE-01 downstream: pathsafe.DerivedOverwrite in any production package may be referenced only as a direct call from planDerivedArtifact (tools/codegen/cellgen/stage_render.go). It scans the running module's production code (Production → findModuleRoot), covering tag-gated files via cfg.BuildTags, and returns the diagnostics it observes.
External Cell repo semantics (this rule is registered in StandardCellRules): the sanctioned planDerivedArtifact site lives in GoCell's own cellgen and does not exist in a consumer module, so the allowlist never matches there — the rule degrades to a PURE BAN. That is intended: pathsafe.DerivedOverwrite is a GoCell platform-internal codegen primitive with no valid use in external Cell code; a clean external repo simply has zero references (vacuous-green).
func CheckScaffoldListenerMarkerTypedConst ¶
func CheckScaffoldListenerMarkerTypedConst(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckScaffoldListenerMarkerTypedConst enforces SCAFFOLD-LISTENER-MARKER-TYPED-CONST-01: it verifies the cellgen.ListenerMarker typed const and the scaffold-cell template's use of {{.ListenerMarker}}, returning the diagnostics it observes.
func CheckSecurityDefaults ¶
func CheckSecurityDefaults(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckSecurityDefaults runs SEC-FAIL-CLOSED-02..10 and returns all violations as []Diagnostic, each message prefixed with its precise sub-rule id (so the aggregate surface stays self-describing even when an external caller Reports the whole slice under one id). The dogfood TestSecurityDefaults instead Reports each sub-rule separately for native per-id output. It does not call t.Errorf; callers use Report to surface the result. t.Fatalf is used only for infrastructure load failures.
SEC-FAIL-CLOSED-01 is skipped (retired; see security_defaults_test.go).
func CheckSeedRoleIface01 ¶
func CheckSeedRoleIface01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckSeedRoleIface01 runs SEED-ROLE-IFACE-01 over the running module and returns its diagnostics. It scans every production .go file (entire module, excluding _test.go, generated, vendor, worktrees, testdata, internal/mem itself) and reports any file that imports corecells/accesscore/internal/mem AND references mem.RoleRepository in any AST position. It is the importable rule body; GoCell's TestSEED_ROLE_IFACE_01 calls it directly — single source, no parallel rule body.
func CheckServiceownedHandlerOwnerCheck01 ¶
func CheckServiceownedHandlerOwnerCheck01(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckServiceownedHandlerOwnerCheck01 runs B1+B2+B3 predicates of SERVICEOWNED-HANDLER-OWNER-CHECK-01 against the running module and returns diagnostics. It does NOT call t.Errorf; the caller should funnel results through Report(t, "SERVICEOWNED-HANDLER-OWNER-CHECK-01", ...).
Not registered in StandardCellRules: gocell-internal-layout funnel; vacuous-green/false-red in an external module; enforced in GoCell via TestSERVICEOWNED_HANDLER_OWNER_CHECK_01.
func CheckSessionProtocolCompositionRoot01 ¶
func CheckSessionProtocolCompositionRoot01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSessionProtocolCompositionRoot01 runs SESSION-PROTOCOL-COMPOSITION-ROOT-01 over the running module and returns its diagnostics.
session.NewProtocol may only be invoked from cmd/* (composition root) or runtime/auth/session/* (the package itself + storetest helpers). Cells, runtime/* (non-session), adapters/*, and tests outside session/* must receive an injected *session.Protocol — not construct one.
cmd/ and examples/ are intentionally outside the scan scope:
- cmd/* is the composition root by definition — wiring authority owns session.NewProtocol construction.
- examples/* each carry their own composition root; allowing them mirrors the AUTH-PLAN-04 / LAYER-09 carve-out for example projects.
func CheckSessionrefreshNoSessionCreate01 ¶
func CheckSessionrefreshNoSessionCreate01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSessionrefreshNoSessionCreate01 runs SESSIONREFRESH-NO-SESSION-CREATE-01 over the running module and returns its diagnostics. GoCell's own TestSessionrefreshNoSessionStoreMutation_01 calls it directly — single source.
func CheckSvctokenCallerCellRequired01 ¶
func CheckSvctokenCallerCellRequired01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSvctokenCallerCellRequired01 runs SVCTOKEN-CALLER-CELL-REQUIRED-01 over the running module and returns its diagnostics: every call to auth.GenerateServiceToken must pass a valid cell-ID string literal as its second argument (callerCell). It is the importable rule body; GoCell's TestSVCTOKEN_CALLER_CELL_REQUIRED_01 dogfoods it directly — single source, no parallel rule body.
Not registered in StandardCellRules: allowlist is gocell-hardcoded with no ConfigForExternalCell consumer-extension → would false-red an external cell's own auth code; kept importable & module-path-agnostic but vacuous-green externally.
func CheckTLSTestMaterialFunnel ¶
func CheckTLSTestMaterialFunnel(t *testing.T) []Diagnostic
CheckTLSTestMaterialFunnel enforces TLS-TEST-MATERIAL-FUNNEL-01 over the whole workspace. It scans the production tree with test variants under both the default build context and FlatNonDefaultTags() (integration / mqtt_tls etc.) and returns the union of diagnostics; GoCell's TestTLSTestMaterialFunnel calls it directly — single source, no parallel rule body.
func CheckTagGroupLoopForbidsTypedRun ¶
func CheckTagGroupLoopForbidsTypedRun(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckTagGroupLoopForbidsTypedRun runs TAGGROUP-LOOP-FORBIDS-TYPED-RUN-01 against tools/archtest/*_test.go (direct children only) and returns its diagnostics. GoCell's TestTagGroupLoopForbidsTypedRun calls this directly — single source, no parallel rule body. cfg.BuildTags is threaded into the scan's TypedOpts.Tags so files behind build directives are not missed.
NOTE: This is a META rule — it governs how the archtest package itself uses typed-scope Run and KnownNonDefaultTags, so its scan scope is internal to GoCell (tools/archtest). It is intentionally NOT added to StandardCellRules() because external Cell repos do not contain archtest code; the rule would be vacuously green (empty scan set) and provide no value to external consumers. The symbol is exported only so the rule library is uniformly addressable — an external repo that calls it directly is likewise vacuously green, since the scan target ./tools/archtest/... does not exist outside GoCell. cfg is consumed for BuildTags only; the scan scope is fixed to GoCell's own archtest package.
func CheckTestwaitExternalReasonLiteral ¶
func CheckTestwaitExternalReasonLiteral(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckTestwaitExternalReasonLiteral enforces TEST-POLLING-EXTERNAL-REASON-LITERAL-01 module-wide and returns diagnostics for every violation found. The caller should pass the results to Report(t, "TEST-POLLING-EXTERNAL-REASON-LITERAL-01", diags).
Two-load coverage (mirror PANIC-REGISTERED-01 plumbing): Load 1 (tags=nil) catches reverse build directives; Load 2 (FlatNonDefaultTags) catches all forward-tagged files in one union. Test-variant load (Tests:true) included in both so *_test.go callers of testwait.External are scanned.
func CheckUserAuthzEpochBumpFunnel01 ¶
func CheckUserAuthzEpochBumpFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckUserAuthzEpochBumpFunnel01 runs USER-AUTHZ-EPOCH-BUMP-FUNNEL-01: ports.UserRepository.BumpAuthzEpoch must only be called from the credentialinvalidate funnel or repository implementations.
func CheckWebhookHMACFunnel ¶
func CheckWebhookHMACFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckWebhookHMACFunnel runs the WEBHOOK-HMAC-FUNNEL-01 production scan (A1/A2/A3/A4/B6) and returns all diagnostics.
cfg is unused: kernel/webhook has no build-tagged production files, so a single default-config scan is complete.
register=no — gocell-internal-layout (scans kernel/webhook), NOT in StandardCellRules() and NOT promised to run externally — this dogfood-only rule targets a package an external repo lacks, so a manual ExtraRules caller does not get a clean pass; migrated for unified PlatformModulePath parameterization + fork-safety, dogfooded via TestWebhookHMACFunnel).
func CheckYAMLQuoteFunnel ¶
func CheckYAMLQuoteFunnel(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckYAMLQuoteFunnel runs YAML-QUOTE-FUNNEL-01 over the running module's production code and returns its diagnostics. GoCell's TestYAMLQuoteFunnel calls it directly (single source, no parallel rule body). cfg.BuildTags is wired into the TypedOpts.Tags for the production scan.
Deliberately NOT registered in StandardCellRules: the rule only constrains conversions of the gocell-internal pkg/yamlsafe.Scalar type, so it is vacuous for an external Cell repo that never imports yamlsafe. It stays importable (non-test file, single source) for the dogfood Test, but external_repo wiring is out of scope — see external.go StandardCellRules godoc + M3 issue #1302.
func Run ¶
func Run(t testing.TB, scope RunScope, rule Rule) []Diagnostic
Run is the single rule-dispatch entry point. It executes rule over scope and returns the union of every rule invocation's diagnostics; pass the slice to Report with a rule ID. The t parameter is testing.TB (a *testing.T satisfies it) so the StandaloneModule fatal-path spy tests can drive Run with a tbFatalSpy.
scope selects the mode and source. Scope quick-pick:
- No go/types needed (pure AST) → AST
- Need go/types over the main module → Typed
- Same as Typed but generated/ excluded (hand-written-source rules) → Production
- Loading archtest_fixture-tagged fixture packages → Fixture
- Standalone testdata module with its own go.mod → StandaloneModule
All failure modes (nil scope, nil rule, module-root not found, load error, parse error, non-absolute StandaloneModule dir) fail-loud via t.Fatalf.
Typed rules read pass.TypesInfo (and pass.Pkg) for resolution; AST-only helpers (EachInSubtree etc.) work unchanged on pass.Files in either mode.
type FixtureOpts ¶
type FixtureOpts struct {
Tests bool
}
FixtureOpts is the option struct accepted by Fixture. It deliberately lacks a Tags field — the archtest_fixture build tag is supplied exclusively by the Run dispatch for a fixtureRunScope. Business callers therefore cannot express "load a fixture with a custom tag" at the type level; passing Tags would require dropping back to Typed / runTypedWithRoot, which is then caught upstream by PASS-FUNNEL-FIXTURE-TAG-01 (façade bypass closure).
This is the Hard-form upgrade of typed function choice: not only the constructor name (Typed vs Fixture) but the input struct field set (FixtureOpts has no Tags) participates in the type-system constraint. See AI-robust §Hard 范本 in .claude/rules/gocell/ai-robust.md.
type FuncDeclContext ¶
type FuncDeclContext = callresolver.FuncDeclContext
FuncDeclContext is the per-FuncDecl visitor context yielded by WalkFuncDecls / WalkFuncDeclsAST. Type alias to callresolver.FuncDeclContext so the façade and internal engine share one type (mirrors the ImportBan = scanner.ImportBan alias in resolve.go).
type ImportBan ¶
ImportBan describes a rule that forbids importing specific packages. It is a type alias for scanner.ImportBan; callers may use it interchangeably with the internal type. Use [ImportBan.Run] to execute the check against a Scope.
Re-exported so business archtest files that use ImportBan need only import "…/tools/archtest" — no direct import of internal/scanner required.
type Pass ¶
type Pass struct {
// Fset is the token.FileSet shared by every file in [Files]. Use
// pass.Fset.Position(node.Pos()) for human-readable line/column.
Fset *token.FileSet
// Files is the slice of parsed files for this Pass. In AST-only mode
// ([AST]) this is ALL in-scope files in a single Pass invocation; in
// typed mode ([Typed] / [Production] / [Fixture] / [StandaloneModule])
// this is the dedup'd set of files belonging to one loaded package.
// Note: AST-only mode delivers ALL files in ONE Pass — rule authors
// must iterate pass.Files and must not assume len(pass.Files)==1.
Files []*ast.File
// Pkg is the go/types package descriptor — exposes Name / Path / Imports /
// Scope. Nil in AST-only mode. Intentionally NOT [*packages.Package]:
// see the package-level Hard-line discussion in scope.go.
Pkg *types.Package
// TypesInfo is the go/types resolution table bound to [Files] / [Pkg].
// Nil in AST-only mode. Use with go/types-aware helpers (info.Types,
// info.Uses, info.Selections, etc.) for type-aware AST resolution.
TypesInfo *types.Info
// Rel returns the module-relative slash path for a file. The file pointer
// must come from [Files]; behavior is undefined for files from other Passes.
Rel func(*ast.File) string
// Abs returns the module-absolute (OS-native) path for a file. The file
// pointer must come from [Files]; behavior is undefined for files from other
// Passes. The returned value always satisfies filepath.IsAbs and equals
// pass.Fset.Position(f.Pos()).Filename — it is the same physical path used
// when computing pass.Rel(f). In AST-only mode ([AST]) both runAST and
// collectASTFiles set Abs from the same abs variable used to compute Rel,
// so the two accessors share a single source of truth with zero new state.
Abs func(*ast.File) string
}
Pass is the rule-execution context constructed by Run and passed to every Rule. It carries the AST file set, the parsed files, and (in typed mode) the go/types Package + TypesInfo bound to those files.
One-Pass-per-scope / one-Pass-per-package shape: an AST scope (AST) delivers a single Pass containing ALL files in scope; a typed scope (Typed / Production / Fixture / StandaloneModule) delivers one Pass per loaded package (test-variant packages are sorted first; dedup by *ast.File pointer identity ensures no file appears in two Passes). Rule authors always iterate pass.Files — never assume len(pass.Files)==1.
Authors MUST NOT construct *Pass directly; the only legitimate construction site is the Run driver in this file. This is enforced by:
- depguard rule archtest-no-direct-packages-load (banning external authors from importing internal/scanner / internal/typeseval / packages),
- meta-archtest PASS-FUNNEL-EACHFILE-01 / LOADPACKAGES-01 / PACKAGES-IMPORT-01 (re-detecting any bypass via type-aware *types.Info resolution).
The Pkg field is intentionally *types.Package (go/types stdlib) — NOT *packages.Package (golang.org/x/tools/go/packages). Authors cannot reach .Syntax from *Pass and therefore cannot reconstruct the INV-1 bug class (pairing AST nodes from one load with a *types.Info from a different load). See docs/architecture/202605141519-adr-archtest-pass-funnel.md §Hard line.
func (*Pass) IsFileInScope ¶
IsFileInScope reports whether f should be processed under the standard build context (all GOOS/GOARCH + cgo + release tags, no project-private tags like "integration" or "archtest_fixture"). It delegates to typeseval.ParseBuildConstraint (extracts the //go:build / // +build directive from the file at pass.Abs(f)) and typeseval.BuildContextPredicate (the toolchain-default tag set).
Returns true when f has no build constraint, or when its constraint evaluates to true under the default predicate. Returns false for files gated by project-specific tags (e.g. "integration", "e2e", "archtest_fixture").
For files that need evaluation under a custom extra-tag set (e.g. "integration"), use archtest.BuildContextPredicate with archtest.ParseBuildConstraint directly — IsFileInScope always uses the default (no-extra-tags) predicate.
f must come from pass.Files; behavior is undefined for files from other Passes.
func (*Pass) IsGenerated ¶
IsGenerated reports whether f is a codegen output file under the repo's generated/ tree. It delegates to typeseval.IsGeneratedRelPath on pass.Rel(f).
Returns true when the file's module-relative path begins with "generated/". Returns false (non-generated, conservative) when Pass.Rel(f) yields the absolute fallback path — i.e. when the file is outside the module root and filepath.Rel returns the absolute path unchanged; IsGeneratedRelPath will not match a "generated/" prefix on an absolute path. f must come from pass.Files; behavior is undefined for files from other Passes.
type Rule ¶
type Rule func(*Pass) []Diagnostic
Rule is the unit of work executed by Run. It receives a driver-constructed *Pass and returns the diagnostics it observed. Rules MUST be pure with respect to the test process (no goroutines, no file IO outside the supplied Pass) so multiple rules can share the same packages load via typeseval.SharedResolver without coordination.
type RunScope ¶
type RunScope interface {
// contains filtered or unexported methods
}
RunScope is the sealed descriptor of WHAT Run analyzes. Obtain a value ONLY from a scope constructor — AST (AST-only over a Scope), Typed (typed main-module patterns), Production (typed main module with <module>/generated/ excluded), Fixture (typed archtest_fixture-tagged packages), or StandaloneModule (typed standalone fixture module).
The sealing method RunScope declares is unexported, so no type OUTSIDE package archtest can implement it: an external Cell repo (or any non-archtest package) cannot forge a production/fixture scope, cannot inject a build tag into a fixture scope (the only public entry that loads fixture-tagged code is Fixture, whose body injects the tag), and cannot reconstruct any banned load shape at the call site. This is the Hard DOWNSTREAM / package-external leg of the seal.
In-package, the seal is Medium, NOT Hard: every GoCell archtest rule lives in package archtest (*_test.go), and Go package visibility cannot forbid a same-package file from constructing the unexported scope structs directly (e.g. `Run(t, typedRunScope{opts: TypedOpts{Tags: …}}, rule)`, which would sidestep Fixture's tag injection). That in-package leg is guarded by the RUNSCOPE-CONSTRUCTOR-FUNNEL-01 meta-archtest, which type-aware-bans scope-struct composite literals outside their five sanctioned constructors (it also closes the struct-literal→archtest_fixture bypass that PASS-FUNNEL-FIXTURE-TAG-01, being CallExpr-only, does not catch). The permanent Go-language ceiling (a same-package test CAN construct the struct; Go cannot make that a compile error) matches #851 / #893 / #1282 / #1424; the true-Hard upgrade (scope structs + Run dispatch behind tools/archtest/ internal/driver) is a deliberate won't-do tracked at gh #1485.
Separately, the "single Run entry, no other Run* export" surface property is guarded by the ARCHTEST-SINGLE-RUN-ENTRY-01 meta-archtest (Medium — Go cannot forbid declaring a new exported func).
RunScope is the rule-DISPATCH descriptor; it is distinct from Scope (= scanner.Scope), the file-ENUMERATION descriptor consumed by EachContentFile / [ImportBan.Run] / AST. AST adapts a Scope into a RunScope.
func AST ¶
AST wraps the file-enumeration Scope fs into a dispatch RunScope for AST-only rule execution. Run parses every file in fs into ONE shared token.FileSet and constructs ONE Pass containing all parsed files; Pass.Pkg and Pass.TypesInfo are nil.
For rules that need go/types resolution, use Typed; for production-only loads (generated/ excluded), Production; for standalone fixture modules, StandaloneModule.
func Fixture ¶
func Fixture(opts FixtureOpts, patterns []string) RunScope
Fixture builds a typed RunScope over packages tagged with the archtest_fixture build tag. It is the typed funnel for fixture-package archtest loading; all fixture-load sites across archtest *_test.go MUST use Run(t, Fixture(...), rule). The framework-internal exceptions are limited to files in passFunnelPermanentExempt (pass_funnel_test.go, pass_test.go, archtest_test.go in tools/archtest/), which call typeseval.SharedResolver directly because they implement or directly test the funnel machinery and cannot be expressed through Pass without circular dependency — see pass_funnel_test.go's passFunnelPermanentExempt godoc for the structural justification.
The archtest_fixture build tag is supplied exclusively by the Run dispatch for a fixtureRunScope (pass.go) — not by this constructor's caller. The unified Run accepts testing.TB; the fixture scope has no spy fatal-path requirement, so no caller distinction is needed. See ADR 202605141519 §Migration path Stage 4.
AI-robust (funnel double-lock):
- Outward Hard (business callers, downstream funnel side): FixtureOpts has no Tags field, and the unified Run takes a sealed RunScope (no tag parameter at all) — there is no public surface that carries a build tag.
- Cross-package Form D Hard (type system, #944): fixtureBuildTag is unexported, so business code outside package archtest cannot feed it to a loader — a compile error, not an archtest finding.
- Upstream Hard (façade bypass closure): PASS-FUNNEL-FIXTURE-TAG-01 archtest rejects any (callee, arg) pair where the callee resolves to a loader (archtest.runTypedWithRoot, or typeseval.SharedResolver / LoadPackages / LoadProductionPackages) AND any arg subtree contains an Expr that EvaluateConstString resolves to "archtest_fixture", OR an *ast.Ident bound (same file) to a slice literal carrying such an Expr. This catches the raw literal / same-pkg const Ident / BinaryExpr const-concat / same-file var-binding forms uniformly via the helper's resolution lattice (archtest-bound (callee, arg) form-uniqueness — isomorphic to charter §Hard 范本 第 2 条 panic(panicregister.Approved) form; see pass_funnel_test.go diagsFixtureTagBypass godoc for full evidence + accepted Blind spots).
- Inward Medium (framework internal): the field set of FixtureOpts itself is frozen by TestRun_Fixture_FixtureOptsLacksTagsField via reflect assertion (NumField == 1, sole field "Tests" of kind Bool) — drift here is a test failure, not a compile error.
func Production ¶
Production builds a typed RunScope over the main module's production package set ONLY — every package whose import path is under <module>/generated/ is excluded by the constructor. Choosing Production IS the typed choice that makes generated/ exclusion non-bypassable: a Pass it yields can never contain a generated/ file, and there is no patterns argument to widen the set.
"Production" here means GENERATED-excluded, NOT test-excluded: opts.Tests is still honored, so Production(TypedOpts{Tests: true}) loads each package's test variant (its *_test.go files) just like Typed — the "ONLY" qualifier is about the generated/ filter, not about *_test.go. Use Tests: false for a hand-written-non-test walk; Tests: true to also see test files.
Use this for rules that reason over hand-written source and must never observe codegen output (false-positive risk + duplicated declarations). It is the Pass-model successor of typeseval.LoadProductionPackages / ProductionResolver: the generated/ filter is applied by the driver, NOT by a per-callsite `if pass.IsGenerated(f) { continue }` discipline (which an author can forget — a Hard→Soft regression).
AI-robust: downstream Hard (scanning generated/ output is not expressible through this scope — a Pass it yields never contains a generated/ file). Upstream Medium (honest caveat): a rule author can still write Run(t, Typed(opts, []string{"./..."}), rule) + a manual pass.IsGenerated(f) skip per file; that form compiles and runs. The Hard "upstream" property (violation unrepresentable at the call site) is not achievable without sealing the Typed scope, which would break fixture-module and partial-scan rules. See PASS-PRODUCTION-UPSTREAM-HARD-01 (#722) for the upstream Hard candidate; this constructor does not, on its own, close that gap.
func StandaloneModule ¶
StandaloneModule builds a typed RunScope loading patterns from the standalone module rooted at dir. dir must be an absolute path to a directory containing its own go.mod (a fixture module isolated from the main module). Patterns are resolved relative to dir.
This is the correct scope for rules that target intentional-violation fixtures in testdata/: those fixtures intentionally import or call constructs that production archtest rules forbid, and must therefore live in a separate module so they do not pollute the main module's build.
Pass.Rel returns paths relative to dir (the fixture module root), not the main module root — so "usage.go" rather than "tools/archtest/testdata/.../usage.go".
AI-robust: Hard — the three-line Hard defense is preserved unchanged:
- Defense #1: Pass.Pkg is still *types.Package (not *packages.Package); rule authors cannot reach .Syntax or reconstruct INV-1 cross-load bugs.
- Defense #2: depguard bans archtest *_test.go from directly importing golang.org/x/tools/go/packages; Run is the approved funnel.
- Defense #3: meta-archtest PASS-FUNNEL-LOADPACKAGES-01 bans direct typeseval.LoadPackages and typeseval.SharedResolver calls; the Run driver is the only approved entry for fixture-module loads.
ref: golang.org/x/tools go/analysis/analysistest/analysistest.go (analysistest.Run receives dir string as the module root for the test programs; same pattern applied here for isolated fixture modules).
func Typed ¶
Typed builds a typed RunScope loading patterns once through the process-wide typeseval.SharedResolver cache from the main module root. Run invokes rule with one Pass per loaded package — Files dedup'd via *ast.File pointer identity across the regular and ".test" synthetic variants.
For loading a standalone testdata fixture module (one with its own go.mod), use StandaloneModule instead.
type RuntimeScopeConfig ¶
type RuntimeScopeConfig struct {
// contains filtered or unexported fields
}
RuntimeScopeConfig is the resolved, per-run scope context that bundles the workspace root, target module path, framework/platform module paths, and platform-cell scan directories into a single value whose fields are all unexported.
Motivation and single-source guarantee ¶
Before #2329 these values were resolved independently at each call site (lookupModuleRoot, moduleImportPath, and the Platform* consts) and passed as separate arguments to runTypedWithRoot / runProduction / platformCellScanDirs — making it easy for a site to diverge or forget one of the values. RuntimeScopeConfig is the single construction point: callers obtain it via DefaultScopeConfig or [defaultScopeConfig], and the defaults- lock test (TestDefaultScopeConfig_ReproducesGoCellDefaults) pins each field to its pre-#2329 value so behavior is byte-identical.
Construction (no out-of-package POPULATED value) ¶
Every field is unexported, so an external package cannot populate a RuntimeScopeConfig: both keyed (RuntimeScopeConfig{workspaceRoot: …}) and positional composite literals fail to compile outside this package. The only way to obtain a usable value is DefaultScopeConfig (the single mint site). That closes the forgery threat — external code cannot fabricate a config with an attacker-chosen targetModulePath.
This is NOT an absolute construction ban: the zero value RuntimeScopeConfig{} is still constructible anywhere (Go permits T{} for any struct), but it is inert — every accessor returns ""/nil and it is never a valid config (see TestRuntimeScopeConfig_ZeroValueIsInert). So the guarantee is "no forged POPULATED value" (Hard, via the unexported fields), not "no value at all".
Relation to ConfigForExternalCell and future issues ¶
ConfigForExternalCell (external.go) is the consumer-supplied description of the module to analyze — the "derive-not-supply" side. RuntimeScopeConfig is the INTERNAL, fully-resolved runtime counterpart assembled from go.work / go.mod discovery. External consumers never construct RuntimeScopeConfig directly; the drivers call [defaultScopeConfig] on their behalf.
The future Hard-ening funnel (forcing every helper and rule driver through this config instead of calling [lookupModuleRoot] / [moduleImportPath] directly) is tracked in #2333. The per-runner Scope/WorkspaceRoot wiring in the subprocess runner (cmd/gocell/internal/archtestrunner.Request) is tracked in #2331.
func DefaultScopeConfig ¶
func DefaultScopeConfig() (RuntimeScopeConfig, error)
DefaultScopeConfig constructs a RuntimeScopeConfig from the running process's go.work / go.mod environment. It resolves the workspace root via [lookupModuleRoot] and the target module path via [moduleImportPath], then assembles the remaining fields from the Platform* consts so that all paths derive from PlatformModulePath (ARCHTEST-MODULE-PATH-FUNNEL-01).
Returns a non-nil error if workspace root discovery or module-path derivation fails (e.g. no go.work above the cwd and no go.mod found). On error the returned RuntimeScopeConfig is the zero value and must not be used.
func (RuntimeScopeConfig) FrameworkModulePath ¶
func (c RuntimeScopeConfig) FrameworkModulePath() string
FrameworkModulePath returns the Go module path of the GoCell framework module (kernel/runtime/pkg). Derived from PlatformFrameworkModulePath.
func (RuntimeScopeConfig) PlatformCellScanDirs ¶
func (c RuntimeScopeConfig) PlatformCellScanDirs() []string
PlatformCellScanDirs returns a defensive copy of the repo-relative on-disk scan roots that hold GoCell's own platform cells. The source is [platformCellScanDirsDefault], shared with [platformCellScanDirs], so both callers agree on the exact set (#2329 single-source requirement).
func (RuntimeScopeConfig) PlatformCellsModulePath ¶
func (c RuntimeScopeConfig) PlatformCellsModulePath() string
PlatformCellsModulePath returns the Go module path of GoCell's platform-cells module. Equals the PlatformCellsModulePath constant.
func (RuntimeScopeConfig) PlatformModulePath ¶
func (c RuntimeScopeConfig) PlatformModulePath() string
PlatformModulePath returns the GoCell org/repo prefix shared by every GoCell module. Equals the PlatformModulePath constant.
func (RuntimeScopeConfig) TargetModulePath ¶
func (c RuntimeScopeConfig) TargetModulePath() string
TargetModulePath returns the Go module import-path prefix of the module under analysis (e.g. "github.com/ghbvf/gocell" in GoCell's own dogfood). In GoCell this equals PlatformModulePath; in an external consumer repo it equals the consumer's own module path.
func (RuntimeScopeConfig) WorkspaceRoot ¶
func (c RuntimeScopeConfig) WorkspaceRoot() string
WorkspaceRoot returns the resolved go.work workspace root (or the module root for a single-module consumer repo). This is the same value returned by [lookupModuleRoot] and used as the root argument to [runTypedWithRoot].
type Scope ¶
Scope is the opaque file-set descriptor produced by ModuleScope or DirsScope. Zero value is invalid.
func DirsScope ¶
func DirsScope(modRoot string, dirs []string, opts ...ScopeOption) Scope
DirsScope creates a Scope limited to dirs (relative to modRoot). See scanner.DirsScope for the full contract.
func ModuleScope ¶
func ModuleScope(modRoot string, opts ...ScopeOption) Scope
ModuleScope creates a Scope rooted at modRoot that walks the entire module, skipping the default directory set: vendor, testdata, worktrees, generated, .git, node_modules.
type ScopeOption ¶
ScopeOption is the functional-option type accepted by ModuleScope / DirsScope; obtain values via IncludeTests, ExcludeRels, MatchRels, IncludeTestdata, IncludeGenerated.
func ExcludeRels ¶
func ExcludeRels(rels ...string) ScopeOption
ExcludeRels returns a ScopeOption that excludes specific module-relative file paths from the scope's file set. Slash-separated; post-Clean matching.
func IncludeGenerated ¶
func IncludeGenerated() ScopeOption
IncludeGenerated allows the walk to descend into "generated" directories. Use for repo-wide rules whose invariant must also bind codegen output.
func IncludeTestdata ¶
func IncludeTestdata() ScopeOption
IncludeTestdata allows the walk to descend into "testdata" directories. Legal only when paired with DirsScope dirs containing a "testdata" segment.
func IncludeTests ¶
func IncludeTests() ScopeOption
IncludeTests returns a ScopeOption that includes *_test.go files in the scope's file set.
func MatchRels ¶
func MatchRels(pred func(rel string) bool) ScopeOption
MatchRels returns a ScopeOption that retains only files whose module-relative slash path satisfies pred. Composes AND with default skip + ExcludeRels.
type TypedOpts ¶
type TypedOpts struct {
// Tests, when true, loads the test-variant of each pattern. Defaults to
// false for production-only walks.
Tests bool
// Tags is the slice of build tags joined as -tags=a,b,c. Defaults to
// the default build context when empty.
Tags []string
}
TypedOpts configures the typed scopes Typed / Production / StandaloneModule. Tests selects the test-variant load (includes *_test.go and synthetic xtest packages) and matches typeseval.LoadPackages semantics; Tags sets build tags via -tags=a,b,c.
Source Files
¶
- adapter_returns_declared_types.go
- aftercommit_pure_transient_invariants.go
- audit_hash_input_frozen.go
- auth_authtest_boundary.go
- auth_keystest_boundary.go
- auth_plan.go
- bcrypt_cost_funnel.go
- callresolver.go
- capability_provider_funnel.go
- cas_protocol_composition_root.go
- cell_init.go
- cell_init_checknotnoop.go
- cell_public_option_param.go
- cell_repo_readyz_probe.go
- cell_scan_dirs.go
- cell_sync_transport_funnel.go
- cell_test_no_adapter_import.go
- cell_transport_docontract_caller.go
- celltest_import_boundary.go
- celltest_import_scope.go
- celltls_material_funnel.go
- celltransport_select_funnel.go
- certdeps_inmem_funnel.go
- clock_invariants.go
- composition_newforrole_funnel.go
- conformance_enrollment.go
- content.go
- contract_path_query_coverage.go
- contracttest_loadbyid_literal.go
- credential_authority_assert_funnel.go
- credential_invalidate_funnel_invariants.go
- cross_module_import_direction.go
- ctxkeys_principal_write_caller.go
- distlock_lock_not_context.go
- doc.go
- domain_authz_mutation_funnel_invariants.go
- errcode_invariants.go
- eval_predicate_centralization.go
- external.go
- fence_token_mint_funnel.go
- fixture.go
- fixture_cellid_typed_builder.go
- generate_rsa_keypair_funnel.go
- governance_rules_invariants.go
- health_aggregation.go
- health_verbose_invariants.go
- healthz_invariants.go
- insecure_skip_verify_funnel.go
- kernel_poolstats_location.go
- metrics_cancel_ctx_conformance.go
- module_root.go
- mqtt_funnel.go
- mqtt_reason_redaction.go
- no_deleted_auth_symbols.go
- notfound_test_strict.go
- observability_metrics.go
- outbox_invariants.go
- outbox_reconstruction_caller.go
- panic_invariants.go
- parser_matcher_examples_symmetry.go
- pass.go
- pg_repo_ambient_tx.go
- probename_sealed_funnel.go
- prod_main_wiring_noop_reject.go
- projection_apply_hook_funnel.go
- projection_event_journal_append_caller.go
- prom_cell_label_funnel.go
- reconstitute_user_caller_allowlist.go
- refresh_invariants.go
- replaydeps_inmem_funnel.go
- required_dep_nil_guard.go
- resolve.go
- reverse_coverage_invariants.go
- rmq_invariants.go
- safeid_funnel.go
- saga_invariants.go
- scaffold_derived_forceoverwrite.go
- scaffold_listener_marker_invariants.go
- scope.go
- scope_config.go
- security_defaults.go
- seed_role_iface.go
- serviceowned_handler_owner_check.go
- session_protocol_composition_root.go
- sessionrefresh_no_session_create.go
- shared_helpers.go
- svctoken_caller_cell.go
- taggroup_loop_no_typed_run_invariants.go
- test_polling_external_reason_literal.go
- tls_test_material_funnel.go
- walk.go
- warm.go
- webhook_hmac_funnel.go
- wrapper_location.go
- yaml_quote_funnel_invariants.go
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
bcryptcostredfixture
Package bcryptcostredfixture is a known-positive for BCRYPT-COST-FUNNEL-01 rule A1: it deliberately calls bcrypt.GenerateFromPassword outside corecells/accesscore/internal/credential/hasher.go.
|
Package bcryptcostredfixture is a known-positive for BCRYPT-COST-FUNNEL-01 rule A1: it deliberately calls bcrypt.GenerateFromPassword outside corecells/accesscore/internal/credential/hasher.go. |
|
callresolver
Package callresolver is an archtest-internal convenience layer over internal/typeseval + internal/scanner for the recurring "walk every FuncDecl body, resolve a callee, assert a receiver" shape that several rules (audit_hash_input_frozen, serviceowned_handler_owner_check, changepassword_inactive_gate, …) previously hand-wrote and copy-drifted.
|
Package callresolver is an archtest-internal convenience layer over internal/typeseval + internal/scanner for the recurring "walk every FuncDecl body, resolve a callee, assert a receiver" shape that several rules (audit_hash_input_frozen, serviceowned_handler_owner_check, changepassword_inactive_gate, …) previously hand-wrote and copy-drifted. |
|
scanner
Package scanner provides shared walk + parse + report primitives for tools/archtest scanners.
|
Package scanner provides shared walk + parse + report primitives for tools/archtest scanners. |
|
taggrouploopfixtures
Package taggrouploopfixtures holds typed-loadable .go fixtures for TAGGROUP-LOOP-FORBIDS-TYPED-RUN-01 (and its red/green reverse self-tests).
|
Package taggrouploopfixtures holds typed-loadable .go fixtures for TAGGROUP-LOOP-FORBIDS-TYPED-RUN-01 (and its red/green reverse self-tests). |
|
typeseval
Package typeseval provides go/types-backed helpers for archtest scanners.
|
Package typeseval provides go/types-backed helpers for archtest scanners. |
|
usage02fixtures
Package usage02fixtures holds typed-loadable .go fixtures for SCANNER-FRAMEWORK-USAGE-02 (main detector + BS1/BS2/BS3 reverse self-tests + BS4/BS5 main-detector-handled forms, on both EachInChildren and EachInSubtree depth axes).
|
Package usage02fixtures holds typed-loadable .go fixtures for SCANNER-FRAMEWORK-USAGE-02 (main detector + BS1/BS2/BS3 reverse self-tests + BS4/BS5 main-detector-handled forms, on both EachInChildren and EachInSubtree depth axes). |
|
walkdepthfixtures
Package walkdepthfixtures holds typed-loadable .go fixtures for WALK-DEPTH-API-EACHCHILDREN-01 (tools/archtest/walk_depth_funcdecl_children_test.go).
|
Package walkdepthfixtures holds typed-loadable .go fixtures for WALK-DEPTH-API-EACHCHILDREN-01 (tools/archtest/walk_depth_funcdecl_children_test.go). |
|
Package scoperules is the single, zero-dependency source of truth for which archtest rule IDs constitute the FRAMEWORK scope — the curated, module-path- agnostic / portable rule set that applies to any repository building Cells on the GoCell framework (#1878 / #2331).
|
Package scoperules is the single, zero-dependency source of truth for which archtest rule IDs constitute the FRAMEWORK scope — the curated, module-path- agnostic / portable rule set that applies to any repository building Cells on the GoCell framework (#1878 / #2331). |