//go:build ags_inventory

package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

const gitstoreImportPath = "github.com/pingcap/agent-git-service/internal/gitstore"

const serviceImportPath = "github.com/pingcap/agent-git-service/internal/service"
const databaseImportPath = "github.com/pingcap/agent-git-service/internal/db"
const repositoryMutationImportPath = serviceImportPath + "/internal/repositorymutation"

type listedPackage struct {
	Dir        string
	ImportPath string
	GoFiles    []string
	Export     string
	Standard   bool
}

type checkedPackage struct {
	listed listedPackage
	fset   *token.FileSet
	files  []*ast.File
	info   *types.Info
	lines  map[string][]string
	parent map[ast.Node]ast.Node
}

type methodUse struct {
	file   string
	method string
	form   string
	line   string
}

type commandUse struct {
	file string
	line string
	call *ast.CallExpr
	bind types.Object
}

type commandInventory struct {
	kinds map[string]string
	keys  []string
}

func fail(format string, args ...any) {
	fmt.Fprintf(os.Stderr, "AGS repository mutation inventory: "+format+"\n", args...)
	os.Exit(1)
}

func main() {
	if len(os.Args) == 2 && os.Args[1] == "--self-test" {
		selfTest()
		return
	}
	root := "."
	update := false
	for _, arg := range os.Args[1:] {
		if arg == "--update" {
			update = true
		} else {
			root = arg
		}
	}
	absRoot, err := filepath.Abs(root)
	if err != nil {
		fail("resolve root: %v", err)
	}
	ags := filepath.Join(absRoot, "x", "agent-git-service")
	guardrail := filepath.Join(absRoot, "guardrails", "ags")
	packages, exports := listPackages(ags)
	checked := checkPackages(packages, exports)

	methodKinds := readMethodInventory(filepath.Join(guardrail, "gitstore-method-inventory.txt"))
	actualMethods := exportedStoreMethods(checked[gitstoreImportPath])
	expectedMethods := sortedKeys(methodKinds)
	if !equalStrings(actualMethods, expectedMethods) {
		fail("exported Store methods must be classified; actual=%v expected=%v", actualMethods, expectedMethods)
	}

	mutationMethods := map[string]bool{}
	for name, kind := range methodKinds {
		if kind == "mutation" {
			mutationMethods[name] = true
		}
	}
	actualMethodUses := collectMutationMethodUses(checked, mutationMethods, ags, gitstoreImportPath)
	actualCallRows := make([]string, 0, len(actualMethodUses))
	for _, use := range actualMethodUses {
		actualCallRows = append(actualCallRows, fmt.Sprintf("%s\t%s\t%s\t%s", use.form, use.file, use.method, use.line))
	}
	sort.Strings(actualCallRows)
	callInventory := filepath.Join(guardrail, "source-mutation-call-inventory.txt")
	if update {
		writeLines(callInventory, actualCallRows)
	} else if expected := readNonemptyLines(callInventory); !equalStrings(actualCallRows, sortedCopy(expected)) {
		fail("typed gitstore mutation uses changed; actual=%v expected=%v", actualCallRows, sortedCopy(expected))
	}

	gitstorePkg := checked[gitstoreImportPath]
	checkCommandInventory(gitstorePkg, filepath.Join(guardrail, "gitstore-command-inventory.txt"), functionObject(gitstorePkg, "runRepositoryMutationCommand", ""))
	backendPath := repositoryMutationImportPath
	backendPkg := checked[backendPath]
	receiverKinds := readMethodInventory(filepath.Join(guardrail, "repositorymutation-receiver-method-inventory.txt"))
	actualReceiverMethods := exportedReceiverMethodKeys(backendPkg)
	if expected := sortedKeys(receiverKinds); !equalStrings(actualReceiverMethods, expected) {
		fail("exported private authority receiver methods must be classified; actual=%v expected=%v", actualReceiverMethods, expected)
	}
	typeKinds := readTypeInventory(filepath.Join(guardrail, "repositorymutation-exported-type-inventory.txt"))
	actualTypes := exportedTypeNames(backendPkg)
	if expected := sortedKeys(typeKinds); !equalStrings(actualTypes, expected) {
		fail("exported private authority types must be classified; actual=%v expected=%v", actualTypes, expected)
	}
	functionKinds := readMethodInventory(filepath.Join(guardrail, "repositorymutation-exported-function-inventory.txt"))
	actualFunctions := exportedPackageFunctionNames(backendPkg)
	if expected := sortedKeys(functionKinds); !equalStrings(actualFunctions, expected) {
		fail("exported private authority functions must be classified; actual=%v expected=%v", actualFunctions, expected)
	}
	variableKinds := readMethodInventory(filepath.Join(guardrail, "repositorymutation-exported-var-inventory.txt"))
	actualVariables := exportedPackageVarNames(backendPkg)
	if expected := sortedKeys(variableKinds); !equalStrings(actualVariables, expected) {
		fail("exported private authority variables must be classified; actual=%v expected=%v", actualVariables, expected)
	}
	fieldKinds := readMethodInventory(filepath.Join(guardrail, "repositorymutation-exported-field-inventory.txt"))
	actualFields := exportedDeclaredFieldKeys(backendPkg)
	if expected := sortedKeys(fieldKinds); !equalStrings(actualFields, expected) {
		fail("exported private fields must be classified; actual=%v expected=%v", actualFields, expected)
	}
	assertAuthoritativeEnsureSignatures(backendPkg)
	checkCommandInventory(backendPkg, filepath.Join(guardrail, "repositorymutation-command-inventory.txt"), functionObject(backendPkg, "startCommand", "repositoryMutationCapability"))
	checkGoGitUseInventory(backendPkg, ags, filepath.Join(guardrail, "repositorymutation-gogit-inventory.txt"), update)
	assertMigrationServiceAuthority(checked[serviceImportPath])
	assertNoPublicMigrationCapabilityUses(checked)

	if len(mutationMethods) != 0 {
		fail("gitstore production source mutation surface must be zero; remaining=%v", sortedKeys(mutationMethods))
	}
	for name, kind := range methodKinds {
		if kind == "authority" {
			fail("gitstore production mutation authority must be zero; remaining method=%s", name)
		}
	}
	assertNoRawAuthority(gitstorePkg)
	fmt.Println("AGS repository mutation method/call/child inventory passed.")
}

