Releases: infobloxopen/devedge-sdk
Release list
v0.58.0 — verify-only credential field mode (WS-033)
Verify-only credential field mode (WS-033)
A new field mode for API keys and tokens: credential — "hash, never encrypt". Unlike secret (which keeps a reversible cipher so the value can be read back), a credential is SDK-minted, returned to the client once, and only ever verified — there is no reversible copy at rest.
secret.CredentialMinter. Mints a prefixed split token<prefix>_<public_id>_<secret>and returns it to the caller exactly once; persists onlypublic_id(UNIQUE lookup key) + salt + a salted one-way hash + a self-describing hash spec. Default SHA-512/256 over a 256-bit secret; SHA-384 and PBKDF2-SHA256 selectable for verify-time agility. Stdlib crypto only — FIPS-clean, no HMAC, no new dependencies.credential: truecodegen. The storage generators emit<field>_public_id/_salt/_hash/_hashspec(no plaintext, no cipher), mint onCreateand return the token once, generateVerify<Field>for constant-time verification, and omit the field from every read response. Fails loud oncredential+secretcollision or a non-string credential field.- Consumes the canonical
github.com/infobloxopen/apis/proto/infoblox/fieldv1.0.0-alpha.4 annotation.
Compatibility: additive (a new annotation). Switching a field from secret to credential is a schema change and old values cannot be migrated (the framework never held a safe copy) — re-issue those credentials.
Synchronized 10-module release (root + 9 adapters), all tagged at v0.58.0.
v0.57.0 — security: secrets-handling hardening (SEC-005..008)
Secrets-handling hardening from the WS-030 dedicated secrets sweep.
- SEC-005: redact the DSN in migrate errors so credentials no longer leak into error text or logs.
- SEC-006: both persistence generators (ent + storage) now return the persistence.ErrNoEncryptor sentinel and fail loudly when a secret field is set but no encryptor is configured, instead of silently dropping the field.
- SEC-007: generators omit INPUT_ONLY fields from response projections, and the scaffold documents the guarantee.
- SEC-008: secret.NewDev now requires an exact 32-byte key, and the vault path surfaces a status-only error.
Compatibility:
- SEC-006 and SEC-007 are behavior-change-on-regen — the difference lands when a consumer re-runs code generation.
- SEC-008 is a breaking change to the dev encryptor (secret.NewDev rejects non-32-byte keys).
v0.52.0 — Create tenant-isolation fix + SLO/codegen/compose hardening
Synchronized multi-module release (root + 8 nested modules all at v0.52.0). Ships the fixes from the DX hardening cadence Runs 16–18 (2026-07-03). Contains a security fix — upgrade recommended.
🔒 Security (upgrade recommended)
- Tenant-isolation bypass on
Create(#175). GeneratedCreatepreviously stampedaccount_idfrom the caller's tenant context only when the client left it empty, so a client-suppliedaccount_idwon — letting any authorized caller plant a resource under an arbitrary tenant'saccount_id. Now stamped fromTenantIDFromContextunconditionally, ignoring client input, on both GORM and ent backends (mirrors the existingUpdateguard). Covered by a newseccheck.AssertNoCrossTenantCreateregression. (PR #176)
Reliability / SLO (WS-025)
de slo generatenow prints a visible notice for custom (AIP-136) methods it does not group into the default SLOs, instead of silently omitting a service's hot path (#171). Correctedoperate/slo.md's promise.- New
reference/slo-kpis.mdsection documenting thedevedge/otel-rpcSLI source fields (#172).
Codegen
- A plain
OUTPUT_ONLYscalar outside the framework vocabulary (etag/delete_time/expire_time, derivedname) now fails generation with an actionable message instead of silently getting no column and losing every write (#170).model-a-resource.mdgains a warning callout.
Composable services (WS-012)
- The gorm scaffold emits a composition seam —
NewModule(db *gorm.DB) servicekit.Module+Models() []any— so a composed host (de compose build) can build a module over one shared DB without naming its repository/model (part of the compose "won't compile" fix). Additive; standalone services are unchanged. (PR #178)
Scaffold
- Generated CI now installs
de@v0.12.0(was a stalev0.7.0) (#179).
See CHANGELOG.md and the run report (devedge-assessment-2026-07-03-dx-loops-16-19.md) for the full context. Open follow-ups: #173 (unique-lookup helper), #177 (ent composition seam).
v0.27.0 — multi-module isolation: backends are opt-in by module (WS-011)
devedge-sdk is now a multi-module repo. The root library module carries NONE of the heavy backend dependencies — each lives in its own nested module a consumer pulls in only by importing it. A service that imports only the core (server, persistence interfaces, middleware, config, resilience, health, events) has a dependency graph provably free of the OTel SDK + exporters, koanf, franz-go, gorm, and ent.
Modules (all released synchronized at v0.27.0)
github.com/infobloxopen/devedge-sdk— the dependency-light library…/observability/otel— OTel SDK + exporters…/config/koanf— koanf (YAML/TOML config)…/events/kafkabus— franz-go (Kafka bus)…/persistence/gormtx— gorm…/persistence/entrepo— ent…/cmd— the CLI +protoc-gen-*plugins (build-time tooling)
For consumers
- Import only what you use; your graph/go.sum stays minimal. gorm/ent/koanf/franz-go are fully absent from a core-only consumer's graph; the OTel API/contrib stays in core for zero-config instrumentation (the OTel SDK is opt-in via
observability/otel). - No import-path changes — module path == import path, so existing imports are unchanged. A service using an adapter must
requireits module at the matching version (the scaffold-generatedgo.moddoes this automatically). - CLI:
go install github.com/infobloxopen/devedge-sdk/cmd/devedge-sdk@v0.27.0.
Establishes the pattern for future heavy components (e.g. a k8s controller scaffolding) to isolate the same way — see docs Adding an isolated module.
v0.26.1 — DX Run 12 cleanup: ECS-stub path fix + complete Config reference + honest chart-publication docs
Closeout fixes from the WS-007 acceptance dogfood (DX Run 12) — the change that takes the assessment to zero behavioral findings.
- fix(deploy): the ECS deploy-stub error no longer references a stale on-disk doc path (
guides/deploy.mdmoved under the Diátaxis IA); now points to the deploy how-to guide in stable prose, with a regression assertion (BF-001). - docs(reference): completed the
server.Configreference — addedLogger,ReadinessChecks, and theResilienceConfigsub-table (RequestTimeout / PerMethodTimeout / RateLimiter / CircuitBreaker) (finding 055). - docs(deploy): honest chart-publication framing —
ghcr.io/infobloxopen/charts/devedge-serviceis presented as the planned publish target (not yet live), with a "publish the chart yourself"helm pushworkflow for today; Docker Compose remains the end-to-end quickstart (finding 058).
Deferred follow-ups tracked: #108 (--version), #109 (install pinning docs), #110 (runtime OTel exporter switch), #111 (publish the framework chart to the OCI registry).
v0.26.0 — operational foundation pt.3: multi-runtime deploy (k8s/Helm/Flux + Docker Compose)
Third slice of the WS-007 operational foundation — a scaffolded service now has a deploy path for ≥2 runtime targets behind a deployment-target seam.
Multi-runtime deploy (#95)
- Target seam: a
Targetinterface + registry;--deploy k8s,composerenders artifacts into the service repo. Adding a target = a new adapter (no core change). An ECS stub proves the seam is open. - k8s/k3s (first-class): a framework-owned Helm chart is
go:embed-ed in the tooling — devs never author or see it. The k8s target emits only a FluxHelmRelease+ anOCIRepositorysource + a thinvalues.yamloverlay. The chart wires the whole foundation: liveness/healthz+ readiness/readyz(#91), theconfig.ServerOptionsenv (#93),OTEL_*(#90), the DSN as a Secret, ingress, resource limits, andterminationGracePeriodSeconds. - Docker Compose:
docker-compose.yml(service + declared deps, e.g. Postgres) wired to the same config/health/observability surface, withstop_grace_period. - Graceful shutdown: scaffolded
mainnow usessignal.NotifyContext(SIGTERM/SIGINT) soServereturns → graceful gRPC/HTTP shutdown + OTel flush + readiness-loop stop.
Hardening (#106)
- Coherence cleanup (single source for chart name+version) + a chart-ref drift guard test (emitted HelmRelease/OCIRepository must match the embedded
Chart.yaml). helm/docker tests skip-when-absent; the embedded chart bytes are exactly whathelm lint/helm templatevalidate.
Dependency-light gate: the deploy feature adds no runtime dependency — production rendering uses text/template (no YAML lib); the deploy code is tooling-only. Artifacts are templates, not deps.
v0.25.0 — operational foundation pt.2: unified config + resilience (dependency-light)
Second slice of the WS-007 operational foundation — both behind neutral, dependency-light seams.
Unified configuration (#93)
- A stdlib-only
configseam:Source+Loadbindingconfig:"KEY"struct tags from flags > env > file > default, with typed parsing (string/int/bool/float/duration). Built-in sources: env, flags, dotenv, JSON, map. - Canonical
ServerOptions(GRPC/HTTP addr, log level, OTLP endpoint, DSN); the scaffold loads these instead of hardcoding. - Adapter
config/koanfisolates YAML/TOML (+ koanf) — core stays stdlib-only.
Resilience (#94)
resiliencepolicy interceptors with the algorithm behind an interface:TimeoutUnary(per-RPC deadline →DeadlineExceeded; 30s default,NoTimeoutto disable, per-method override),RateLimitUnary(RateLimiterinterface + stdlib token bucket →ResourceExhausted; off by default), and aCircuitBreakerseam + interceptor documented for plugging a lib — no breaker library baked into core.- Wired into the default chain: rate-limit early (load-shed), breaker outside the handler, timeout innermost.
Hardening (#104)
- Fixed
DotEnvsilent truncation on scanner error; fixed a dead scaffold flag layer (generated-GRPC_ADDR/etc. overrides now actually work — matches the README); corrected precedence docs.-race+ cleancore guard green.
Dependency-light gate: core gains no heavy dep — koanf confined to config/koanf, token bucket is stdlib, no circuit-breaker lib in core (enforced by cleancore_test.go).
v0.24.0 — operational foundation pt.1: built-in observability + health/readiness (foundation-first docs)
First slice of the WS-007 operational foundation: a scaffolded service now comes up observable and with health/readiness probes out of the box — each behind a neutral, dependency-light seam (the OTel SDK/exporters live only in the observability/otel adapter, exactly like events/kafkabus).
Observability (#90)
- Built-in OpenTelemetry tracing + RED metrics + structured slog request logging in the default server chain.
- Dependency-light seam: core uses the OTel API only (no-op until an SDK is installed); the SDK + OTLP/stdout exporters are isolated in the new
observability/oteladapter. Install it (honors standardOTEL_*env) to activate — zero overhead and no behavioral change without it. - One trace across the hop: W3C context propagates HTTP gateway → gRPC → handler.
- Redaction on by default: secret-annotated fields never appear in cleartext logs; logs carry
trace_id/span_id.
Health & readiness (#91)
- gRPC health service + HTTP
/healthz(liveness) and/readyz(readiness). - Readiness-check seam (
health.Check, per-check 2s timeout) + a stdlib DB-ping check; readiness drives the gRPC health status; probes bypass authz. Scaffold wires a DB readiness check.
Docs reposition (#98)
- README + docs home lead with the secure-by-default foundation; secret-fields demoted to one Security instance.
Hardening (#101)
- Fixed a readiness-loop goroutine leak (
Servenow owns the loop lifecycle on every return path) and a data race on listener accessors; added regression tests;go test -raceclean.
Also
Dependency-light gate: core packages do not import the OTel SDK/exporters — only observability/otel does (enforced by cleancore_test.go).
v0.23.0 — write-only outbox + event bus (in-memory + Kafka), relay & consumer
Reworks the outbox/events subsystem to a write-only outbox + queue-based event bus (supersedes v0.22.0's poll+lease+mark dispatcher).
Pipeline: aggregate tx → write-only outbox (Append) → RELAY (ReadAfter+cursor → bus, leader-elected) → BUS (in-memory dev | Kafka prod) → CONSUMER (group → handlers, Atomically + idempotency).
Highlights
- Write-only outbox — only the transactional
INSERT+DROP PARTITIONretention; the dispatcher never mutates rows (no lease/delivered/attempts). A clean CDC source, no write+delete churn. - Queue-based consumption — multi-replica coherence/throughput via Kafka consumer groups (competing consumers, partition ordering, coherent offsets). In-memory bus for dev/in-process; Kafka (franz-go) for prod, Redpanda for integration tests.
- Relay (outbox→bus) is leader-elected (
SingleProcessLeaderdev /gormtx.PGAdvisoryLeaderprod — crash-safe via the advisory lock as lease) so exactly one replica pumps. Consumer commits the Kafka offset only after the handler+idempotency commit → no loss + exactly-once effect; NACK rewinds in-session (at-least-once). OutboxCDCConsumerseam stays documented for an external WAL→queue project; the SDK ships no WAL engine.
Proven
Real Postgres, MySQL 8, and Redpanda (testcontainers, -race): write-only end-to-end, two-consumers-split-partitions, per-key ordering, exactly-once under redelivery, single-relay under contended PG advisory leader, drop-partition retention. CI guards PG/MySQL/Kafka didn't silently skip.
Clean core
No broker/Kafka/ORM dep in persistence/authz/grpcauthz/events; franz-go only in events/kafkabus; mysql/redpanda only in testdata. Scaffold + all existing suites green.
v0.22.0 — SQL idempotency, partitioned/append-only outbox (PG+MySQL), scaffold aggregate+outbox
Three follow-on items on the aggregate/events subsystem, each implemented → hardened → shipped.
1. SQL-backed EntIdempotencyStore (#84)
Closes the documented ent exactly-once gap: the ent dispatch path used the in-memory idempotency store (marker not in the handler tx → orphan-marker window). Adds EntIdempotencyStore (the ent twin of gormtx.GormIdempotencyStore) — Record inserts the marker through the handler's *ent.Tx, unique conflict → events.ErrAlreadyApplied. Exactly-once verified on real Postgres under -race.
2. Append-only partitioned outbox + drop-partition retention, Postgres AND MySQL (#85)
Reworks the outbox per directive — no read+delete churn:
- Append-only: a delivered event costs one claim-lease + one idempotent
delivered_timemark over its whole life, then is never re-claimed or DELETEd (hardening caught + fixed a make-or-break bug where delivered events were re-claimed every poll, moving churn from DELETE to repeated UPDATEs). - Drop-partition retention: declarative RANGE partitioning on
created_time(PG) /TO_DAYSRANGE (MySQL); retention =DROP PARTITION(O(1) DDL, proven load-bearing via partition-count assertions, not row-delete).RunRetentionhelper; the service owns the scheduler. OutboxCDCConsumerseam (interface + docs only) so an integrator can plug logical-replication/Debezium (WAL) without the SDK shipping a heavy CDC engine — "WAL if possible, else partitions," with partitioning built into the pattern.- Validated on real Postgres and MySQL 8 (testcontainers,
-race); CI guards both didn't silently skip.
3. Scaffold aggregate + outbox out of the box (#86)
devedge-sdk new service --aggregate generates a service that wires TxRunner + AggregateRepository + outbox (both ent and gorm) with the member resource boundary-gated (no direct member writes). Non-aggregate scaffold output is byte-identical to before. Proven by integration tests that drive real make generate/build/test.
Throughout
Clean core preserved (no ORM/driver/CDC/broker dep in persistence/authz/grpcauthz/events; engine deps confined to the gormtx/entrepo adapters and testdata go.mods). CI green on main. Known follow-up: a latent protoc-gen-ent multi-word has_many edge-accessor bug (sidestepped, not yet fixed).