This guide is for contributors to metacore-kernel. If you are embedding
the kernel into an application, read CONSUMER_GUIDE.md
instead.
- Prerequisites
- Clone and bootstrap
- Repository layout
- Tests, vet, race detector
- Working with the SDK locally
- Branching and contribution flow
- Style and architectural guardrails
- Debugging the WASM runtime
- Releasing
Embedding the kernel in a host? You want
CONSUMER_GUIDE.mdandembedding-quickstart.md. Working on the dynamic CRUD framework? Readdynamic-system.mdfirst — fixtures, handler tests, and migration helpers indynamic/*_test.go,metadata/*_test.go, andinstaller/*_test.goassume the contracts documented there.
| Tool | Version |
|---|---|
| Go | 1.25+ (matches go.mod and CI) |
| Git | 2.30+ |
| GitHub CLI | recommended for PRs and releases |
| Make | optional |
The kernel depends on wazero (pure Go WASM runtime — no cgo), Fiber v2,
GORM, gofiber/websocket, prometheus/client_golang, and goose for
versioned migrations. There are no native build tools required.
go env -w GOPRIVATE="github.com/asteby/*"
git config --global url."git@github.com:".insteadOf "https://github.com/"
mkdir -p ~/projects && cd ~/projects
git clone git@github.com:asteby/metacore-kernel.git
git clone git@github.com:asteby/metacore-sdk.git
cd metacore-kernel
go mod downloadThe two repos must live as siblings — the kernel's go.mod carries
replace github.com/asteby/metacore-sdk => ../metacore-sdk so SDK changes
are picked up without publishing a tag.
Verify the toolchain:
go version # expect 1.25.x
go vet ./...
go test ./...metacore-kernel/
├── auth/ JWT, login/refresh handlers, Fiber middleware
├── bridge/ Adapters: kernel actions/tools/webhooks ↔ host integrations
├── bundle/ Addon bundle I/O (`bundle.tgz` reader/writer)
├── docs/ Developer-facing documentation (this directory)
├── dynamic/ Generic CRUD over registered models
├── eventlog/ Org-scoped persisted event log with cursor pagination
├── events/ In-process pub/sub bus for addons
├── flow/ Workflow primitives reused by addons
├── host/ `App` and `Host` facades
├── httpx/ HTTP helpers shared across handlers
├── installer/ Install/enable/disable/uninstall flow
├── lifecycle/ Addon contract, registry, interceptors
├── log/ Builder-style logger (legacy; use obs/ for new code)
├── manifest/ Declarative addon manifest schema
├── metadata/ TableMetadata/ModalMetadata registry, cache, handler
├── metrics/ Prometheus integration
├── migrations/ Goose-based versioned migration runner
├── modelbase/ Stable interfaces and base structs
├── navigation/ Sidebar merger
├── notifications/ Delivery queue + workers + ChannelHandler
├── obs/ Structured slog logger with request-id propagation
├── permission/ Role + capability checks
├── push/ Web Push (VAPID)
├── query/ Filter/sort/paginate query builder
├── runtime/wasm/ wazero-based WASM runtime
├── security/ Enforcer, Capabilities, HMAC, secretbox, nonce
├── strings/ Shared string helpers
├── tool/ Addon tool runtime + dispatcher + registry
├── webhooks/ Outbound HMAC-signed webhooks with retry
├── ws/ WebSocket hub
├── ARCHITECTURE.md The four laws of the kernel — read before adding a package
├── CHANGELOG.md Release history
└── README.md Top-level overview
Each package owns its tests (*_test.go), a doc.go where useful, and a
single coherent responsibility (see ARCHITECTURE.md, Law 0).
CI runs the same commands you should run locally before opening a PR
(.github/workflows/ci.yml):
go vet ./...
go test -race -coverprofile=coverage.out ./...Useful subset patterns:
# A single package, verbose
go test -race -v ./runtime/wasm/...
# Watch a single test
go test -race -run TestEnforcer_Shadow ./security/...
# Coverage HTML
go tool cover -html=coverage.outThe race detector is mandatory — the kernel hosts long-lived goroutines
(WS hub, webhook dispatcher, notification workers) and most regressions
surface only under -race.
During day-to-day work the kernel resolves the SDK from ../metacore-sdk via
the replace directive in go.mod. To preview the build that consumers will
actually pull:
go mod edit -dropreplace github.com/asteby/metacore-sdk
go mod tidy
go test ./...Re-add the replace before resuming local work:
go mod edit -replace github.com/asteby/metacore-sdk=../metacore-sdk
go mod tidyNever commit a go.mod without the replace directive on a feature branch —
the release script drops it as part of tagging.
- Branch from
main. Use a descriptive prefix:feat/,fix/,refactor/,docs/,chore/. - Conventional Commits are enforced for changelog generation. The
feat:/fix:/BREAKING CHANGE:markers drive the SemVer decision at release time (seeRELEASE.md). - One coherent change per PR. Public-API changes need a corresponding
// Deprecated:comment if they replace existing symbols. - Open the PR, let CI go green, request review, squash-merge.
The full statement is in ARCHITECTURE.md. The four
points to internalize before contributing:
- Stability by interfaces, not structs. Every public contract lives
behind an interface (
AuthUser,AuthOrg,ModelDefiner, …). Apps extend by composition; the kernel evolves without breaking them. - Opinionated defaults, pluggable escape hatches. Constructors take a
Config; behavior overrides areWith*methods, never forks. - Services are mandatory, handlers are optional. A
service.gomust never importgithub.com/gofiber/fiber/v2. Handlers are thin Fiber wrappers around services. - What belongs in the kernel. Substrate that every web app needs on day one. Optional reusable infra goes in the SDK; product-specific code goes in the app. When in doubt, default to keeping it out.
Additional dependency rules:
modelbase/imports nothing beyondgorm.io/gorm,github.com/google/uuid,golang.org/x/crypto/bcrypt. No Fiber, no HTTP, no SDK.obs/imports only the standard library. It is the most upstream package.- No kernel package may import
github.com/asteby/metacore-sdk/pkg/*unless the dependency is on a stable public type (manifest, bundle schema).
The WASM runtime lives in runtime/wasm/. A handful of patterns that pay
off when diagnosing addon failures:
- Reproduce in
wasm_test.go. The package ships a fixture that compiles a tiny Go-to-WASM module and runs it through the full ABI; copy that test and add the failing scenario. - Inspect host imports.
capabilities.goregisters every host import. If the addon'simported function not founderrors at instantiation time, the symbol name is missing fromregisterHostModule. - Check the enforcer mode. Locally the default is
ModeShadow; turn onModeEnforce(METACORE_ENFORCE=1) when chasing capability bugs so they surface as errors instead of warnings. - Memory and timeouts. Defaults are 64 MiB / 10 s per invocation, with a
global 256 MiB ceiling on the runtime config. Override per-addon via
manifest.BackendSpec.
Most kernel changes that affect consumer apps land in dynamic/,
metadata/, permission/, installer/, or manifest/. A few patterns
that pay off when iterating there:
- End-to-end fixture per package.
dynamic/service_test.gobuilds an in-memory SQLite DB, registers a fake model, and exercises Create / Get / List / Update / Delete. Copy that fixture to add a regression test for a new code path; do not stand up Postgres unless you are testing RLS or a Postgres-only feature. - Handler tests use
app.Test().metadata/handler_test.goshows the pattern: build a Fiber app, mount the handler, sendhttptest-style requests, assert the JSON envelope. Keep handler tests as thin as possible — service-level tests cover correctness, handler tests cover status codes and the wire envelope. - Manifest fixtures live in tests.
manifest/validate_test.goandinstaller/dualwrite_test.godeclare manifests inline. There is no separate fixture directory; if you need a complex one, add it next to the test that uses it. - Schema-affecting changes touch three places. Adding a column type
to
dynamic/model.go:columnGoTypealso requiresdynamic/schema.go:pgColumnTypeand a corresponding entry indynamic-system.mdAllowed column types. Renaming or removing a column type is a MAJOR bump because addon manifests in the wild depend on it. - Public response shapes are wire contracts. The JSON tags on
modelbase.TableMetadata,modelbase.ModalMetadata, thedynamic.Handlerenvelope ({success, data, meta}) andquery.PageMetaare stable across minors. Adding a field is fine; removing or renaming one is a MAJOR.
The release process — version selection, tag publication, GoReleaser,
retract — is documented end-to-end in RELEASE.md. In
short: git push origin vX.Y.Z runs the release workflow, which runs the
test suite, indexes the proxy and publishes a GitHub Release. Consumers
discover the new version through Renovate / Dependabot polling the Go
proxy on their own schedule.