func assertNoPublicMigrationCapabilityUses(packages map[string]*checkedPackage) {
	blocked := map[string]bool{
		"SealRepositoryForMigration":               true,
		"RenewRepositoryMigrationProof":            true,
		"ValidateRepositoryMigrationProof":         true,
		"AbortRepositoryMigrationSealBeforeImport": true,
		"AbortFailedRepositorySeal":                true,
	}
	var violations []string
	for path, pkg := range packages {
		if pkg == nil || !(path == "github.com/pingcap/agent-git-service/server" ||
			strings.HasPrefix(path, "github.com/pingcap/agent-git-service/internal/rest") ||
			strings.HasPrefix(path, "github.com/pingcap/agent-git-service/internal/graphql") ||
			strings.HasPrefix(path, "github.com/pingcap/agent-git-service/internal/githttp")) {
			continue
		}
		for selector, selection := range pkg.info.Selections {
			method, _ := selection.Obj().(*types.Func)
			if method == nil || !blocked[method.Name()] || method.Pkg() == nil || method.Pkg().Path() != serviceImportPath {
				continue
			}
			signature, _ := method.Type().(*types.Signature)
			if signature == nil || signature.Recv() == nil || !isPointerToNamed(signature.Recv().Type(), serviceImportPath, "Service") {
				continue
			}
			violations = append(violations, path+": "+sourceLine(pkg, selector.Pos()))
		}
	}
	sort.Strings(violations)
	if len(violations) != 0 {
		fail("public routes must not reference migration seal/proof/abort methods in any typed form: %v", violations)
	}
}

func assertAuthoritativeEnsureSignatures(pkg *checkedPackage) {
	for _, name := range []string{"EnsureRepository", "EnsureWikiRepository"} {
		object, _ := functionObject(pkg, name, "Owner").(*types.Func)
		if object == nil {
			fail("private Owner operation is missing: %s", name)
		}
		signature, _ := object.Type().(*types.Signature)
		if signature == nil || signature.Params().Len() != 2 || !isNamedType(signature.Params().At(0).Type(), "context", "Context") {
			fail("%s must accept only context and immutable repository ID", name)
		}
		basic, ok := signature.Params().At(1).Type().Underlying().(*types.Basic)
		if !ok || basic.Kind() != types.Uint {
			fail("%s repository identity must be uint", name)
		}
	}
}

func isNamedType(value types.Type, packagePath, name string) bool {
	named, ok := value.(*types.Named)
	return ok && isNamedObject(named.Obj(), packagePath, name)
}

func checkGoGitUseInventory(pkg *checkedPackage, ags, inventoryPath string, update bool) {
	actual := collectPackageFunctionUses(pkg, ags, "github.com/go-git/")
	actualRows := make([]string, 0, len(actual))
	for _, use := range actual {
		actualRows = append(actualRows, fmt.Sprintf("%s\t%s\t%s\t%s", use.form, use.file, use.method, use.line))
	}
	sort.Strings(actualRows)
	if update {
		classified := make([]string, len(actualRows))
		for index, row := range actualRows {
			classified[index] = "read\t" + row
		}
		writeLines(inventoryPath, classified)
		return
	}
	expectedRows := make([]string, 0)
	for _, line := range readNonemptyLines(inventoryPath) {
		parts := strings.SplitN(line, "\t", 5)
		if len(parts) != 5 || !oneOf(parts[0], "mutation", "read") {
			fail("invalid go-git inventory row: %q", line)
		}
		expectedRows = append(expectedRows, strings.Join(parts[1:], "\t"))
	}
	sort.Strings(expectedRows)
	if !equalStrings(actualRows, expectedRows) {
		fail("private Owner go-git calls must be classified; actual=%v expected=%v", actualRows, expectedRows)
	}
}

func collectPackageFunctionUses(pkg *checkedPackage, ags, packagePrefix string) []methodUse {
	if pkg == nil {
		return nil
	}
	var uses []methodUse
	aliases := map[types.Object]methodUse{}
	for _, file := range pkg.files {
		ast.Inspect(file, func(node ast.Node) bool {
			selector, ok := node.(*ast.SelectorExpr)
			if !ok {
				return true
			}
			var object *types.Func
			var form string
			if selection := pkg.info.Selections[selector]; selection != nil {
				object, _ = selection.Obj().(*types.Func)
				form = selectionForm(selection, selector, pkg.parent)
			} else {
				object, _ = pkg.info.Uses[selector.Sel].(*types.Func)
				form = "value"
				if call, ok := pkg.parent[selector].(*ast.CallExpr); ok && call.Fun == selector {
					form = "call"
				}
			}
			if object == nil || object.Pkg() == nil || !strings.HasPrefix(object.Pkg().Path(), packagePrefix) {
				return true
			}
			use := methodUse{
				file:   relativeGoFile(ags, pkg.fset.Position(selector.Pos()).Filename),
				method: object.Name(), form: form, line: sourceLine(pkg, selector.Pos()),
			}
			uses = append(uses, use)
			if form == "value" {
				bindMethodAlias(pkg, selector, use, aliases)
			}
			return true
		})
	}
	for expression, object := range pkg.info.Uses {
		use, ok := aliases[object]
		if !ok {
			continue
		}
		if call, ok := pkg.parent[expression].(*ast.CallExpr); ok && call.Fun == expression {
			uses = append(uses, methodUse{file: use.file, method: use.method, form: "alias_call", line: sourceLine(pkg, expression.Pos())})
		}
	}
	sort.Slice(uses, func(i, j int) bool {
		return fmt.Sprintf("%s\t%s\t%s\t%s", uses[i].form, uses[i].file, uses[i].method, uses[i].line) <
			fmt.Sprintf("%s\t%s\t%s\t%s", uses[j].form, uses[j].file, uses[j].method, uses[j].line)
	})
	return uses
}

func assertMigrationServiceAuthority(pkg *checkedPackage) {
	if pkg == nil {
		fail("service package was not loaded")
	}
	violations := collectMigrationServiceAuthorityViolations(pkg, serviceImportPath, databaseImportPath, repositoryMutationImportPath)
	if len(violations) != 0 {
		fail("migration Service operations may use only private Owner value/opaque APIs: %v", violations)
	}
}

