Skip to content

Releases: infobloxopen/devedge-sdk

v0.58.0 — verify-only credential field mode (WS-033)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 04 Jul 21:25

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 only public_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: true codegen. The storage generators emit <field>_public_id / _salt / _hash / _hashspec (no plaintext, no cipher), mint on Create and return the token once, generate Verify<Field> for constant-time verification, and omit the field from every read response. Fails loud on credential+secret collision or a non-string credential field.
  • Consumes the canonical github.com/infobloxopen/apis/proto/infoblox/field v1.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)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 04 Jul 19:19

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

Choose a tag to compare

@daniel-garcia daniel-garcia released this 03 Jul 23:28

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). Generated Create previously stamped account_id from the caller's tenant context only when the client left it empty, so a client-supplied account_id won — letting any authorized caller plant a resource under an arbitrary tenant's account_id. Now stamped from TenantIDFromContext unconditionally, ignoring client input, on both GORM and ent backends (mirrors the existing Update guard). Covered by a new seccheck.AssertNoCrossTenantCreate regression. (PR #176)

Reliability / SLO (WS-025)

  • de slo generate now 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). Corrected operate/slo.md's promise.
  • New reference/slo-kpis.md section documenting the devedge/otel-rpc SLI source fields (#172).

Codegen

  • A plain OUTPUT_ONLY scalar outside the framework vocabulary (etag/delete_time/expire_time, derived name) now fails generation with an actionable message instead of silently getting no column and losing every write (#170). model-a-resource.md gains 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 stale v0.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)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 28 Jun 02:29

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 require its module at the matching version (the scaffold-generated go.mod does 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

Choose a tag to compare

@daniel-garcia daniel-garcia released this 28 Jun 00:19
6497206

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.md moved 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.Config reference — added Logger, ReadinessChecks, and the ResilienceConfig sub-table (RequestTimeout / PerMethodTimeout / RateLimiter / CircuitBreaker) (finding 055).
  • docs(deploy): honest chart-publication framing — ghcr.io/infobloxopen/charts/devedge-service is presented as the planned publish target (not yet live), with a "publish the chart yourself" helm push workflow 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)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 27 Jun 23:37
b47e32b

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 Target interface + registry; --deploy k8s,compose renders 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 Flux HelmRelease + an OCIRepository source + a thin values.yaml overlay. The chart wires the whole foundation: liveness /healthz + readiness /readyz (#91), the config.ServerOptions env (#93), OTEL_* (#90), the DSN as a Secret, ingress, resource limits, and terminationGracePeriodSeconds.
  • Docker Compose: docker-compose.yml (service + declared deps, e.g. Postgres) wired to the same config/health/observability surface, with stop_grace_period.
  • Graceful shutdown: scaffolded main now uses signal.NotifyContext (SIGTERM/SIGINT) so Serve returns → 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 what helm lint/helm template validate.

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)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 27 Jun 23:02
229584f

Second slice of the WS-007 operational foundation — both behind neutral, dependency-light seams.

Unified configuration (#93)

  • A stdlib-only config seam: Source + Load binding config:"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/koanf isolates YAML/TOML (+ koanf) — core stays stdlib-only.

Resilience (#94)

  • resilience policy interceptors with the algorithm behind an interface: TimeoutUnary (per-RPC deadline → DeadlineExceeded; 30s default, NoTimeout to disable, per-method override), RateLimitUnary (RateLimiter interface + stdlib token bucket → ResourceExhausted; off by default), and a CircuitBreaker seam + 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 DotEnv silent 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)

Choose a tag to compare

@daniel-garcia daniel-garcia released this 27 Jun 22:17
51c03f6

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/otel adapter. Install it (honors standard OTEL_* 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 (Serve now owns the loop lifecycle on every return path) and a data race on listener accessors; added regression tests; go test -race clean.

Also

  • fix(protoc-gen-ent): correct LoadAggregate edge accessor casing for multi-word edges (#88/#89).

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

Choose a tag to compare

@daniel-garcia daniel-garcia released this 27 Jun 19:04
8012534

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 PARTITION retention; 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 (SingleProcessLeader dev / gormtx.PGAdvisoryLeader prod — 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).
  • OutboxCDCConsumer seam 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

Choose a tag to compare

@daniel-garcia daniel-garcia released this 27 Jun 09:16
a283d9d

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_time mark 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_DAYS RANGE (MySQL); retention = DROP PARTITION (O(1) DDL, proven load-bearing via partition-count assertions, not row-delete). RunRetention helper; the service owns the scheduler.
  • OutboxCDCConsumer seam (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).