⚠️ v0.x, pre-GA — no stability guarantees; breaking changes and Go-version bumps may happen anytime.
Cell-native Go Engineering Foundation.
GoCell provides Cell/Slice runtime primitives, governance toolchain, and built-in Cells for building reliable Go services with the Slice-Cell architecture.
The todoorder example requires JWT keys and a service secret for the internal listener. Run the commands from the repository root. If you have not cloned it yet:
git clone https://github.com/ghbvf/gocell.git
cd gocellThen copy-paste the steps below in a single terminal:
# Step 1 — generate RS256 key pair
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out /tmp/gocell-todoorder-jwt.key
openssl rsa -in /tmp/gocell-todoorder-jwt.key -pubout \
-out /tmp/gocell-todoorder-jwt.pub
# Step 2 — set required env vars
export GOCELL_JWT_PRIVATE_KEY="$(cat /tmp/gocell-todoorder-jwt.key)"
export GOCELL_JWT_PUBLIC_KEY="$(cat /tmp/gocell-todoorder-jwt.pub)"
export GOCELL_JWT_ISSUER=todoorder-local
export GOCELL_JWT_AUDIENCE=gocell
export GOCELL_TODOORDER_SERVICE_SECRET="$(openssl rand -base64 32)"
# Step 3 — mint a test RS256 token (role:customer, signed by the local key)
# reads $GOCELL_JWT_PRIVATE_KEY/$GOCELL_JWT_ISSUER/$GOCELL_JWT_AUDIENCE from env
export TODOORDER_TOKEN="$(go run ./examples/todoorder/localtoken)"
# Step 4 — start the server (primary :8082, internal 127.0.0.1:9082, health 127.0.0.1:9092)
go run ./examples/todoorder &
# Step 5 — wait for readiness (/healthz and /readyz live on the health listener)
until curl -fsS http://127.0.0.1:9092/readyz >/dev/null; do sleep 0.2; done
# Step 6 — exercise the API
curl -s -X POST http://localhost:8082/api/v1/orders/ \
-H "Authorization: Bearer $TODOORDER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"item":"my first order"}' | jq .
curl -s http://localhost:8082/api/v1/orders/ \
-H "Authorization: Bearer $TODOORDER_TOKEN" | jq .Check the application logs — you should see event.order.created consumed.
For full configuration options (production hardening, real-mode adapters, multi-pod), see examples/todoorder/README.md.
For a full local Docker stack (PostgreSQL + Redis + corebundle), see Local Docker Deploy guide.
┌─────────────────────────────────────────────────┐
│ Assembly (physical deployment unit) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Cell │ │ Cell │ │ Cell │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Slice │ │ │ │ Slice │ │ │ │ Slice │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Slice │ │ │ │ Slice │ │ │ │ Slice │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │
│ └───── Contract ─┘───── Contract ┘ │
└─────────────────────────────────────────────────┘
| Concept | Description |
|---|---|
| Cell | Independent domain unit with lifecycle (Init/Start/Stop/Health). Types: core, edge, support. |
| Slice | A single responsibility within a Cell (e.g., sessionlogin, ordercreate). |
| Contract | Cross-Cell communication boundary (HTTP, event, command). Cells never import each other directly. |
| Assembly | Physical deployment — groups Cells into a runnable binary. |
| Journey | End-to-end acceptance specification spanning multiple Cells and Contracts. |
| Level | Name | Pattern | Example |
|---|---|---|---|
| L0 | LocalOnly | Single slice, no side effects | Validation, computation |
| L1 | LocalTx | Single cell transaction | Session creation |
| L2 | OutboxFact | Transaction + outbox event | Order creation + event publish |
| L3 | WorkflowEventual | Cross-cell eventual consistency | Audit trail, projections |
| L4 | DeviceLatent | High-latency device loop | Command → ack with timeout |
GoCell uses codegen to eliminate boilerplate. The workflow is:
define contract.yaml → run gocell generate contract → import the generated handler.
For a deeper walkthrough see docs/guides/codegen-new-endpoint.md.
mkdir -p contracts/http/mycell/hello/v1
mkdir -p cells/mycell/slices/myhelloCreate contracts/http/mycell/hello/v1/contract.yaml:
id: http.mycell.hello.v1
kind: http
ownerCell: mycell
consistencyLevel: L0
lifecycle: active
endpoints:
server: mycell
clients: [] # external callers (cell ids or actor ids); empty = open API
http:
method: GET
path: /api/v1/hello
successStatus: 200
noContent: false # true only for endpoints whose contract returns no body (e.g. 204 DELETE)
auth:
public: true # JWT-exempt; mutually exclusive with passwordResetExempt (FMT-26)
schemaRefs:
response: response.schema.jsonCreate contracts/http/mycell/hello/v1/response.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}Create cells/mycell/cell.yaml:
id: mycell
type: core
consistencyLevel: L0
owner:
team: my-team
role: my-owner
verify:
smoke:
- mycell/smokeCreate cells/mycell/slices/myhello/slice.yaml:
id: myhello
belongsToCell: mycell
consistencyLevel: L1
contractUsages:
- contract: http.mycell.hello.v1
role: serve
verify:
unit: myhello/unit
contract: myhello/contract
allowedFiles:
- handler.gogo run ./cmd/gocell generate contract
# → generated/contracts/http/mycell/hello/v1/types_gen.go
# → generated/contracts/http/mycell/hello/v1/iface_gen.go
# → generated/contracts/http/mycell/hello/v1/handler_gen.goCreate cells/mycell/slices/myhello/handler.go:
package myhello
import (
"context"
hellog "github.com/ghbvf/gocell/generated/contracts/http/mycell/hello/v1"
kcell "github.com/ghbvf/gocell/framework/kernel/cell"
)
// HelloAdapter implements hellog.Service for http.mycell.hello.v1.
type HelloAdapter struct{}
func (HelloAdapter) Hello(ctx context.Context, _ *hellog.Request) (*hellog.Response, error) {
return &hellog.Response{Message: "hello from mycell"}, nil
}
// Handler wires the generated contract handler for the myhello slice.
type Handler struct{ h *hellog.Handler }
func NewHandler() *Handler {
return &Handler{h: hellog.NewHandler(HelloAdapter{})}
}
func (h *Handler) RegisterRoutes(mux kcell.RouteHandler) error {
return h.h.RegisterRoutes(mux)
}Cell metadata and Init wiring are produced by codegen from cell.yaml — set goStructName: MyCell in the yaml and run go run ./cmd/gocell generate cell --all to emit cells/mycell/cell_gen.go (the file holds the metadata.CellMeta{} literal plus a generated Init that drains markers).
Hand-write only cells/mycell/cell.go:
package mycell
import (
"context"
"net/http"
"github.com/ghbvf/gocell/cells/mycell/slices/myhello"
"github.com/ghbvf/gocell/framework/kernel/cell"
"github.com/ghbvf/gocell/framework/runtime/auth"
)
// +cell:listener:ref=cell.PrimaryListener,prefix=/api/v1
type MyCell struct {
*cell.BaseCell
// +slice:route:slice=myhello,subPath=
helloH *myhello.Handler
}
func New() *MyCell {
return &MyCell{
BaseCell: cell.MustNewBaseCell(loadCellMetadata()),
helloH: myhello.NewHandler(),
}
}
// initInternal is the hand-written init hook called from cell_gen.go after
// BaseCell.Init runs. Wire dependencies (DB, clients, workers) here.
func (c *MyCell) initInternal(ctx context.Context, reg cell.Registrar) error {
return nil
}
// Compile-time assertion: MyCell satisfies all four ISP sub-interfaces.
// Forgetting a method pinpoints the missing sub-interface (e.g.,
// "missing method Stop" surfaces as "does not implement CellLifecycle").
// ref: docs/architecture/202605101800-adr-cell-interface-isp-split.md §D3
var (
_ cell.CellIdentity = (*MyCell)(nil)
_ cell.CellLifecycle = (*MyCell)(nil)
_ cell.CellStatus = (*MyCell)(nil)
_ cell.CellInventory = (*MyCell)(nil)
)The +cell:listener / +slice:route markers tell cellgen how to emit cell_gen.go::Init, which calls BaseCell.Init, then c.initInternal, then registers each route group + slice. Re-run gocell generate cell --all after changing markers.
package main
import (
"context"
"os/signal"
"syscall"
mycell "github.com/ghbvf/gocell/cells/mycell"
"github.com/ghbvf/gocell/framework/kernel/auth"
"github.com/ghbvf/gocell/framework/kernel/assembly"
"github.com/ghbvf/gocell/framework/kernel/cell"
"github.com/ghbvf/gocell/framework/kernel/clock"
"github.com/ghbvf/gocell/framework/kernel/outbox"
"github.com/ghbvf/gocell/framework/runtime/bootstrap"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
clk := clock.Real()
asm := assembly.New(clk, assembly.Config{ID: "myapp", DurabilityMode: outbox.DurabilityDemo})
asm.Register(mycell.New())
app := bootstrap.New(
clk,
bootstrap.WithAssembly(asm),
// QUICKSTART ONLY — auth.AuthNone disables JWT entirely on the public
// listener. Production wires `auth.NewAuthJWTFromAssembly(asm)` here
// (PrimaryListener) and `auth.NewAuthServiceToken(store, ring)` on
// InternalListener; see docs/guides/cell-development-guide.md.
bootstrap.WithListener(cell.PrimaryListener, ":8080",
[]auth.ListenerAuth{auth.AuthNone{}}),
bootstrap.WithListener(cell.HealthListener, "127.0.0.1:9091",
[]auth.ListenerAuth{auth.AuthNone{}}), // loopback-isolated
)
app.Run(ctx)
}go run ./cmd/gocell validate # verify contracts are well-formed
go build ./cmd/myapp && ./myapp
# In another terminal:
curl http://localhost:8080/api/v1/hello
# {"message":"hello from mycell"}-
docs/guides/cell-interface-isp.md— why the Cell interface is split into four sub-interfaces (CellIdentity / CellLifecycle / CellStatus / CellInventory) -
docs/guides/why-sealed-marker.md— outbox / persistence injection: the sealed marker pattern -
docs/architecture/202605101800-adr-cell-interface-isp-split.md— ADR: Cell interface ISP split -
docs/architecture/202605101900-adr-cell-raw-infra-sealed-marker.md— ADR: Cell raw infra sealed marker
Create a complete cell bundle in one command:
gocell scaffold cell --id=foo --team=platform --role=cell-ownerThis generates cells/foo/ with cell.go + cell.yaml + slice + contract + JSON schemas, and runs codegen automatically so that go test ./cells/foo/... passes immediately.
Create an assembly in one command:
gocell scaffold assembly --id=bar --cells=foo --team=platform --role=admin --deploy=k8sThis generates assemblies/bar/assembly.yaml + cmd/bar/{run,app,modules_gen,main}.go + boundary.yaml.
See the ADR docs/architecture/202605101430-adr-scaffold-one-cmd-double-source-removal.md for details.
| Example | Complexity | What it demonstrates |
|---|---|---|
| todoorder | Medium | Custom Cell, CRUD, outbox event publish, RabbitMQ consume |
| ssobff | Medium-High | 3 built-in Cells composition (access + audit + config) |
| iotdevice | High | L4 DeviceLatent: command queue, ack, high-latency loop |
The ssobff example demonstrates the initial admin bootstrap path: an operator
hits POST /api/v1/access/setup/admin on the primary listener, protected by
HTTP Basic Auth using GOCELL_BOOTSTRAP_ADMIN_USERNAME /
GOCELL_BOOTSTRAP_ADMIN_PASSWORD (persistent operator credentials, required at
startup). The endpoint body carries the actual admin identity (username /
email / password, 8-72 printable ASCII). See docs/ops/first-run-setup.md
for the env contract and password-reset flow, and
docs/architecture/202605061600-adr-bootstrap-admin-boundary.md for the
security boundary ADR.
GoCell assemblies must declare a DurabilityMode explicitly (zero value is rejected):
| Mode | Value | Noop Allowed | Use Case |
|---|---|---|---|
DurabilityDemo |
1 | Yes — NoopWriter, outbox.DemoTxRunner, DiscardPublisher accepted; missing Tx/outbox dependencies are completed with explicit no-op defaults |
Development, unit tests, examples |
DurabilityDurable |
2 | No — CheckNotNoop rejects at Init() and L2 Cells require a real outbox writer + Tx runner |
Production storage topologies |
// Production
asm := assembly.New(clock.Real(), assembly.Config{ID: "prod", DurabilityMode: outbox.DurabilityDurable})
// Development / tests
asm := assembly.New(clock.Real(), assembly.Config{ID: "dev", DurabilityMode: outbox.DurabilityDemo})cmd/corebundle maps PostgreSQL storage topology to DurabilityDurable;
development and memory storage topologies use DurabilityDemo so examples can
run without a database or broker. Demo mode is explicit: Cells inject
outbox.DemoTxRunner / NoopEmitter when dependencies are absent, or a direct
outbox.Emitter when a publisher is supplied without a durable writer. Durable
mode never silently falls back to those no-op dependencies.
kernel/ — Cell/Slice runtime + governance tools (framework core)
corecells/ — Platform Cell implementations (accesscore / auditcore / configcore); dedicated go.work module github.com/ghbvf/gocell/corecells (#1560)
contracts/ — Platform cross-Cell boundary contracts ({kind}/{domain}/{version}/)
journeys/ — Platform Journey acceptance specs + status-board.yaml
runtime/ — HTTP middleware, auth, worker, observability, bootstrap
adapters/ — External system adapters (postgres / redis / rabbitmq / websocket / s3 / oidc)
pkg/ — Shared utilities (errcode / ctxkeys / httputil / query)
cmd/ — CLI (gocell validate [--strict] / scaffold / generate / check / verify)
examples/ — Example projects; may include example-local cells/contracts/journeys
docs/templates/ — Project templates (ADR / cell-design / contract-review / runbook / postmortem / grafana)
generated/ — Tool-generated artifacts (indexes, derived views)
kernel/ ← stdlib + pkg/ + gopkg.in/yaml.v3 (no runtime, adapters, corecells)
runtime/ ← kernel/ + pkg/ (no corecells, adapters)
corecells/ ← kernel/ + runtime/ (no adapters — interface decoupling)
adapters/ ← kernel/ + runtime/ + pkg/ + external libs (no corecells)
examples/ ← all layers
Architectural and security invariants are enforced by static gates that run in
CI (make verify) and can be reproduced locally:
| Gate | Script / Test | Enforces |
|---|---|---|
PROD-CLOCK-INJECTION-01 |
tools/archtest TestProdClockInjection |
Production code must inject kernel/clock.Clock; stdlib time.Now / Since / Until / NewTimer / NewTicker / After / AfterFunc / Tick / Sleep are forbidden outside leaf adapters |
KERNEL-CLOCK-LEAF-FALLBACK-01 |
tools/archtest TestKernelClockLeafFallback |
Leaf code must not silently default to clock.Real() — composition root must inject explicitly |
KERNEL-CLOCK-RESET-RELATIVE-PROD-01 |
tools/archtest TestKernelClockResetRelativeProd |
Production code must use Timer.ResetAt(deadline) rather than Timer.Reset(d duration) to eliminate read-then-act race |
CLOCK-POSITIONAL-INJECTION-01 |
tools/archtest TestClockPositionalInjection |
Downstream Hard: bans MustHaveClock selector args and clock option-injectors (any exported With*Clock function). Clock is a mandatory first positional parameter; the compiler enforces its presence. |
PROD-CLOCKMOCK-IMPORT-01 |
.golangci.yml depguard rule clockmock-test-only |
Production code must not import kernel/clock/clockmock (test-helper packages under **/testutil/ and **/storetest/ are exempt) |
LAYER-01..04 |
.golangci.yml depguard rules kernel/pkg/runtime/adapters-isolation |
Layered import boundaries (kernel ⇏ runtime/adapters/cells, etc.) |
SUPPLY-CHAIN-VULN |
hack/verify-supply-chain-clean.sh, govulncheck, gosec, Semgrep, CodeQL |
Vulnerable dependencies + insecure code patterns |
SHELL-SAFETY-01 |
hack/verify-shell-safety.sh |
All hack/*.sh scripts use set -euo pipefail
|
Convenience aggregator: bash hack/verify-archtest-invariants.sh runs the
clock-injection, duration-const, test-time-literal, and panic-registered
gates in one shot (~33s, shared-resolver).
- accesscore — Identity management, JWT session lifecycle (RS256), RBAC authorization (9 Slices)
- auditcore — Tamper-proof audit trail with HMAC-SHA256 hash chain (4 Slices)
- configcore — Configuration management with versioning, publishing, and feature flags (6 Slices)
| Adapter | Capabilities | Kernel Interface |
|---|---|---|
adapters/postgres |
Pool, TxManager, Migrator (goose v3), OutboxWriter, PGOutboxStore |
outbox.Writer, outbox.BatchWriter, runtime/outbox.Store
|
adapters/redis |
Client, DistLock, IdempotencyClaimer, Cache | idempotency.Claimer |
adapters/oidc |
Thin go-oidc v3 wrapper (Config, Provider, Refresh, Verifier, OAuth2Config) | — |
adapters/s3 |
Thin aws-sdk-go-v2 wrapper (Config, Upload, Health, SDK escape hatch) | — |
adapters/rabbitmq |
Publisher, Subscriber, ConsumerBase (DLQ + retry) |
outbox.Publisher, outbox.Subscriber
|
adapters/websocket |
WebSocket Hub, signal-first push | — |
adapters/otel |
OTel SDK tracer + MetricProvider + pool collector (OTLP gRPC exporter, semconv db.client.connection.*) |
kernel/wrapper.Tracer, kernel/observability/metrics.Provider
|
adapters/prometheus |
MetricProvider (backs runtime/outbox collectors) + LifecycleHookObserver |
kernel/observability/metrics.Provider, cell.LifecycleHookObserver
|
The transactional outbox is split across three layers — Cell services depend on
persistence.TxRunner + outbox.Emitter, store + relay loop lives in
runtime/outbox, and persistence lives in adapters/postgres:
clk := clock.Real()
// 1. Adapt the durable writer at the Cell boundary.
emitter, err := outbox.NewWriterEmitter(postgres.NewOutboxWriter(clk))
if err != nil {
return err
}
// 2. Service code writes business state + emits inside the same transaction.
err = txRunner.RunInTx(ctx, func(txCtx context.Context) error {
// ... write business state ...
return emitter.Emit(txCtx, entry)
})
// 3. Compose the relay at bootstrap (cmd/corebundle, examples, etc.)
store := postgres.NewOutboxStore(pool.DB(), clk)
relay := outbox.NewRelay(clk, store, publisher, outbox.DefaultRelayConfig())
// relay implements worker.Worker — register with bootstrap to manage lifecycle.Direct-publish demo paths use outbox.NewDirectEmitter; durable writer and
direct publisher paths both marshal the same kernel/outbox v1 wire envelope.
runtime/outbox owns relay/store runtime state only.
runtime/outbox defines the SQL-dialect-neutral Store interface (ClaimPending / MarkPublished / MarkRetry / MarkDead / ReclaimStale / CleanupPublished / CleanupDead / OldestEligibleAt) and the Relay worker that owns the poll / reclaim / cleanup goroutines. Cleanup is data-driven: it sleeps until the next published / dead row crosses its retention window, so an idle table costs zero DB cycles.
For HTTP flows that publish through the transactional outbox, GoCell now bridges
requestId, correlationId, and optional traceId from handler context
into outbox.Entry.Observability on the write path. When the event is
consumed, SubscriberWithMiddleware.SubscribeEntry restores those keys into the
consumer handler context before business code runs.
Consumer setup now has two composition contracts. Subscription-bearing
bootstrap applications must configure WithConsumerBase; phase6 fails fast
without it so idempotency and broker settlement are explicit:
clk := clock.Real()
cb, err := outbox.NewConsumerBase(
idempotency.NewInMemClaimer(clk),
outbox.ConsumerBaseConfig{},
clk,
)
if err != nil {
panic(err)
}
app := bootstrap.New(
clk,
bootstrap.WithSubscriber(rawSub),
bootstrap.WithConsumerBase(cb),
bootstrap.WithTracer(tracer),
)Bootstrap automatically decorates the subscriber with
eventrouter.NewContractTracingSubscriber(rawSub, tracer), so consumer spans end
after final broker settlement (ack, requeue, commit_failed,
retry_exhausted). For non-bootstrap usage, call
SubscriberWithMiddleware.SubscribeEntry rather than the raw subscriber when
consuming business handlers, and include the same subscriber decorator when
final-settlement tracing is required:
tracedSub := eventrouter.NewContractTracingSubscriber(rawSub, tracer)
wrappedSub := &outbox.SubscriberWithMiddleware{
Inner: tracedSub,
Middleware: businessMiddleware,
ConsumerBase: cb,
}
err := wrappedSub.SubscribeEntry(ctx, sub, handler)Raw Subscriber.Subscribe is reserved for adapter/test delivery paths; it
bypasses business middleware, ConsumerBase, observability restoration, and
final-settlement tracing. When HTTP tracing is enabled,
GoCell now extracts inbound traceparent and b3 headers before starting the
server span so synchronous service hops preserve the same trace_id. Note:
span_id is intentionally excluded across async boundaries — spans do not
cross the outbox hop.
When HTTP tracing is enabled via WithTracer, GoCell automatically extracts
inbound W3C traceparent and B3 headers before starting the server span.
W3C takes precedence; B3 is used only as a fallback. Invalid or missing headers
safely degrade to a new root trace.
Enablement — tracing is opt-in via bootstrap.WithTracer or
router.WithTracer:
// bootstrap (recommended) — production wires the OTel adapter.
// otel.NewTracer returns (*otel.Tracer, shutdown func(context.Context) error, error).
tracer, shutdown, err := otel.NewTracer(ctx, otel.TracerConfig{ServiceName: "my-service"})
if err != nil { /* handle */ }
defer shutdown(context.Background())
clk := clock.Real()
jwtAuth, err := auth.NewAuthJWTFromAssembly(asm)
if err != nil { /* handle */ }
app := bootstrap.New(
clk,
bootstrap.WithAssembly(asm),
bootstrap.WithListener(cell.PrimaryListener, ":8080",
[]auth.ListenerAuth{jwtAuth}),
bootstrap.WithTracer(tracer), // tracer is a kernel/wrapper.Tracer
)
// router (standalone)
r, err := router.New(clk, router.WithTracer(tracer))
if err != nil { /* handle */ }Without
WithTracer, span creation falls back towrapper.NoopTracer{}. Tests that need inspectable trace/span IDs use the in-process fixtureruntime/observability/tracingtest.NewSimpleTracer("svc")(test-only — archtestTRACING-SIMPLETRACER-TEST-ONLY-01bans it from production).
Trust assumption: trace header propagation assumes a trusted-upstream deployment (service-to-service behind a gateway or mesh). Public-facing edges should sanitize or ignore inbound trace headers at the gateway layer.
Framework-emitted consumer logs pick up these fields when the process uses
GoCell's context-aware slog handler. This branch does not make plain slog JSON
handlers automatically extract request_id, correlation_id, or trace_id
(slog log-key naming, intentionally snake_case per observability.md; the
corresponding wire JSON envelope fields are camelCase: requestId,
correlationId, traceId).
Values restored from broker metadata are validated for safe characters and
length before injection into context.
GoCell is public — no GOPRIVATE or auth setup is required. Post-#1565 the
framework core is the dedicated github.com/ghbvf/gocell/framework submodule (the
repo root holds only go.work, no module); pin every gocell satellite at the same
synchronized vX.Y.Z.
# Add the framework to your project (or pin a stable tag, e.g. @v0.1.0)
go get github.com/ghbvf/gocell/framework@latestThe gocell governance/codegen CLI installs via go install github.com/ghbvf/gocell/cmd/gocell@vX.Y.Z from any stable tag that ships the #2045
release pipeline; for earlier tags or unreleased code, install from source (git clone … && go install ./cmd/gocell). See
docs/guides/cell-external-repo-quickstart.md.
GoCell includes templates for common engineering documents:
-
docs/templates/adr.md— Architecture Decision Record -
docs/templates/cell-design.md— Cell design document -
docs/templates/contract-review.md— Contract review checklist -
docs/templates/runbook.md— Operations runbook -
docs/templates/postmortem.md— Incident postmortem -
docs/templates/grafana-dashboard.json— Grafana monitoring dashboard