func collectMigrationServiceAuthorityViolations(pkg *checkedPackage, servicePath, databasePath, backendPath string) []string {
	aliasDefinitions := collectFunctionAliasDefinitions(pkg)
	functionBodies := map[types.Object]*ast.BlockStmt{}
	declarationObjects := map[*ast.FuncDecl]types.Object{}
	literalObjects := map[*ast.FuncLit]types.Object{}
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			function, ok := declaration.(*ast.FuncDecl)
			if !ok || function.Body == nil {
				continue
			}
			if object := pkg.info.Defs[function.Name]; object != nil {
				functionBodies[object] = function.Body
				declarationObjects[function] = object
			}
		}
		ast.Inspect(file, func(node ast.Node) bool {
			literal, ok := node.(*ast.FuncLit)
			if !ok {
				return true
			}
			signature, _ := pkg.info.TypeOf(literal).(*types.Signature)
			if signature == nil {
				return true
			}
			object := types.NewFunc(literal.Pos(), nil, fmt.Sprintf("funcLiteral%d", literal.Pos()), signature)
			literalObjects[literal] = object
			functionBodies[object] = literal.Body
			return true
		})
	}
	seeds := map[types.Object]bool{}
	calls := map[types.Object][]types.Object{}
	callers := map[types.Object][]types.Object{}
	unresolved := map[types.Object][]string{}
	for literal, object := range literalObjects {
		if enclosing := enclosingFunctionObject(pkg, literal, declarationObjects, literalObjects); enclosing != nil {
			calls[enclosing] = append(calls[enclosing], object)
			callers[object] = append(callers[object], enclosing)
		}
	}
	for object, body := range functionBodies {
		ast.Inspect(body, func(node ast.Node) bool {
			if literal, ok := node.(*ast.FuncLit); ok && literal.Body != body {
				return false
			}
			call, ok := node.(*ast.CallExpr)
			if !ok {
				return true
			}
			targets, resolved := resolveFunctionTargets(pkg, call.Fun, aliasDefinitions, literalObjects, map[types.Object]bool{})
			if !resolved {
				unresolved[object] = append(unresolved[object], sourceLine(pkg, call.Pos()))
			}
			for _, target := range targets {
				if _, ok := functionBodies[target]; ok {
					calls[object] = append(calls[object], target)
					callers[target] = append(callers[target], object)
				}
				if isMigrationOwnerOperation(target, backendPath) {
					seeds[object] = true
				}
			}
			return true
		})
	}
	closure := map[types.Object]bool{}
	queue := make([]types.Object, 0, len(seeds))
	for object := range seeds {
		closure[object] = true
		queue = append(queue, object)
	}
	for len(queue) != 0 {
		object := queue[0]
		queue = queue[1:]
		for _, callee := range calls[object] {
			if !closure[callee] {
				closure[callee] = true
				queue = append(queue, callee)
			}
		}
		for _, caller := range callers[object] {
			if !closure[caller] {
				closure[caller] = true
				queue = append(queue, caller)
			}
		}
	}
	violations := map[string]bool{}
	for object, body := range functionBodies {
		inMigrationClosure := closure[object]
		if inMigrationClosure {
			for _, line := range unresolved[object] {
				violations["unclassified dynamic call: "+line] = true
			}
		}
		ast.Inspect(body, func(node ast.Node) bool {
			if literal, ok := node.(*ast.FuncLit); ok && literal.Body != body {
				return false
			}
			switch node := node.(type) {
			case *ast.Ident:
				used := pkg.info.Uses[node]
				if isNamedObject(used, databasePath, "RepositoryMigrationFreeze") ||
					(inMigrationClosure && isNamedObject(used, databasePath, "Release")) {
					violations[sourceLine(pkg, node.Pos())] = true
				}
			case *ast.SelectorExpr:
				if !inMigrationClosure {
					return true
				}
				selection := pkg.info.Selections[node]
				if selection != nil && isServiceAuthoritySelection(selection, servicePath) {
					violations[sourceLine(pkg, node.Pos())] = true
				}
			}
			return true
		})
	}
	return sortedKeys(violations)
}

func enclosingFunctionObject(pkg *checkedPackage, literal *ast.FuncLit, declarations map[*ast.FuncDecl]types.Object, literals map[*ast.FuncLit]types.Object) types.Object {
	for parent := pkg.parent[literal]; parent != nil; parent = pkg.parent[parent] {
		switch parent := parent.(type) {
		case *ast.FuncLit:
			return literals[parent]
		case *ast.FuncDecl:
			return declarations[parent]
		}
	}
	return nil
}

func collectFunctionAliasDefinitions(pkg *checkedPackage) map[types.Object][]ast.Expr {
	definitions := map[types.Object][]ast.Expr{}
	for _, file := range pkg.files {
		ast.Inspect(file, func(node ast.Node) bool {
			switch node := node.(type) {
			case *ast.AssignStmt:
				if len(node.Lhs) == len(node.Rhs) {
					for index, left := range node.Lhs {
						identifier, ok := left.(*ast.Ident)
						if !ok {
							continue
						}
						object := pkg.info.Defs[identifier]
						if object == nil {
							object = pkg.info.Uses[identifier]
						}
						if object != nil {
							definitions[object] = append(definitions[object], node.Rhs[index])
						}
					}
				}
			case *ast.ValueSpec:
				if len(node.Names) == len(node.Values) {
					for index, name := range node.Names {
						if object := pkg.info.Defs[name]; object != nil {
							definitions[object] = append(definitions[object], node.Values[index])
						}
					}
				}
			}
			return true
		})
	}
	return definitions
}

