OpenRisk Backend Charter
SkillDev toolsOpenRisk backend engineering charter — Clean Architecture layering, tenant isolation patterns, typed errors, transactions, migrations, scoring integration and the backend definition of done. Load before implementing or reviewing Go code.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OpenRisk Backend Charter skill
What this skill tells your AI
The instructions your AI receives, as published by opendefender/openrisk in .claude/skills/openrisk-backend-charter/SKILL.md and read by ahel’s review.
Layering — dependency direction is inward only
/cmd/server/main.go DI container, graceful shutdown
/internal/domain/ pure entities. A gorm.io or fiber import here is a merge blocker.
/internal/application/ one use case per file. Declares repository interfaces.
/internal/infrastructure/ GORM repositories, Redis, messaging, integrations
/internal/api/http/ Fiber handlers, DTOs, middleware, RBAC declarations
/pkg/ scoring, cti, notify, export, ai, crq — pure, testable
Tenant isolation — the pattern
Every repository method takes the tenant from the context and puts it in the WHERE clause:
func (r *GormRiskRepository) FindByID(ctx context.Context, id uuid.UUID) (*domain.Risk, error) {
tenantID, ok := middleware.TenantFromContext(ctx)
if !ok {
return nil, domain.ErrForbidden // fail-closed, never uuid.Nil
}
var risk domain.Risk
if err := r.db.WithContext(ctx).
Where("id = ? AND tenant_id = ?", id, tenantID).
First(&risk).Error; err != nil { ... }
}
Table without a tenant_id column (e.g. risk_histories): gate through the
parent with an explicit ownership check, and JOIN for list queries.
func (s *Service) ownsRisk(ctx context.Context, riskID uuid.UUID) error
Sequential integer IDs are enumerable. Any handler taking one calls ownsX
before reading or writing. Return 404 on a foreign object, never 403 —
do not confirm the object exists.
Typed errors — the only four
ErrNotFound · ErrForbidden · ErrConflict · ErrValidation.
Wrap with context: fmt.Errorf("application.CreateRisk: %w", err).
Mapped to HTTP status in exactly one place. Never inline in a handler.
Tests — minimum per use case
TestXxx_Success · TestXxx_NotFound · TestXxx_Unauthorized
plus, for anything with a tenant dimension, TestXxx_CrossTenant proving
tenant A cannot reach tenant B's object by ID.
Table-driven. Test names describe behaviour:
TestCreateRisk_RejectsDuplicateReference, not TestCreateRisk2.
Migrations
golang-migrate, forward-only, one concern per file, numbered sequentially.
Adding a tenant_id to an existing table requires a backfill step and a new
unique index scoped by tenant. Test against a fresh database before reporting.
Any new model: confirm it is registered in AutoMigrate or explain why not.
Definition of Done — backend
gofmt -l .returns nothinggo vet ./...cleango test ./... -race -count=1green, no new failures- Every new query re-read for its tenant filter
- Cross-tenant test written for every new endpoint
- Typed errors, no raw
errors.Newescaping the domain - OpenAPI spec updated if a route changed
Signals
- GitHub stars
- 33
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
openrisk-backend-charter- Source
- github.com/opendefender/openrisk