Skip to main content
πŸ—“οΈ Schedule discussionOpens Google Calendar β€” add attendees there.

Versioning & release-notes standard

OpenAuthor: @relbnsOwner: openEffort: M

Problem​

Today a deployed service often has no version set at all, there are no consistent release notes, and there is no reliable way to answer "what version is running where":

  • On EKS you can read the running image tag out of Kubernetes-deployments values-*.yaml.
  • On ECS / IL the running version lives only in the task definition inside the AWS console β€” not readable without console access.
  • During a release window we cannot tell which client build is talking to which backend, so coordinating a backend deploy that depends on a specific app build is guesswork.

A stop-gap is already in flight (support-tool now prints its commit SHA in the browser console), and leaders-app 1.8.5 already sends X-App-Version / X-App-Platform headers β€” but these are point fixes, not a standard. We want a clear, automated, repeatable process: a SemVer bump + release notes on every prod deploy, with the version visible and queryable everywhere.

Proposal​

Define an org-wide versioning & release standard, layered on top of the existing tag-driven deploy β€” the deploy mechanism (the <region>-<stage>-<feature> tags) does not change. The version is a metadata layer that rides alongside the deploy, so we get SemVer + release notes without touching the working pattern the team relies on.

Four layers:

  1. Version source β€” Conventional Commits drive an automated release tool that bumps SemVer and generates CHANGELOG.md + a GitHub Release on merge to the prod-bound branch.
  2. Build-time stamping β€” every build injects version, commit, builtAt, env into the artifact.
  3. Standard exposure contracts β€” the version is readable at runtime everywhere (endpoint, startup log, structured log field, console banner, client header).
  4. Persisted deployment record β€” an append-only, queryable log of every deploy that becomes the single source of truth for "what's running where" and also closes the ECS/IL visibility gap.

Why now / Why us​

  • The need is concrete and recurring (release-window coordination, support triage, the IL "what's running" gap), and pieces are already being built ad-hoc β€” better to set the standard before more point fixes diverge.
  • It is the foundation for two larger initiatives already on the roadmap: the person-independent deploy/version platform and the Sentinel AI on-call agent. Both need "what version is where" as queryable data; this proposal produces exactly that substrate.
  • It is cheap to start: pilot on two repos that already have momentum (support-tool, backend-services), then sweep the rest in the uniform-CI style.

Sketch of approach​

Layer 1 β€” version source (the one real decision; see Open questions):

  • (A) release-please β€” recommended default. GitHub-native; accumulates Conventional Commits into a "release PR" that a maintainer merges β€” which fits the existing human-approval gate (only Aviad / Baruch / Shimon approve), generates CHANGELOG.md + a GitHub Release, AI-friendly.
  • (B) semantic-release β€” push-based, fully automatic on merge, no release PR. Powerful, but bypasses the human gate.
  • (C) changesets β€” explicit intent files per change; best for the backend-services monorepo / multi-package, heavier for single-service repos.
  • Recommendation: release-please as the org default; changesets only where a monorepo genuinely needs per-package versioning.

Layer 2 β€” build-time stamping:

  • CI passes version (from layer 1), commit, builtAt, env as build args β†’ runtime config. One shared snippet, adopted per repo.