func resolveFunctionTargets(pkg *checkedPackage, expression ast.Expr, definitions map[types.Object][]ast.Expr, literals map[*ast.FuncLit]types.Object, seen map[types.Object]bool) ([]types.Object, bool) {
	switch expression := expression.(type) {
	case *ast.SelectorExpr:
		if selection := pkg.info.Selections[expression]; selection != nil {
			return []types.Object{selection.Obj()}, true
		}
		if object := pkg.info.Uses[expression.Sel]; object != nil {
			return []types.Object{object}, true
		}
	case *ast.Ident:
		object := pkg.info.Uses[expression]
		if _, ok := object.(*types.Func); ok {
			return []types.Object{object}, true
		}
		if _, ok := object.(*types.Builtin); ok {
			return nil, true
		}
		if _, ok := object.(*types.TypeName); ok {
			return nil, true
		}
		if object == nil || seen[object] {
			return nil, false
		}
		seen[object] = true
		var targets []types.Object
		resolved := len(definitions[object]) != 0
		for _, value := range definitions[object] {
			found, ok := resolveFunctionTargets(pkg, value, definitions, literals, seen)
			targets = append(targets, found...)
			resolved = resolved && ok
		}
		delete(seen, object)
		return uniqueObjects(targets), resolved
	case *ast.FuncLit:
		object := literals[expression]
		return []types.Object{object}, object != nil
	}
	return nil, false
}

func uniqueObjects(values []types.Object) []types.Object {
	seen := map[types.Object]bool{}
	result := make([]types.Object, 0, len(values))
	for _, value := range values {
		if value != nil && !seen[value] {
			seen[value] = true
			result = append(result, value)
		}
	}
	return result
}

func isMigrationOwnerOperation(object types.Object, backendPath string) bool {
	function, ok := object.(*types.Func)
	if !ok || function.Pkg() == nil || function.Pkg().Path() != backendPath {
		return false
	}
	signature, _ := function.Type().(*types.Signature)
	return signature != nil && signature.Recv() != nil &&
		isPointerToNamed(signature.Recv().Type(), backendPath, "Owner") &&
		oneOf(function.Name(), "SealRepository", "RenewMigrationProof", "ValidateMigrationProof", "AbortMigrationBeforeImport", "AbortFailedSeal")
}

func isNamedObject(object types.Object, packagePath, name string) bool {
	return object != nil && object.Pkg() != nil && object.Pkg().Path() == packagePath && object.Name() == name
}

func isServiceAuthoritySelection(selection *types.Selection, servicePath string) bool {
	if selection == nil || !oneOf(selection.Obj().Name(), "DB", "DBForCtx", "Git") {
		return false
	}
	return isPointerToNamed(selection.Recv(), servicePath, "Service")
}

func checkCommandInventory(pkg *checkedPackage, inventoryPath string, runner types.Object) {
	if pkg == nil {
		fail("command inventory package is missing for %s", inventoryPath)
	}
	actualCommands := collectExecCommands(pkg)
	commandInventory := readCommandInventory(inventoryPath)
	for _, kind := range commandInventory.kinds {
		if kind == "mutation" && runner == nil {
			fail("mutation command runner is missing for %s", inventoryPath)
		}
	}
	actualCommandKeys := make([]string, 0, len(actualCommands))
	for _, command := range actualCommands {
		actualCommandKeys = append(actualCommandKeys, command.file+"\t"+command.line)
	}
	sort.Strings(actualCommandKeys)
	expectedCommandKeys := sortedCopy(commandInventory.keys)
	if !equalStrings(actualCommandKeys, expectedCommandKeys) {
		fail("os/exec command calls changed; actual=%v expected=%v", actualCommandKeys, expectedCommandKeys)
	}
	for _, command := range actualCommands {
		if commandInventory.kinds[command.file+"\t"+command.line] != "mutation" {
			continue
		}
		if !hasAncestorCallObject(pkg, command.call, runner) &&
			!objectPassedToCallObject(pkg, command.bind, runner) {
			fail("authoritative child lacks inherited fence runner: %s: %s", command.file, command.line)
		}
	}
}

func listPackages(ags string) (map[string]listedPackage, map[string]string) {
	command := exec.Command("go", "list", "-deps", "-export", "-json", "./internal/...", "./server")
	command.Dir = ags
	output, err := command.Output()
	if err != nil {
		if exit, ok := err.(*exec.ExitError); ok {
			fail("go list: %s", strings.TrimSpace(string(exit.Stderr)))
		}
		fail("go list: %v", err)
	}
	decoder := json.NewDecoder(bytes.NewReader(output))
	all := map[string]listedPackage{}
	exports := map[string]string{}
	for {
		var item listedPackage
		if err := decoder.Decode(&item); err == io.EOF {
			break
		} else if err != nil {
			fail("decode go list: %v", err)
		}
		if item.ImportPath == "" {
			continue
		}
		all[item.ImportPath] = item
		if item.Export != "" {
			exports[item.ImportPath] = item.Export
		}
	}
	targets := map[string]listedPackage{}
	for path, item := range all {
		if path == gitstoreImportPath || strings.HasPrefix(path, "github.com/pingcap/agent-git-service/internal/") || path == "github.com/pingcap/agent-git-service/server" {
			targets[path] = item
		}
	}
	return targets, exports
}

func checkPackages(packages map[string]listedPackage, exports map[string]string) map[string]*checkedPackage {
	checked := map[string]*checkedPackage{}
	paths := sortedKeys(packages)
	for _, path := range paths {
		item := packages[path]
		if len(item.GoFiles) == 0 {
			continue
		}
		fset := token.NewFileSet()
		files := make([]*ast.File, 0, len(item.GoFiles))
		lines := map[string][]string{}
		for _, name := range item.GoFiles {
			fullPath := filepath.Join(item.Dir, name)
			data, err := os.ReadFile(fullPath)
			if err != nil {
				fail("read %s: %v", fullPath, err)
			}
			file, err := parser.ParseFile(fset, fullPath, data, 0)
			if err != nil {
				fail("parse %s: %v", fullPath, err)
			}
			files = append(files, file)
			lines[fullPath] = splitLines(string(data))
		}
		info := &types.Info{
			Defs:       map[*ast.Ident]types.Object{},
			Uses:       map[*ast.Ident]types.Object{},
			Types:      map[ast.Expr]types.TypeAndValue{},
			Selections: map[*ast.SelectorExpr]*types.Selection{},
		}
		lookup := func(importPath string) (io.ReadCloser, error) {
			export, ok := exports[importPath]
			if !ok {
				return nil, fmt.Errorf("missing export data for %s", importPath)
			}
			return os.Open(export)
		}
		config := types.Config{Importer: importer.ForCompiler(fset, "gc", lookup)}
		if _, err := config.Check(path, fset, files, info); err != nil {
			fail("type-check %s: %v", path, err)
		}
		parent := map[ast.Node]ast.Node{}
		for _, file := range files {
			ast.Inspect(file, func(node ast.Node) bool {
				if node == nil {
					return false
				}
				for _, child := range directChildren(node) {
					parent[child] = node
				}
				return true
			})
		}
		checked[path] = &checkedPackage{listed: item, fset: fset, files: files, info: info, lines: lines, parent: parent}
	}
	return checked
}

