molecule-ai-plugin-gh-identity/pluginloader/pluginloader.go
claude-ceo-assistant b2ed330475
All checks were successful
CI / Shellcheck + wrapper tests (pull_request) Successful in 24s
CI / Go build + test + vet (pull_request) Successful in 1m10s
fix(post-suspension): vanity import path go.moleculesai.app/plugin/gh-identity (closes molecule-ai/internal#71 phase 2)
Migrates go.mod + the single Go import + a stale go.mod replace-comment
+ README require example off the dead github.com/Molecule-AI/ identity
and onto the vanity host go.moleculesai.app, owned by us. Adds a
structural lint test that walks every *.go / *.mod / Dockerfile / *.md
and rejects future re-introduction of either the literal
github.com/Molecule-AI/ string or the historical
Molecule-AI/molecule-monorepo path.

Why a vanity host and not git.moleculesai.app/molecule-ai/...:
the latter just relocates the lock-in. 2026-05-06 didn't teach us
'Gitea > GitHub'; it taught us 'vendor URLs in source code are a future
incident'. With go.moleculesai.app, the next SCM migration edits one
config (the responder) instead of every import statement. See
internal#71 for the full rationale + the alternatives rejected.

Smoke-validation for this migration sweep — chosen as the first PR
because it has the smallest blast radius (1 import line).
Following PRs: molecule-cli, molecule-controlplane, molecule-core
(parallel after this lands).

Test plan:
- go build ./... — clean
- go test ./... — internal/ghidentity green
- TestNoLegacyGitHubImportPaths — passes on clean tree, FAILS on
  injected canary string (mutation-tested before commit)

Open dependency: go.moleculesai.app responder must be deployed before
external 'go install go.moleculesai.app/plugin/gh-identity@latest'
works. Internal builds are unaffected (self-referential module path,
no external fetch needed). Responder code prepared in worker.js;
deploy tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:23:51 +00:00

51 lines
1.7 KiB
Go

// Package pluginloader builds a provisionhook.Registry populated with
// the gh-identity Mutator so the platform's cmd/server can wire this
// plugin into the workspace provision chain.
//
// Operators integrating the plugin:
//
// reg, err := pluginloader.BuildRegistry()
// if err != nil { log.Fatalf("gh-identity: %v", err) }
// wh.SetEnvMutators(append(existingMutators, reg.Mutators()...))
//
// The plugin is INTENTIONALLY non-fatal on missing config: absent the
// optional MOLECULE_GH_IDENTITY_CONFIG_FILE env var, this still
// registers a Mutator that reads workspace-supplied roles and emits
// wrapper env — just without @me owner rewriting. Operators who want
// owner rewriting set MOLECULE_GH_IDENTITY_CONFIG_FILE.
package pluginloader
import (
"fmt"
"os"
"go.moleculesai.app/plugin/gh-identity/internal/ghidentity"
)
// Result bundles what BuildRegistry returns — a single mutator plus
// whatever config it loaded, so test harnesses can inspect both.
type Result struct {
Mutator *ghidentity.Mutator
Config *ghidentity.Config
}
// BuildRegistry reads MOLECULE_GH_IDENTITY_CONFIG_FILE (optional),
// constructs the Mutator, and returns it.
//
// Error modes:
// - config file set but unreadable → error (operator bug; fail loud)
// - config file unset → fine, use empty map
// - config file set but non-existent → fine, use empty map (lets you
// point at a file that CI hasn't created yet without blocking boot)
func BuildRegistry() (*Result, error) {
cfgPath := os.Getenv("MOLECULE_GH_IDENTITY_CONFIG_FILE")
cfg, err := ghidentity.LoadConfig(cfgPath)
if err != nil {
return nil, fmt.Errorf("gh-identity: %w", err)
}
return &Result{
Mutator: &ghidentity.Mutator{Config: cfg},
Config: cfg,
}, nil
}