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 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.
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
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 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 kernel/metadata/metadatatest.NewCellID(literal) or one of metadatatest's pre-validated package-level cell-id vars (CellID*). 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.
INVARIANT: MQTT-CLIENT-ID-NAMESPACE-01
- INVARIANT: MQTT-TOPIC-NAMESPACE-01
mqtt_funnel.go — importable sealed-struct construction funnel logic for adapters/mqtt.ClientID and adapters/mqtt.TopicNamespace.
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.
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.
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 in signer.go. The check is FUNCTION-level, not file-level: an hmac.New in any other function (even inside signer.go) fails. This also closes the package-internal upstream blind spot: any new struct that wants to sign MUST call hmac.New, which is allowlisted to computeMAC, so an unsealed internal holder cannot produce a signature undetected. Detection: ResolvePackageRef(callee) == crypto/hmac.New AND (file basename != signer.go OR enclosing func != computeMAC).
- 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). Package-internal new holders are not blocked by sealing (Medium) — covered transitively by A1. Explicit Hard-ization of the internal axis (unexported method-set interface + private construction, per the SPAN-SETATTR-HOLDER-SEAL #851 precedent) is tracked in gh #1243. Detection: the Signer/Verifier interface type decls must contain an unexported method.
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. 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 CheckCelltestImportBoundary(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckCelltestImportScope(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckClockPositionalInjection(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 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 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 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 CheckPolicyRepoConformanceEnrollment01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckProdClockInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckProjectionApplyHookFunnel01(t *testing.T, cfg 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 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 CheckSagaGlobalReaderConformanceEnrollment(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaJournalConformanceEnrollment(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaJournalHolderSeal(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckSagaMetricLabelValuesFrozen(t *testing.T, cfg 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 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 CheckTagGroupLoopForbidsTypedRun(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckTestwaitExternalReasonLiteral(t *testing.T, _ ConfigForExternalCell) []Diagnostic
- func CheckUserAuthzEpochBumpFunnel01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
- func CheckUserRepoConformanceEnrollment01(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 Scope
- type ScopeOption
- type TypedOpts
Constants ¶
const PlatformModulePath = "github.com/ghbvf/gocell"
PlatformModulePath is the Go module path of the GoCell platform itself. It is the ONE sanctioned site for the "github.com/ghbvf/gocell" string literal in this package: every rule that resolves a GoCell platform symbol path must derive it as PlatformModulePath+"/pkg/…" 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.
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"},
})
}
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 (CheckSagaJournalConformanceEnrollment): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
- SAGA-GLOBALREADER-CONFORMANCE-ENROLL-01 (CheckSagaGlobalReaderConformanceEnrollment): reasons about GoCell-internal runtime/saga layout (or conformance enrollment) → vacuous/false-red in an external module.
- 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.
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
// 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. A field reserved for a not-yet-migrated rule (e.g. a composition-root package list for PROD-MAIN-WIRING-NOOP-REJECT-01, issue #1303) is NOT added here until that rule lands — a public no-op field is a premature abstraction. 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.
It is vacuous in the single-module workspace of today (no other module exists to import); the reverse multi-module fixture exercises the live 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 lands per concrete satellite at extraction time: a `.golangci.yml` depguard rule banning the satellite's import path from core packages is a path-level compile-fast gate. This generic archtest auto-covers every future satellite (including ones not yet enumerated in depguard); per-satellite depguard Hard-ization is tracked as the close-out task (gh #1590).
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/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/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 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 CheckClockPositionalInjection ¶
func CheckClockPositionalInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckClockPositionalInjection runs CLOCK-POSITIONAL-INJECTION-01 against the running module and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
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 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 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 running module 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 running module 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+A3 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 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+A3 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 running module 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 the running module (resolved from its go.mod by Run(t, Typed(...))); cfg.BuildTags supplies the build tags for the second pass so panics behind the consumer's //go:build directives are scanned too.
The default build config is always scanned; when cfg.BuildTags is non-empty a second Run(t, Typed(...)) 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 CheckPolicyRepoConformanceEnrollment01 ¶
func CheckPolicyRepoConformanceEnrollment01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckPolicyRepoConformanceEnrollment01 runs POLICYREPO-CONFORMANCE-ENROLLMENT-01 over the running module and returns its diagnostics.
This is the importable CellRule body. GoCell's own Test* functions in policy_repo_conformance_enrollment_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 ports/conformance), making it vacuous-green or false-red for an external module.
func CheckProdClockInjection ¶
func CheckProdClockInjection(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckProdClockInjection runs PROD-CLOCK-INJECTION-01 against the running module and returns its diagnostics. Does not call t.Errorf — callers funnel diagnostics through Report.
Not registered in StandardCellRules: see file godoc.
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 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 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 CheckSagaGlobalReaderConformanceEnrollment ¶
func CheckSagaGlobalReaderConformanceEnrollment(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaGlobalReaderConformanceEnrollment is the importable form of SAGA-GLOBALREADER-CONFORMANCE-ENROLL-01. Not registered in StandardCellRules.
func CheckSagaJournalConformanceEnrollment ¶
func CheckSagaJournalConformanceEnrollment(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckSagaJournalConformanceEnrollment is the importable form of SAGA-JOURNAL-CONFORMANCE-ENROLLMENT-01. It scans the running module for concrete journal.Journal implementations that lack a conformance suite call. Not registered in StandardCellRules (targets GoCell-internal journal impls).
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 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.
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 cells/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 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 CheckUserRepoConformanceEnrollment01 ¶
func CheckUserRepoConformanceEnrollment01(t *testing.T, cfg ConfigForExternalCell) []Diagnostic
CheckUserRepoConformanceEnrollment01 runs USERREPO-CONFORMANCE-ENROLLMENT-01 over the running module and returns its diagnostics.
This is the importable CellRule body. GoCell's own Test* functions in user_repo_conformance_enrollment_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 ports/conformance), making it vacuous-green or false-red for an external module.
func CheckWebhookHMACFunnel ¶
func CheckWebhookHMACFunnel(t *testing.T, _ ConfigForExternalCell) []Diagnostic
CheckWebhookHMACFunnel runs the WEBHOOK-HMAC-FUNNEL-01 production scan (A1/A2/A3/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 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_test_no_adapter_import.go
- celltest_import_boundary.go
- celltest_import_scope.go
- clock_invariants.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
- governance_rules_invariants.go
- health_aggregation.go
- health_verbose_invariants.go
- healthz_invariants.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
- policy_repo_conformance_enrollment.go
- probename_sealed_funnel.go
- projection_apply_hook_funnel.go
- prom_cell_label_funnel.go
- reconstitute_user_caller_allowlist.go
- refresh_invariants.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
- 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
- user_repo_conformance_enrollment.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 cells/accesscore/internal/credential/hasher.go.
|
Package bcryptcostredfixture is a known-positive for BCRYPT-COST-FUNNEL-01 rule A1: it deliberately calls bcrypt.GenerateFromPassword outside cells/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). |