func exportedStoreMethods(pkg *checkedPackage) []string {
	return exportedReceiverMethods(pkg, gitstoreImportPath, "Store")
}

func exportedReceiverMethods(pkg *checkedPackage, packagePath, receiverName string) []string {
	if pkg == nil {
		fail("receiver package was not loaded")
	}
	var methods []string
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			function, ok := declaration.(*ast.FuncDecl)
			if !ok || function.Recv == nil || !function.Name.IsExported() {
				continue
			}
			object, _ := pkg.info.Defs[function.Name].(*types.Func)
			if object == nil {
				continue
			}
			signature, _ := object.Type().(*types.Signature)
			if signature != nil && isPointerToNamed(signature.Recv().Type(), packagePath, receiverName) {
				methods = append(methods, function.Name.Name)
			}
		}
	}
	sort.Strings(methods)
	return methods
}

func exportedReceiverMethodKeys(pkg *checkedPackage) []string {
	if pkg == nil {
		fail("receiver package was not loaded")
	}
	methodSet := map[string]bool{}
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			general, ok := declaration.(*ast.GenDecl)
			if !ok || general.Tok != token.TYPE {
				continue
			}
			for _, spec := range general.Specs {
				typeSpec, ok := spec.(*ast.TypeSpec)
				if !ok {
					continue
				}
				object, _ := pkg.info.Defs[typeSpec.Name].(*types.TypeName)
				if object == nil {
					continue
				}
				named, _ := object.Type().(*types.Named)
				if named == nil {
					continue
				}
				for _, receiver := range []types.Type{named, types.NewPointer(named)} {
					methods := types.NewMethodSet(receiver)
					for index := 0; index < methods.Len(); index++ {
						method := methods.At(index).Obj()
						if method.Exported() {
							methodSet[typeSpec.Name.Name+"."+method.Name()] = true
						}
					}
				}
			}
		}
	}
	return sortedKeys(methodSet)
}

func exportedDeclaredFieldKeys(pkg *checkedPackage) []string {
	if pkg == nil {
		fail("field package was not loaded")
	}
	fields := map[string]bool{}
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			general, ok := declaration.(*ast.GenDecl)
			if !ok || general.Tok != token.TYPE {
				continue
			}
			for _, spec := range general.Specs {
				typeSpec, ok := spec.(*ast.TypeSpec)
				if !ok || !typeSpec.Name.IsExported() {
					continue
				}
				object, _ := pkg.info.Defs[typeSpec.Name].(*types.TypeName)
				if object == nil {
					continue
				}
				structure, _ := object.Type().Underlying().(*types.Struct)
				if structure == nil {
					continue
				}
				for index := 0; index < structure.NumFields(); index++ {
					field := structure.Field(index)
					if field.Exported() {
						fields[typeSpec.Name.Name+"."+field.Name()] = true
					}
				}
			}
		}
	}
	return sortedKeys(fields)
}

func receiverNamedType(value types.Type) *types.Named {
	if pointer, ok := value.(*types.Pointer); ok {
		value = pointer.Elem()
	}
	named, _ := value.(*types.Named)
	return named
}

func exportedTypeNames(pkg *checkedPackage) []string {
	if pkg == nil {
		fail("type package was not loaded")
	}
	var names []string
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			general, ok := declaration.(*ast.GenDecl)
			if !ok || general.Tok != token.TYPE {
				continue
			}
			for _, spec := range general.Specs {
				typeSpec, ok := spec.(*ast.TypeSpec)
				if ok && typeSpec.Name.IsExported() {
					names = append(names, typeSpec.Name.Name)
				}
			}
		}
	}
	sort.Strings(names)
	return names
}

func exportedPackageFunctionNames(pkg *checkedPackage) []string {
	if pkg == nil {
		fail("function package was not loaded")
	}
	var names []string
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			function, ok := declaration.(*ast.FuncDecl)
			if ok && function.Recv == nil && function.Name.IsExported() {
				names = append(names, function.Name.Name)
			}
		}
	}
	sort.Strings(names)
	return names
}

func exportedPackageVarNames(pkg *checkedPackage) []string {
	if pkg == nil {
		fail("variable package was not loaded")
	}
	var names []string
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			general, ok := declaration.(*ast.GenDecl)
			if !ok || general.Tok != token.VAR {
				continue
			}
			for _, spec := range general.Specs {
				value, ok := spec.(*ast.ValueSpec)
				if !ok {
					continue
				}
				for _, name := range value.Names {
					if name.IsExported() {
						names = append(names, name.Name)
					}
				}
			}
		}
	}
	sort.Strings(names)
	return names
}

func isStorePointer(value types.Type) bool {
	return isPointerToNamed(value, gitstoreImportPath, "Store")
}

func isPointerToNamed(value types.Type, packagePath, typeName string) bool {
	pointer, ok := value.(*types.Pointer)
	if !ok {
		return false
	}
	named, ok := pointer.Elem().(*types.Named)
	return ok && named.Obj().Name() == typeName && named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == packagePath
}

func collectMutationMethodUses(packages map[string]*checkedPackage, mutationMethods map[string]bool, ags, targetPackage string) []methodUse {
	var uses []methodUse
	for _, pkg := range packages {
		if pkg == nil {
			continue
		}
		aliases := map[types.Object]methodUse{}
		for selector, selection := range pkg.info.Selections {
			method, _ := selection.Obj().(*types.Func)
			if method == nil || method.Pkg() == nil || method.Pkg().Path() != targetPackage || !mutationMethods[method.Name()] {
				continue
			}
			use := methodUse{file: relativeGoFile(ags, pkg.fset.Position(selector.Pos()).Filename), method: method.Name(), form: selectionForm(selection, selector, pkg.parent), line: sourceLine(pkg, selector.Pos())}
			uses = append(uses, use)
			if use.form == "value" {
				bindMethodAlias(pkg, selector, use, aliases)
			}
		}
		for expression, object := range pkg.info.Uses {
			use, ok := aliases[object]
			if !ok {
				continue
			}
			if call, ok := pkg.parent[expression].(*ast.CallExpr); ok && call.Fun == expression {
				uses = append(uses, methodUse{file: relativeGoFile(ags, pkg.fset.Position(expression.Pos()).Filename), method: use.method, form: "alias_call", line: sourceLine(pkg, expression.Pos())})
			}
		}
	}
	sort.Slice(uses, func(i, j int) bool {
		return fmt.Sprintf("%s\t%s\t%s\t%s", uses[i].form, uses[i].file, uses[i].method, uses[i].line) < fmt.Sprintf("%s\t%s\t%s\t%s", uses[j].form, uses[j].file, uses[j].method, uses[j].line)
	})
	return uses
}

