Make the app distributable to other GE Aerospace sites (one self-hosted
instance per site, ADR-004). GE values remain the shipped defaults; every
site-specific behavior is now a Setting an admin can change in the UI.
Settings-driven site config:
- Branding: site/QR/badge logos, favicon, primary color (upload endpoints
mirror the map-blueprint pattern; new Settings > Branding section).
- ServiceNow: search/incident/change URL templates ({ticket}), ticket
prefixes, enable toggle. Defaults point at the current
geaerospaceqa.service-now.com global search. Disabled = plain-text tickets.
- Employee-id regex (employeeid_pattern), printer hostname template,
QR label targets (qr_target_printer / qr_target_usb, blank = asset page,
else URL template with placeholders), usb_label_style (barcode|qr).
- West Jefferson floor-plan PNGs removed from the tree; generic placeholder
ships as the map default and sites upload their own blueprint.
Security closeout:
- dashboarddefaults writes now require admin.
- Collector: generic error messages (no str(exc) leak); API key accepted
via X-API-Key header only (BREAKING: querystring api_key removed).
- IP-based login rate limiting (AUTH_RATELIMIT_* knobs) atop account lockout.
- Setting.set() creation race fixed (IntegrityError retry).
Release engineering and docs:
- __version__ 0.5.0 (distinct from __contract_version__, ADR-007),
CHANGELOG.md, Gitea Actions CI config, frontend version aligned.
- One wizard-first install story across README/DEPLOY; new CONFIG.md,
UPGRADE.md, BACKUP-RESTORE.md; CLAUDE.md and ROADMAP de-staled.
- Dockerfile multi-stage build now bundles the frontend; compose binds
MySQL to 127.0.0.1; stale database/schema.sql and one-off SQL removed.
Debt and fixes:
- .query.get() -> db.session.get() sweep; datetime.utcnow() removed
(naive-UTC via timezone-aware now); users.py on authz decorators.
- Fixed 4 stale tests (slides feed shape, shopfloor splitperemployee,
plugin contract purity) and the USB label page field mapping (both usb
modes emit the cmmc shape: device_id/device_desc).
- Health endpoint reports the real version.
248 tests pass; naming/style check green; frontend builds; fresh-DB
flask db upgrade + seeds verified; QR targets verified by decoding
rendered codes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
4.7 KiB
Markdown
116 lines
4.7 KiB
Markdown
# ADR-007: Product versioning and releases
|
|
|
|
- **Status:** ACCEPTED
|
|
- **Date:** 2026-07-10
|
|
- **Deciders:** cproudlock
|
|
- **Supersedes:** none
|
|
|
|
## Context
|
|
|
|
ADR-002 established semantic versioning for the plugin contract via
|
|
`__contract_version__` in `shopdb/__init__.py`. That number answers one
|
|
question only: is a given plugin compatible with this platform build. It
|
|
says nothing about the state of the product as a whole. A sister site
|
|
standing up its own instance needs a different answer: which release am I
|
|
running, and what changed since the last one.
|
|
|
|
Until now the product had no version, no tags, no changelog, and no CI.
|
|
ADR-002's pinning model implicitly assumes that a downstream site can
|
|
pin a known-good build, but there were no tags to pin to. Adopters had no
|
|
release record to read before upgrading, and no automated gate confirming
|
|
that a given commit builds and passes tests.
|
|
|
|
## Decision
|
|
|
|
The product carries its own release version, separate from the plugin
|
|
contract version.
|
|
|
|
1. **Product version** (`shopdb/__init__.py`): a single `__version__`
|
|
constant. This is the version of the shopdb-flask product as a whole.
|
|
It follows semantic versioning of the product's user-visible and
|
|
operator-visible behavior. It is deliberately NOT part of the
|
|
`shopdb.api` contract surface, so it is never re-exported through
|
|
`shopdb.api`; exporting it would itself be a contract change under
|
|
ADR-002.
|
|
|
|
2. **Plugin contract version** (`__contract_version__`): unchanged from
|
|
ADR-002. It moves only when the plugin contract surface changes, per
|
|
the major/minor/patch rules in ADR-002.
|
|
|
|
The two are distinct series with independent bump rules. They happen to
|
|
coincide at `0.5.0` for this release; that is a coincidence of timing,
|
|
not a coupling. A product release that changes no contract surface bumps
|
|
`__version__` while leaving `__contract_version__` fixed, and vice versa.
|
|
|
|
3. **Release record** (`CHANGELOG.md`, repo root): the canonical,
|
|
human-readable record of what changed in each release, in
|
|
Keep-a-Changelog format. Every release has an entry; work in flight
|
|
accumulates under `## [Unreleased]`.
|
|
|
|
4. **Git tags**: each product release is tagged `vX.Y.Z`, where `X.Y.Z`
|
|
matches `__version__` at the tagged commit. Tags are what ADR-002's
|
|
downstream-pinning model pins to.
|
|
|
|
5. **Frontend version** (`frontend/package.json`): kept in lock-step with
|
|
`__version__` so the shipped single-page app reports the same product
|
|
version as the backend that serves it.
|
|
|
|
### Release procedure
|
|
|
|
To cut release `X.Y.Z`:
|
|
|
|
1. Bump `__version__` in `shopdb/__init__.py` to `X.Y.Z`.
|
|
2. Bump `version` in `frontend/package.json` to the same `X.Y.Z`.
|
|
3. Move the accumulated `## [Unreleased]` notes in `CHANGELOG.md` into a
|
|
new `## [X.Y.Z] - YYYY-MM-DD` section, leaving a fresh empty
|
|
`## [Unreleased]` above it.
|
|
4. If the plugin contract surface changed this release, bump
|
|
`__contract_version__` per ADR-002 (independently of `__version__`).
|
|
5. Commit, then tag: `git tag -a vX.Y.Z -m "shopdb-flask X.Y.Z"`.
|
|
6. Push the commit and the tag.
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- Adopters have a real release to name in bug reports and a changelog to
|
|
read before upgrading.
|
|
- ADR-002's pinning story finally has tags to pin to.
|
|
- Product and contract can evolve at their own pace without one dragging
|
|
the other into a misleading bump.
|
|
|
|
### Negative / cost
|
|
|
|
- Two version numbers to keep straight. The comment in
|
|
`shopdb/__init__.py` and this ADR exist to keep the distinction clear.
|
|
- Release discipline: the changelog and both version constants must be
|
|
updated together, or the tagged build misreports itself.
|
|
|
|
### Neutral
|
|
|
|
- CI (`.gitea/workflows/ci.yml`) runs the backend tests, the naming/style
|
|
gate, and the frontend build on push and PR. It is best-effort: Gitea
|
|
Actions availability on the host is unverified, so the workflow is
|
|
config-only until a runner is confirmed.
|
|
|
|
## Alternatives considered
|
|
|
|
1. **Reuse `__contract_version__` as the product version.** Conflates two
|
|
independent concerns; a docs-only or UI-only release would either
|
|
falsely bump the contract or leave the product looking unchanged.
|
|
Rejected.
|
|
2. **Calendar versioning for the product** (e.g. `2026.07.0`). Easy to
|
|
bump but poor at signaling breaking operator-facing changes. Rejected
|
|
for the same reasons ADR-002 rejected it for the contract.
|
|
3. **No product version, rely on git SHAs.** Opaque to adopters and
|
|
unpinnable in any human-meaningful way. Rejected.
|
|
|
|
## References
|
|
|
|
- ADR-001 (defines the contract surface)
|
|
- ADR-002 (plugin contract versioning; `__contract_version__` bump rules)
|
|
- `shopdb/__init__.py` (`__version__`, `__contract_version__`)
|
|
- `CHANGELOG.md` (release record)
|
|
- `frontend/package.json` (frontend version, kept in lock-step)
|
|
- `.gitea/workflows/ci.yml` (CI gate)
|