Versioning & release-notes standard
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-deploymentsvalues-*.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:
- 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. - Build-time stamping β every build injects
version,commit,builtAt,envinto the artifact. - Standard exposure contracts β the version is readable at runtime everywhere (endpoint, startup log, structured log field, console banner, client header).
- 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-servicesmonorepo / 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,envas 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 structuredservice.versionlog field on every request. - Frontend: a
version.json+ aconsole.logbanner + an About/footer line. Already shipping:support-toolnow 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-Versionheader (generalising theX-App-Versionalready shipped by leaders-app); the backend logs bothclient.versionand its ownservice.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 withstatus: rolled_backfor the re-deployed commit. - Written by the pipelines: EKS via
Kubernetes-deploymentsdeploy-env.yaml(it already bumpsvalues-*.yaml); ECS via a step in eachfr-*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 latestsuccessrecord per(service, env); the deploy-platform UI renders the version matrix + history; Sentinel consumes the stream (afailedrecord β ClickUp bug + Slack on-call). Cross-check against the/versionendpoint (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.
| Repo | Stack | Health endpoint | version | commit | builtAt/deploy-time | Injection present |
|---|---|---|---|---|---|---|
| backend-services | NestJS (msg-pattern + HTTP gateways) | /api/health, /api/service_info | hardcoded 0.0.1 (CI never injects) | β | β | reads npm_package_version only |
| backend-api | NestJS | GET / (status-only) | β | β | β | none |
| sqs-consumer | NestJS v9 | none | β | β | β | none |
| superco-consumer | Yii2-PHP | /api/v1/test/health-check | static string "Coing API version 1.0" | β | β | none |
| consumer-php-base | PHP-FPM base image | none (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 fromdevelop; 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 annotatedv<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?
Relatedβ
- 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.