func selectionForm(selection *types.Selection, selector *ast.SelectorExpr, parents map[ast.Node]ast.Node) string {
	if call, ok := parents[selector].(*ast.CallExpr); ok && call.Fun == selector {
		if selection.Kind() == types.MethodExpr {
			return "expression_call"
		}
		return "call"
	}
	if selection.Kind() == types.MethodExpr {
		return "expression_value"
	}
	return "value"
}

func bindMethodAlias(pkg *checkedPackage, selector *ast.SelectorExpr, use methodUse, aliases map[types.Object]methodUse) {
	parent := pkg.parent[selector]
	switch node := parent.(type) {
	case *ast.AssignStmt:
		for index, rhs := range node.Rhs {
			if rhs == selector && index < len(node.Lhs) {
				if ident, ok := node.Lhs[index].(*ast.Ident); ok {
					if object := pkg.info.Defs[ident]; object != nil {
						aliases[object] = use
					}
				}
			}
		}
	case *ast.ValueSpec:
		for index, rhs := range node.Values {
			if rhs == selector && index < len(node.Names) {
				if object := pkg.info.Defs[node.Names[index]]; object != nil {
					aliases[object] = use
				}
			}
		}
	}
}

func collectExecCommands(pkg *checkedPackage) []commandUse {
	aliases := map[types.Object]bool{}
	var commands []commandUse
	for _, file := range pkg.files {
		ast.Inspect(file, func(node ast.Node) bool {
			selector, ok := node.(*ast.SelectorExpr)
			if !ok || !isExecCommandObject(pkg.info.Uses[selector.Sel]) {
				return true
			}
			if call, ok := pkg.parent[selector].(*ast.CallExpr); ok && call.Fun == selector {
				commands = append(commands, commandUse{file: filepath.Base(pkg.fset.Position(selector.Pos()).Filename), line: sourceLine(pkg, selector.Pos()), call: call, bind: boundCallObject(pkg, call)})
				return true
			}
			bindFunctionAlias(pkg, selector, aliases)
			return true
		})
	}
	for identifier, object := range pkg.info.Uses {
		if !aliases[object] {
			continue
		}
		if call, ok := pkg.parent[identifier].(*ast.CallExpr); ok && call.Fun == identifier {
			commands = append(commands, commandUse{file: filepath.Base(pkg.fset.Position(identifier.Pos()).Filename), line: sourceLine(pkg, identifier.Pos()), call: call, bind: boundCallObject(pkg, call)})
		}
	}
	sort.Slice(commands, func(i, j int) bool {
		return commands[i].file+"\t"+commands[i].line < commands[j].file+"\t"+commands[j].line
	})
	return commands
}

func isExecCommandObject(object types.Object) bool {
	return object != nil && object.Pkg() != nil && object.Pkg().Path() == "os/exec" && (object.Name() == "Command" || object.Name() == "CommandContext")
}

func bindFunctionAlias(pkg *checkedPackage, expression ast.Expr, aliases map[types.Object]bool) {
	parent := pkg.parent[expression]
	switch node := parent.(type) {
	case *ast.AssignStmt:
		for index, rhs := range node.Rhs {
			if rhs == expression && index < len(node.Lhs) {
				if ident, ok := node.Lhs[index].(*ast.Ident); ok {
					if object := pkg.info.Defs[ident]; object != nil {
						aliases[object] = true
					}
				}
			}
		}
	case *ast.ValueSpec:
		for index, rhs := range node.Values {
			if rhs == expression && index < len(node.Names) {
				if object := pkg.info.Defs[node.Names[index]]; object != nil {
					aliases[object] = true
				}
			}
		}
	}
}

func hasAncestorCallObject(pkg *checkedPackage, node ast.Node, target types.Object) bool {
	for parent := pkg.parent[node]; parent != nil; parent = pkg.parent[parent] {
		call, ok := parent.(*ast.CallExpr)
		if ok && callTargetObject(pkg, call) == target {
			return true
		}
	}
	return false
}

func boundCallObject(pkg *checkedPackage, call *ast.CallExpr) types.Object {
	switch parent := pkg.parent[call].(type) {
	case *ast.AssignStmt:
		for index, rhs := range parent.Rhs {
			if rhs == call && index < len(parent.Lhs) {
				if identifier, ok := parent.Lhs[index].(*ast.Ident); ok {
					if object := pkg.info.Defs[identifier]; object != nil {
						return object
					}
					return pkg.info.Uses[identifier]
				}
			}
		}
	case *ast.ValueSpec:
		for index, rhs := range parent.Values {
			if rhs == call && index < len(parent.Names) {
				return pkg.info.Defs[parent.Names[index]]
			}
		}
	}
	return nil
}

func objectPassedToCallObject(pkg *checkedPackage, object, target types.Object) bool {
	if object == nil {
		return false
	}
	for identifier, used := range pkg.info.Uses {
		if used != object {
			continue
		}
		call, ok := pkg.parent[identifier].(*ast.CallExpr)
		if !ok {
			continue
		}
		for _, argument := range call.Args {
			if argument != identifier {
				continue
			}
			if callTargetObject(pkg, call) == target {
				return true
			}
		}
	}
	return false
}

func callTargetObject(pkg *checkedPackage, call *ast.CallExpr) types.Object {
	switch function := call.Fun.(type) {
	case *ast.Ident:
		return pkg.info.Uses[function]
	case *ast.SelectorExpr:
		if selection := pkg.info.Selections[function]; selection != nil {
			return selection.Obj()
		}
		return pkg.info.Uses[function.Sel]
	}
	return nil
}