Layer 3 β€” exposure contracts (a shared mini-standard):

  • Backend: log version at startup + GET /version (or extend the health endpoint) + a structured service.version log field on every request.
  • Frontend: a version.json + a console.log banner + an About/footer line. Already shipping: support-tool now prints its commit SHA to the browser console β€” the first real instance of this layer. Generalise it (commit + SemVer + env, sourced from layer 2's build-time stamp) and adopt the same banner across the other frontends.
  • Mobile: already carries v<version>+<build> β€” just log it.
  • Cross-talk: clients send an X-Client-Version header (generalising the X-App-Version already shipped by leaders-app); the backend logs both client.version and its own service.version. This makes logs in Loki/Datadog filterable by version and answers "which app is talking to which backend" + dependency management. The existing Datadog-tagging proposal is the mobileβ†’Datadog slice of this layer; this standard subsumes and generalises it.

Layer 4 β€” persisted deployment record (the durable, reusable store):

An append-only record of every deploy event β€” the single queryable answer to "what version is running where", written by the deploy workflows. Closes the ECS/IL visibility gap (today IL's running version lives only in an ECS task definition, console-only) and gives the future deploy platform + Sentinel a structured deploy stream.

  • Record (one per deploy): {service, env, platform, version, commit, image_tag, trigger_tag, status, deployed_at, actor, run_url} β€” append-only; a rollback is a new record with status: rolled_back for the re-deployed commit.
  • Written by the pipelines: EKS via Kubernetes-deployments deploy-env.yaml (it already bumps values-*.yaml); ECS via a step in each fr-* workflow after the task-def update β€” this ECS step is what makes IL readable without the console. All fields come from the workflow context (trigger tag, commit, actor, run URL). Best-effort β€” it must never fail the deploy (the record is an observability artifact, not a gate); it is also written on failure (status: failed).
  • Storage (the shape is the contract; the store is swappable): start with in-repo JSONL in Kubernetes-deployments (deploys/<env>.jsonl) β€” free, versioned, greppable, no new infra; the ECS workflows push there too. Promote to a DynamoDB table only if write-contention or query needs grow.
  • Read: "what's running in <env>" = the latest success record per (service, env); the deploy-platform UI renders the version matrix + history; Sentinel consumes the stream (a failed record β†’ ClickUp bug + Slack on-call). Cross-check against the /version endpoint (layer 3): the record is the deploy intent, the endpoint is the live runtime β€” a mismatch flags a bad/partial deploy.

Rollout: repo-by-repo in the uniform-CI style. Pilot on support-tool + backend-services; each subsequent repo is an S-sized adoption. (Whole-org rollout is incremental, hence overall effort M for the standard + pilot rather than a single big-bang L.)

Current-state audit (2026-06-04)​

A read-only scan of five locally-available service repos confirms the problem statement with per-service evidence. No service exposes a commit hash, build time, or deploy time today β€” the field is uniformly absent. The only "version" signals are cosmetic and untrustworthy.

RepoStackHealth endpointversioncommitbuiltAt/deploy-timeInjection present
backend-servicesNestJS (msg-pattern + HTTP gateways)/api/health, /api/service_infohardcoded 0.0.1 (CI never injects)❌❌reads npm_package_version only
backend-apiNestJSGET / (status-only)❌❌❌none
sqs-consumerNestJS v9none❌❌❌none
superco-consumerYii2-PHP/api/v1/test/health-checkstatic string "Coing API version 1.0"❌❌none
consumer-php-basePHP-FPM base imagenone (FPM status/ping commented out)❌❌❌none

Also: none of the five wires a health-check path into its ECS task definition or an ALB target group; there is zero build-time metadata injection in any Dockerfile/CI. (Not audited: cognito-lambdas, recommendations β€” not checked out locally; ALB health paths possibly defined in Terraform/console outside these repos.)

Implication for the layers: Layer 4's "deploy-record vs live-runtime" cross-check has nothing live to read against until Layers 2–3 land. The smallest unblock is the build-arg injection (Layer 2: Dockerfile ARG GIT_SHA/BUILD_TIME/APP_VERSION β†’ ENV, passed from CI; add once in consumer-php-base so PHP services inherit) feeding a uniform GET /version β†’ {version, commit, builtAt} (Layer 3). Once those exist, watch-deploy asserts the live /version commit equals the deployed commit β€” a partial/bad-deploy detector it currently lacks.

Open questions​

  • Layer 1 tool β€” release-please (recommended) vs semantic-release vs changesets? Does the team's existing personal-project flow favour one?
  • Prod-bound branch β€” most repos cut prod from master, a few from develop; the release tool's trigger branch must match each repo's reality (documented in the infra doc appendix).
  • Layer 4 storage β€” in-repo manifest (simple, greppable) vs a small datastore (queryable at scale)? Start simple and evolve?
  • Relationship to the deploy tags β€” the version is independent of the <region>-<stage>-<feature> deploy tag; do we also want an annotated v<semver> tag per release for traceability, or is the GitHub Release enough?
  • SemVer policy for internal services β€” do we apply strict SemVer (breaking = major) to internal services, or a simpler date/incrementing scheme where SemVer adds little?
  • Infrastructure & Environments β€” Β§5 (version-management gap) and Β§10.C (this shape sketched). Landed via bewith-docs PR #91.
  • Tag Datadog from X-App-Version β€” the mobileβ†’Datadog slice of layer 3; this standard generalises it.
  • Uniform CI β€” the repo-by-repo adoption pattern this rollout mirrors.
  • Connects to the broader person-independent deploy platform and Sentinel AI on-call vision (infra doc Β§10).

Triage notes​

To be filled in at the next triage session.