Platform Module — github.com/opsplanetools/platform
Build once. Import everywhere. The platform module is the shared infrastructure layer that every OpsPlane product imports. Each product focuses on domain logic only — auth, audit, billing, crypto, errors, and middleware are solved once.
Why a Separate Module
Without it, every product rebuilds the same infrastructure:
Arcan: build auth + audit + billing + crypto + ... (weeks)
OpsPlane: build auth + audit + billing + crypto + ... (weeks, again)
OpsTools: build auth + audit + billing + ... (weeks, again)
OpsKeys: build auth + audit + billing + ... (weeks, again)
With it, each product is domain logic only:
Platform: build once, test thoroughly (2-3 weeks)
Arcan: import platform → focus on engines, plugins (domain only)
OpsPlane: import platform → focus on AI, intelligence (domain only)
OpsTools: import platform → focus on tool runners (domain only)
OpsKeys: import platform → focus on SSH key management (domain only)
Module Identity
Module: github.com/opsplanetools/platform
Vanity: (future) platform.opsplane.ai
License: Apache 2.0
Visibility: Public (OSS contributors can see and use it)
Reuse Matrix
| Package | Arcan | OpsPlane.ai | OpsTools | SecretVault | OpsKeys |
|---|---|---|---|---|---|
auth | Yes | Yes | Yes | Yes | Yes |
errors | Yes | Yes | Yes | Yes | Yes |
audit | Yes | Yes | Yes | Yes | Yes |
billing | Enterprise | Pro/Team/Biz | Pro $9/mo | Future | Future |
crypto | Yes | Yes | Yes | Yes | Yes |
store | Yes | Yes | Yes | Yes | Yes |
middleware | Yes | Yes | Yes | Yes | Yes |
health | Yes | Yes | Yes | Yes | Yes |
config | Yes | Yes | Yes | Yes | Yes |
telemetry | Yes | Yes | Yes | Yes | Yes |
10 packages. 5 products. Zero duplication.
Package Design Principles
Every package in the platform module follows these rules:
-
Zero product knowledge. The package never references Arcan, OpsPlane, OpsTools, or any specific product. It provides infrastructure, not business logic.
-
Functional options for configuration. Every constructor uses the
WithXxx()pattern. Sensible defaults mean zero-config works, but everything is customizable. -
Interfaces for extension. The caller provides implementations (stores, resolvers, sinks). The package provides the engine.
-
No mandatory dependencies. Each package can be imported independently.
authdoesn't requirebilling.auditdoesn't requirestore. Optional integrations are enabled viaWithXxx()options. -
Test helpers included. Every package provides mock implementations for testing:
auth.MockKeyStore,audit.MockDispatcher,store.MockDB, etc.
Module Structure
github.com/opsplanetools/platform/
├── go.mod
├── go.sum
├── README.md
├── CLAUDE.md # Points to arcan-oss/constitution for standards
│
├── auth/
│ ├── middleware.go # NewMiddleware + functional options
│ ├── apikey.go # API key generation, hashing, prefix validation
│ ├── jwt.go # JWT validation (JWKS endpoint, static secret)
│ ├── k8s.go # Kubernetes service account auth
│ ├── interfaces.go # KeyStore, UserResolver, TokenClaims
│ ├── options.go # WithAPIKeyPrefix, WithJWKS, WithAnonymous, etc.
│ ├── mock.go # MockKeyStore, MockUserResolver (for tests)
│ └── auth_test.go
│
├── errors/
│ ├── errors.go # Error types: NotFound, Validation, Forbidden, etc.
│ ├── http.go # WriteJSON — HTTP error response writer
│ ├── cli.go # PrintCLI — terminal error card formatter
│ ├── codes.go # Error code enum and HTTP status mapping
│ └── errors_test.go
│
├── audit/
│ ├── dispatcher.go # NewDispatcher, Emit, async buffered channel
│ ├── sink_webhook.go # HMAC-signed webhook delivery with retry
│ ├── sink_syslog.go # RFC 5424 syslog (TCP/UDP)
│ ├── interfaces.go # Sink, Store interfaces
│ ├── options.go # WithWebhookSink, WithSyslogSink, WithBufferSize
│ ├── mock.go # MockDispatcher (captures events for assertions)
│ └── audit_test.go
│
├── billing/
│ ├── service.go # NewService + functional options
│ ├── stripe.go # Stripe API: customers, subscriptions, invoices
│ ├── webhook.go # HandleWebhook — Stripe event processor
│ ├── entitlements.go # HasFeature, CheckQuota, feature gating
│ ├── interfaces.go # LicenseStore, EntitlementChecker
│ ├── options.go # WithStripeKey, WithPlans, WithTrialDays
│ ├── mock.go # MockBillingService (for tests without Stripe)
│ └── billing_test.go
│
├── crypto/
│ ├── encryptor.go # NewEncryptor, Encrypt/Decrypt (AES-256-GCM)
│ ├── keymanager.go # Key manager factory + HKDF derivation
│ ├── kms_aws.go # AWS KMS envelope encryption
│ ├── kms_gcp.go # GCP Cloud KMS
│ ├── kms_azure.go # Azure Key Vault
│ ├── file.go # File-based key (auto-generate, 0600 permissions)
│ ├── options.go # WithFileKey, WithAWSKMS, WithAutoGenerate
│ ├── mock.go # MockEncryptor (deterministic for tests)
│ └── crypto_test.go
│
├── store/
│ ├── sqlite.go # NewSQLite — WAL mode, busy timeout, pragmas
│ ├── postgres.go # NewPostgres — connection pool, SSL mode
│ ├── migrate.go # Migration runner (embedded SQL files)
│ ├── helpers.go # SoftDelete, WithDeleted, NowUTC, UUIDv4
│ ├── interfaces.go # DB interface (wraps *sql.DB with helpers)
│ ├── options.go # WithMigrations, WithWAL, WithPoolSize
│ ├── mock.go # MockDB, in-memory SQLite for tests
│ └── store_test.go
│
├── middleware/
│ ├── requestid.go # X-Request-Id generation/extraction
│ ├── logger.go # Structured slog request/response logging
│ ├── ratelimit.go # Per-IP and per-token rate limiting
│ ├── cors.go # Configurable CORS headers
│ ├── recoverer.go # Panic → 500 with request_id in response
│ ├── tls.go # TLS auto-setup (self-signed if not configured)
│ └── middleware_test.go
│
├── health/
│ ├── checker.go # NewChecker, WithCheck, Handler
│ ├── mock.go # MockChecker
│ └── health_test.go
│
├── config/
│ ├── loader.go # Load — flags > env > file > defaults
│ ├── options.go # WithEnvPrefix, WithFile, WithDefaults
│ └── config_test.go
│
└── telemetry/
├── logging.go # Slog handler setup (text vs JSON)
├── metrics.go # Prometheus counter/histogram helpers
├── context.go # RequestContext, typed context keys, propagation
└── telemetry_test.go
Usage Examples
Arcan Server
package main
import (
"github.com/opsplanetools/platform/auth"
"github.com/opsplanetools/platform/audit"
"github.com/opsplanetools/platform/config"
"github.com/opsplanetools/platform/crypto"
"github.com/opsplanetools/platform/errors"
"github.com/opsplanetools/platform/health"
"github.com/opsplanetools/platform/middleware"
"github.com/opsplanetools/platform/store"
"github.com/opsplanetools/platform/telemetry"
)
func main() {
// Config
cfg, _ := config.Load(
config.WithEnvPrefix("ARCAN"),
config.WithFile("~/.arcan/config.yaml"),
)
// Logging
telemetry.SetupLogging(cfg.String("log.format")) // "text" or "json"
// Database
db, _ := store.NewSQLite(cfg.String("store.path"),
store.WithMigrations(arcanMigrations),
store.WithWAL(true),
)
// Encryption
enc, _ := crypto.NewEncryptor(
crypto.WithFileKey("~/.arcan/master.key"),
crypto.WithAutoGenerate(true),
)
// Auth
authMW := auth.NewMiddleware(
auth.WithAPIKeyPrefix("arc_"),
auth.WithAPIKeyStore(db), // db implements auth.KeyStore
)
// Audit
dispatcher := audit.NewDispatcher(
audit.WithStoreSink(db),
audit.WithProductName("arcan"),
)
// Health
hc := health.NewChecker(
health.WithVersion(version, commit, built),
health.WithMode("standalone"),
health.WithCheck("database", db.HealthCheck),
health.WithCheck("encryption", enc.HealthCheck),
)
// Router
r := chi.NewRouter()
r.Use(middleware.RequestID())
r.Use(middleware.Logger(slog.Default()))
r.Use(middleware.RateLimit(100))
r.Use(authMW.Handler)
r.Get("/api/v1/health", hc.Handler)
// ... Arcan-specific handlers (KV, engines, plugins)
}
OpsPlane Server
func main() {
cfg, _ := config.Load(
config.WithEnvPrefix("OPSPLANE"),
config.WithFile("~/.opsplane/config.yaml"),
)
db, _ := store.NewPostgres(cfg.String("database.url"),
store.WithMigrations(opsplaneMigrations),
store.WithPoolSize(20),
)
authMW := auth.NewMiddleware(
auth.WithAPIKeyPrefix("ops_"),
auth.WithAPIKeyStore(db),
auth.WithJWKS("https://clerk.example.com/.well-known/jwks.json"),
)
billing := billing.NewService(
billing.WithStripeKey(os.Getenv("STRIPE_SECRET_KEY")),
billing.WithPlans(opsplanePlans),
billing.WithLicenseStore(db),
)
// ... OpsPlane-specific handlers (AI, intelligence, dashboards)
}
OpsTools Server
func main() {
cfg, _ := config.Load(
config.WithEnvPrefix("OPSTOOLS"),
)
authMW := auth.NewMiddleware(
auth.WithSupabaseJWT(cfg.String("supabase.project"), cfg.String("supabase.jwt_secret")),
auth.WithAnonymous(true), // tools work without auth
)
billing := billing.NewService(
billing.WithStripeKey(os.Getenv("STRIPE_SECRET_KEY")),
billing.WithPlans(map[string]billing.Plan{
"pro": {PriceID: "price_xxx", Features: []string{"ai-tools"}},
}),
)
// ... OpsTools-specific handlers (tool runners, Bedrock integration)
}
Interface Contracts
Each package defines interfaces that callers implement. This is how the platform stays product-agnostic:
// auth/interfaces.go
type KeyStore interface {
LookupKeyHash(ctx context.Context, hash string) (*KeyRecord, error)
}
type UserResolver interface {
ResolveUser(ctx context.Context, subject string) (*User, error)
}
// audit/interfaces.go
type Sink interface {
Send(ctx context.Context, event Event) error
Name() string
}
type Store interface {
InsertAuditEvent(ctx context.Context, event Event) error
}
// billing/interfaces.go
type LicenseStore interface {
GetLicense(ctx context.Context, id string) (*License, error)
UpdateLicense(ctx context.Context, license *License) error
GetActivation(ctx context.Context, fingerprint string) (*Activation, error)
CreateActivation(ctx context.Context, activation *Activation) error
}
type EntitlementChecker interface {
HasFeature(ctx context.Context, userID string, feature string) (bool, error)
}
Every product's database layer implements these interfaces. The platform never imports the product — the product imports the platform.
Testing
Every package includes mock implementations:
// In product tests:
import "github.com/opsplanetools/platform/auth"
func TestMyHandler(t *testing.T) {
mockKeys := auth.NewMockKeyStore()
mockKeys.AddKey("arc_test123", "user-1", []string{"secrets:read"})
authMW := auth.NewMiddleware(
auth.WithAPIKeyPrefix("arc_"),
auth.WithAPIKeyStore(mockKeys),
)
// test handler with real auth middleware, mock store
}
import "github.com/opsplanetools/platform/audit"
func TestAuditEmission(t *testing.T) {
mock := audit.NewMockDispatcher()
// ... trigger action ...
events := mock.Events()
assert.Len(t, events, 1)
assert.Equal(t, "secret.created", events[0].Type)
}
import "github.com/opsplanetools/platform/store"
func TestMigrations(t *testing.T) {
db := store.NewMockDB(t) // in-memory SQLite with test cleanup
// ... test store operations
}
Versioning
Module versioning: semver (v0.1.0, v0.2.0, v1.0.0)
Rules:
- Adding new options (WithXxx) → minor version bump
- Adding new packages → minor version bump
- Adding new interface methods → MAJOR version bump (breaks implementers)
- Changing function signatures → MAJOR version bump
- Bug fixes → patch version bump
All products pin to a specific version:
require github.com/opsplanetools/platform v0.3.2
Products upgrade on their own schedule — platform releases don't force product releases.
Development Workflow
1. Need a new platform feature?
→ Open PR on github.com/opsplanetools/platform
→ Tests pass, review, merge
→ Tag new version
2. Product wants the feature?
→ go get github.com/opsplanetools/[email protected]
→ Use the new package/option
→ No other product is affected
3. Breaking change needed?
→ Major version bump
→ Products upgrade when ready
→ Old version continues working