func functionObject(pkg *checkedPackage, name, receiverType string) types.Object {
	if pkg == nil {
		return nil
	}
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			function, ok := declaration.(*ast.FuncDecl)
			if !ok || function.Name.Name != name {
				continue
			}
			object, _ := pkg.info.Defs[function.Name].(*types.Func)
			if object == nil {
				continue
			}
			signature, _ := object.Type().(*types.Signature)
			if receiverType == "" && signature.Recv() == nil {
				return object
			}
			if signature.Recv() != nil && isPointerToNamed(signature.Recv().Type(), pkg.listed.ImportPath, receiverType) {
				return object
			}
		}
	}
	return nil
}

func assertNoRawAuthority(pkg *checkedPackage) {
	if pkg == nil {
		fail("gitstore package was not loaded for authority audit")
	}
	for _, file := range pkg.files {
		for _, declaration := range file.Decls {
			switch declaration := declaration.(type) {
			case *ast.FuncDecl:
				if declaration.Name.IsExported() && (strings.Contains(declaration.Name.Name, "RepositoryMutation") || strings.Contains(declaration.Name.Name, "MigrationSeal")) {
					fail("raw gitstore authority is exported across package: %s", declaration.Name.Name)
				}
			case *ast.GenDecl:
				for _, spec := range declaration.Specs {
					typeSpec, ok := spec.(*ast.TypeSpec)
					if ok && typeSpec.Name.IsExported() && strings.Contains(typeSpec.Name.Name, "RepositoryMutation") {
						fail("raw gitstore authority type is exported across package: %s", typeSpec.Name.Name)
					}
				}
			}
		}
	}
}

func readMethodInventory(path string) map[string]string {
	result := map[string]string{}
	for _, line := range readNonemptyLines(path) {
		parts := strings.Fields(line)
		if len(parts) != 2 || !oneOf(parts[0], "authority", "coordination", "mutation", "read") {
			fail("invalid method inventory row: %q", line)
		}
		if _, exists := result[parts[1]]; exists {
			fail("duplicate Store method classification: %s", parts[1])
		}
		result[parts[1]] = parts[0]
	}
	return result
}

func readTypeInventory(path string) map[string]string {
	result := map[string]string{}
	for _, line := range readNonemptyLines(path) {
		parts := strings.Fields(line)
		if len(parts) != 2 || !oneOf(parts[0], "authority", "error", "value") {
			fail("invalid exported type inventory row: %q", line)
		}
		if _, exists := result[parts[1]]; exists {
			fail("duplicate exported type classification: %s", parts[1])
		}
		result[parts[1]] = parts[0]
	}
	return result
}

func readCommandInventory(path string) commandInventory {
	result := commandInventory{kinds: map[string]string{}}
	for _, line := range readNonemptyLines(path) {
		parts := strings.SplitN(line, "\t", 3)
		if len(parts) != 3 || !oneOf(parts[0], "ephemeral", "mutation", "read", "sentinel") {
			fail("invalid command inventory row: %q", line)
		}
		key := parts[1] + "\t" + parts[2]
		if kind, exists := result.kinds[key]; exists && kind != parts[0] {
			fail("conflicting command classification: %s", key)
		}
		result.kinds[key] = parts[0]
		result.keys = append(result.keys, key)
	}
	return result
}

func readNonemptyLines(path string) []string {
	file, err := os.Open(path)
	if err != nil {
		fail("open %s: %v", path, err)
	}
	defer file.Close()
	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		if line := strings.TrimSpace(scanner.Text()); line != "" {
			lines = append(lines, line)
		}
	}
	if err := scanner.Err(); err != nil {
		fail("read %s: %v", path, err)
	}
	return lines
}

func writeLines(path string, lines []string) {
	data := strings.Join(lines, "\n") + "\n"
	if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
		fail("write %s: %v", path, err)
	}
}

func sourceLine(pkg *checkedPackage, position token.Pos) string {
	location := pkg.fset.Position(position)
	lines := pkg.lines[location.Filename]
	if location.Line <= 0 || location.Line > len(lines) {
		return ""
	}
	return strings.TrimSpace(lines[location.Line-1])
}

func relativeGoFile(ags, path string) string {
	relative, err := filepath.Rel(ags, path)
	if err != nil {
		return filepath.ToSlash(path)
	}
	return filepath.ToSlash(relative)
}

func splitLines(source string) []string {
	return strings.Split(strings.ReplaceAll(source, "\r\n", "\n"), "\n")
}

func directChildren(node ast.Node) []ast.Node {
	var children []ast.Node
	ast.Inspect(node, func(candidate ast.Node) bool {
		if candidate == nil || candidate == node {
			return true
		}
		children = append(children, candidate)
		return false
	})
	return children
}

func sortedKeys[T any](values map[string]T) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func sortedCopy(values []string) []string {
	result := append([]string(nil), values...)
	sort.Strings(result)
	return result
}

func equalStrings(left, right []string) bool {
	if len(left) != len(right) {
		return false
	}
	for index := range left {
		if left[index] != right[index] {
			return false
		}
	}
	return true
}

func oneOf(value string, allowed ...string) bool {
	for _, candidate := range allowed {
		if value == candidate {
			return true
		}
	}
	return false
}

func selfTest() {
	source := `package fixture
import runner "os/exec"
type Embedded struct{}
func (*Embedded) Promoted() {}
type Store struct{ *Embedded; Forge func() }
func NewStore() *Store { return &Store{} }
var Forge = NewStore
func (store *Store) Existing() {}
func (different *Store) NewExport() {}
func probe(store *Store) {
	command := runner.Command
	_ = command("git")
	mutate := store.Existing
	mutate()
	_ = (*Store).Existing
}`
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, "fixture.go", source, 0)
	if err != nil {
		panic(err)
	}
	info := &types.Info{Defs: map[*ast.Ident]types.Object{}, Uses: map[*ast.Ident]types.Object{}, Types: map[ast.Expr]types.TypeAndValue{}, Selections: map[*ast.SelectorExpr]*types.Selection{}}
	if _, err := (&types.Config{Importer: importer.Default()}).Check("fixture", fset, []*ast.File{file}, info); err != nil {
		panic(err)
	}
	parent := map[ast.Node]ast.Node{}
	ast.Inspect(file, func(node ast.Node) bool {
		if node == nil {
			return false
		}
		for _, child := range directChildren(node) {
			parent[child] = node
		}
		return true
	})
	pkg := &checkedPackage{
		listed: listedPackage{ImportPath: "fixture"}, fset: fset, files: []*ast.File{file}, info: info,
		lines: map[string][]string{"fixture.go": splitLines(source)}, parent: parent,
	}
	methods := exportedReceiverMethods(pkg, "fixture", "Store")
	if !equalStrings(methods, []string{"Existing", "NewExport"}) {
		panic(fmt.Sprintf("receiver collector missed renamed receiver: %v", methods))
	}
	methodKeys := exportedReceiverMethodKeys(pkg)
	if !equalStrings(methodKeys, []string{"Embedded.Promoted", "Store.Existing", "Store.NewExport", "Store.Promoted"}) {
		panic(fmt.Sprintf("authority receiver collector missed methods: %v", methodKeys))
	}
	typeNames := exportedTypeNames(pkg)
	if !equalStrings(typeNames, []string{"Embedded", "Store"}) {
		panic(fmt.Sprintf("exported type collector missed receiver: %v", typeNames))
	}
	functions := exportedPackageFunctionNames(pkg)
	if !equalStrings(functions, []string{"NewStore"}) {
		panic(fmt.Sprintf("exported function collector missed constructor: %v", functions))
	}
	variables := exportedPackageVarNames(pkg)
	if !equalStrings(variables, []string{"Forge"}) {
		panic(fmt.Sprintf("exported variable collector missed function alias: %v", variables))
	}
	fields := exportedDeclaredFieldKeys(pkg)
	if !equalStrings(fields, []string{"Store.Embedded", "Store.Forge"}) {
		panic(fmt.Sprintf("exported field collector missed authority-bearing fields: %v", fields))
	}
	uses := collectMutationMethodUses(map[string]*checkedPackage{"fixture": pkg}, map[string]bool{"Existing": true}, ".", "fixture")
	forms := map[string]bool{}
	for _, use := range uses {
		forms[use.form] = true
	}
	if !forms["value"] || !forms["alias_call"] || !forms["expression_value"] {
		panic(fmt.Sprintf("method-use collector missed alias/expression: %#v", forms))
	}
	commands := collectExecCommands(pkg)
	if len(commands) != 1 || commands[0].line != `_ = command("git")` {
		panic(fmt.Sprintf("exec collector missed import/function alias: %#v", commands))
	}
	authoritySource := `package authorityfixture
type RepositoryMigrationFreeze struct{}
type Owner struct{}
func (*Owner) SealRepository() {}
type Service struct { DB int; Git int; repositoryMutations *Owner }
func (service *Service) migrate() { seal := service.repositoryMutations.SealRepository; seal(); hidden := service.hidden; hidden() }
func (service *Service) outer() { migrate := service.migrate; alias := migrate; alias(); _ = service.Git }
func (service *Service) hidden() {
    _ = service.DB
    _ = RepositoryMigrationFreeze{}
}
func (service *Service) literalProbe() {
    literal := func() {
        seal := service.repositoryMutations.SealRepository
        seal()
        _ = service.DB + 1
    }
    first := literal
    second := first
    second()
    _ = service.Git
}
`
	authorityFset := token.NewFileSet()
	authorityFile, err := parser.ParseFile(authorityFset, "authority.go", authoritySource, 0)
	if err != nil {
		panic(err)
	}
	authorityInfo := &types.Info{Defs: map[*ast.Ident]types.Object{}, Uses: map[*ast.Ident]types.Object{}, Types: map[ast.Expr]types.TypeAndValue{}, Selections: map[*ast.SelectorExpr]*types.Selection{}}
	if _, err := (&types.Config{}).Check("authorityfixture", authorityFset, []*ast.File{authorityFile}, authorityInfo); err != nil {
		panic(err)
	}
	authorityParents := map[ast.Node]ast.Node{}
	ast.Inspect(authorityFile, func(node ast.Node) bool {
		if node == nil {
			return false
		}
		for _, child := range directChildren(node) {
			authorityParents[child] = node
		}
		return true
	})
	authorityPkg := &checkedPackage{
		listed: listedPackage{ImportPath: "authorityfixture"}, fset: authorityFset, files: []*ast.File{authorityFile}, info: authorityInfo,
		lines: map[string][]string{"authority.go": splitLines(authoritySource)}, parent: authorityParents,
	}
	var literal *ast.FuncLit
	var secondCall *ast.CallExpr
	ast.Inspect(authorityFile, func(node ast.Node) bool {
		if candidate, ok := node.(*ast.FuncLit); ok {
			literal = candidate
		}
		if call, ok := node.(*ast.CallExpr); ok {
			if identifier, ok := call.Fun.(*ast.Ident); ok && identifier.Name == "second" {
				secondCall = call
			}
		}
		return true
	})
	if literal == nil || secondCall == nil {
		panic("migration authority literal fixture was not parsed")
	}
	literalObject := types.NewFunc(literal.Pos(), nil, "literalFixture", authorityInfo.TypeOf(literal).(*types.Signature))
	targets, resolved := resolveFunctionTargets(authorityPkg, secondCall.Fun, collectFunctionAliasDefinitions(authorityPkg), map[*ast.FuncLit]types.Object{literal: literalObject}, map[types.Object]bool{})
	if !resolved || len(targets) != 1 || targets[0] != literalObject {
		panic(fmt.Sprintf("migration authority collector missed multi-hop literal alias: resolved=%v targets=%#v", resolved, targets))
	}
	violations := collectMigrationServiceAuthorityViolations(authorityPkg, "authorityfixture", "authorityfixture", "authorityfixture")
	violationSet := map[string]bool{}
	for _, violation := range violations {
		violationSet[violation] = true
	}
	if len(violations) != 5 || !violationSet["_ = service.DB + 1"] || !violationSet["_ = service.Git"] {
		panic(fmt.Sprintf("migration authority collector did not reject renamed helper/literal/model access: %#v", violations))
	}
	fmt.Println("AGS repository mutation inventory self-test passed.")
}
