Release Manifest — design reference

The long-form companion to SDD — Version Control Service. The SDD carries the decisions that need approval; this page carries the evidence behind them — what the code does today, what the snapshot looks like, how the checklist is generated, and what fits into the delivery window.

Sources: release-manifest-guide7.docx (reference and developer guide v1.0), release-manifest-guide-addendum_v9.docx (Docker Pull column), release-manifest-v11.html (working prototype, all data mocked).

Requirements

Traceable identifiers used throughout. Source: guide (G) and addendum (A).

ID Requirement Source

R1

Releases are grouped in the sidebar under their Solution family; hot-fixes render above sprints within a group

G 1.3

R2

The Solution version is derived from the FTACS major.minor version shipped in that release

G 1.4, 2.2

R3

Release Notes list the Jira items shipped, split into Tasks/Features and Bug fixes, each with key, link and the verbatim summary

G 1.3

R4

Bug items show the affected customer name

G 1.3, 2.3

R5

Hot-fix releases show only the Bug fixes column and no version table

G 1.3

R6

The version table lists every component grouped by deployment layer, with version, build, status badge and changelog link

G 1.3

R7

The status badge is one of updated, stable, breaking, hotfix, new — computed against the previous release

G 1.3, 2.3

R8

Compatibility renders an ACS pivot block plus per-component Requires and Used-by cards, from a single directed dependency list

G 1.3, 2.3

R9

On a hot-fix, Compatibility narrows to the patched components plus their direct dependencies

G 1.3

R10

The deployment checklist has one expandable entry per component needing action, with change-type tags, a plain-English summary, template links and a git-style diff with per-file tabs

G 1.3, 2.4

R11

Breaking entries carry the red border and the warning banner

G 1.3, 2.4

R12

Upgrade Path: From/To dropdowns, chronological walk, later entry supersedes earlier, aggregated release notes, path badges, validation of same-version and downgrade input

G 1.5, 2.8

R13

Page state is in-memory only; the published page is read-only

G 2.9

A1

The version table has a Docker Pull column with click-to-copy per FT component; third-party rows show a dash; the column is absent on hot-fixes

A 1.3a

A2

The Harbor path per component is data, not derived from the display name

A 2.3a

Non-functional: the published artifact is self-contained with no build step and readable offline (N1); a published manifest is immutable history (N2); generation must never block or fail the finalize flow (N3); all three test layers are exercised (N4).

Gap analysis

Verified against the working tree.

Already available

Against main, which is the baseline this feature is built on. main stores everything in JSON files; the Postgres work happening elsewhere is not close to release and is not depended on here — see the storage decision (D11) and the migration path in the SDD.

The single row marked (missing) is the one capability main lacks that this feature needs.

Need Where it lives now

Release record: sprint name, URL, author, per-project version, build, status, steps

services/release_storage.pycreate_release; the real record shape is visible in data/releases.json

Harbor image URL, tag and digest per project

Project result fields harborProject, imageTag, imageUrl, imageDigest

Component catalogue for FT services

data/projects.json — 19 projects keyed by key, of which 16 carry a harborProject path and 14 declare dependencies; group and docsComponentName where set

Jira tickets per release, with issue type and a customer flag

routes/workflow/release/_changelog.py (_resolve_jira_refs), ft-jira-client

Docs-portal commit and build trigger

routes/workflow/release/_docs_portal.py (trigger_docs_portal_build)

Finalize e-mail with a call-to-action button

routes/workflow/email.py (_cta_button, build_sprint_email)

Hot-fix builds with branch, version and build tag

routes/workflow/release_build.py (submit_release_build), services/release_build_history.py

Third-party images with pinned tags, per-service variable contracts, config lists, dependency edges

The deployment.yaml contracts and the compose templates in the deployments repository

JSON storage with a locked read-modify-write, and a retention pattern to copy

services/storage.py over ft-json-storage; services/release_storage.py and services/release_build_history.py as the models to follow

Reading arbitrary repository files at a given ref (missing)

Needs a REST transport in ft-github-client — about 40 lines, taken as its own change

Missing — the actual work

# Gap Consequence

G1

No Solution version concept anywhere. Deployment Manager deliberately dropped platform_version in migration 20260520_0003 because per-service tags made an environment-wide label redundant.

R1, R2 and R12 have no data source. The concept is introduced here and must not be pushed back into Deployment Manager.

G2

No layer attribute on components. projects.json has group, which is an organisational grouping, not a deployment layer.

R6

G3

The databases are absent from projects.json — MySQL, Oracle, ClickHouse, PostgreSQL and Redis are never built by CI, so nothing carries their versions. Hazelcast, ejabberd, Grafana and Superset are listed, but the last three have no Harbor path.

R6, R8

G4

Jira tickets are computed during changelog generation and never persisted.

R3 — historic manifests could not be rebuilt without re-walking commits and re-querying Jira.

G5

ft-jira-client reduces the "Project (assets)" field to a boolean, discarding the customer identity.

R4

G6

No previous-state snapshot, so updated versus stable versus new cannot be computed.

R7

G7

Nothing captures deployment actions: summary text, breaking flag, template links, diffs.

R10, R11

G8

release_build_history prunes after 10 days.

R5 — hot-fix manifests must be snapshotted at build time, not derived later.

G9

The prototype is one 1646-line file mixing data, rendering and styling.

N1, plus the repository’s file-size rule.

Specification deviations

Deviation 1 — Harbor tag format. The addendum specifies <version>_<buildNum>, e.g. hub.friendly-tech.com/ui/portals:7.0.6_0.0.2. The tag the pipeline actually pushes is v{version}-b{build}; a real record carries hub.friendly-tech.com/configs/ft-configs-service:v1.0.1-b0.0.1. A support engineer copying the addendum-formatted command would get a not-found from the registry. Resolution: the Docker Pull column renders the recorded imageUrl verbatim; no tag is recomputed. If the Harbor convention does change, the manifest follows it for free.

Deviation 2 — customer name. The guide shows a customer on bug items. The client only knows whether a ticket has a linked asset (hasCustomer from customfield_10183, whose value is a list of Assets objects). Whether the response carries a readable label, or a second Assets call is required, is unverified. A time-boxed spike decides; the fallback is a manual field.

Design decisions

ID Decision

D1

A module in CI/CD Manager, published as a static artifact. All inputs are local; the docs-portal publication path already exists. Rejected: a standalone application (would fetch all of its data back over HTTP), and a module in Deployment Manager (owns contracts, not releases). Exit path: because the artifact reads one JSON document, extracting a renderer service later is a file move.

D2

A manifest is an immutable, self-sufficient snapshot. It embeds the catalogue, edges, versions, items and checklist as they were, and never joins to live data at read time. Cost: duplication of roughly 30-60 KB per release. Accepted.

D3

Solution version is derived, not entered. major.minor of the FTACS project version; solution id sol-{major}; colour from a fixed palette. The derived value is stored and remains editable for releases where FTACS did not ship.

D4

The catalogue is composed, not re-entered. FT components from projects.json plus a new layer attribute; third-party components from the contracts, with layers from a small static map. Rejected: a components table — a third source of truth for the same facts.

D5

Dependency edges are unified, with projects.json winning. Project keys and contract service names are reconciled through an alias map; the contract role database expands to both mysql and oracle, which is how the guide wants them shown.

D6

Status is computed by diffing against the previous published snapshot. new = absent before; updated = version or build changed; stable = unchanged; hotfix = build-only change on a hot-fix; breaking = updated and the checklist entry is marked breaking.

D7

Generated draft plus human override, and the override wins. Every field records whether it is generated or edited; regeneration never overwrites an edited field. A tool that silently discards a human correction is worse than no tool.

D8

Contract diffs are computed between snapshots, not git refs. Each manifest stores a contract fingerprint. No dependency on the contracts repository being tagged, and history stays correct even if that repository is rewritten. It also side-steps Deployment Manager keeping only the latest contract row.

D9

One renderer, two consumers. A pure function from bundle to DOM, fed from the API in the app and inlined in the export. Upgrade Path stays client-side, as prototyped.

D10

Generation is a side effect of finalize, never a gate. The draft is built after the docs-portal phase and before the e-mail; failure logs a warning and the release continues.

D11

Manifests are stored as JSON on main, with the Postgres move designed in. The Postgres work under way elsewhere is not close to release, and this feature has a date — coupling them would hand that date to another project. Since a manifest is already one self-contained document, the file record maps one-to-one onto a row with a jsonb column, so the later move replaces one module’s internals rather than rippling through callers. Three constraints protect that: callers never touch the file, JSON field names are the future column names, and nothing relies on file ordering or whole-list reads. Rejected: waiting for the database (hands over the deadline), and lifting the database layer out on its own first (a day out of nine, for a benefit this feature does not need).

Snapshot document

Shaped as the union of the prototype’s top-level constants, so the renderer consumes it with no adaptation layer.

{
  "schemaVersion": 1,
  "id": "sprint-7",
  "type": "sprint",
  "solution": "sol-7",
  "solutionVersion": "7.0",
  "name": "Sprint 7",
  "period": "June 2025",
  "date": "2025-06-18",
  "notes": "<html allowed>",
  "updatedCount": 7,

  "components": [
    { "id": "ftacs", "layer": "core", "name": "FTACS (ACS Server)",
      "sub": "TR-069 / USP / MQTT", "slug": "ft-acs",
      "image": "ftacs/ftacs", "core": true }
  ],
  "layers":    [ { "id": "db", "label": "Layer 1 - Databases" } ],
  "solutions": [ { "id": "sol-7", "label": "Solution 7.x", "color": "#388BFD" } ],
  "deps":      [ ["ftacs", "mysql"] ],

  "items": [
    { "type": "bug", "jira": "DEV-2097", "jiraUrl": "https://.../browse/DEV-2097",
      "title": "OneIoT ACS > Provision > ...", "customer": "Proximus BE",
      "component": "Provision API", "status": "fixed" }
  ],

  "versions": {
    "ftacs": { "version": "7.0.0", "build": "b.0.0.1", "status": "updated",
               "pull": "hub.friendly-tech.com/ftacs/ftacs:v7.0.0-b0.0.1",
               "changelogUrl": "https://docs.friendly-tech.com/services/ft-acs/changelog" }
  },

  "checklist": [
    { "compId": "ftacs", "tags": ["env", "compose", "dbmigr"], "breaking": true,
      "summary": [ { "icon": "<emoji>", "text": "...",
                     "fileLink": { "label": "...", "href": "..." },
                     "origin": "generated" } ],
      "diffs":   [ { "file": ".env", "origin": "generated",
                     "hunks": [ { "type": "removed", "lineOld": 14, "text": "..." } ] } ],
      "origin": "edited" }
  ],

  "contracts": {
    "ftacs": { "image": "hub.friendly-tech.com/ftacs/ftacs", "defaultTag": null,
               "variables": { "ACS_WEB_SERVICE_PORT": { "default": "8080", "required": false } },
               "configs": ["config.d/..."], "ports": [8080],
               "templateSha": "9f2c..." }
  },

  "source": { "releaseId": "4390807f-...", "generatedAt": "2026-07-30T09:00:00Z",
              "generatedBy": "...@friendly-tech.com", "generator": "1.0" }
}

pull is stored resolved — it is the release record’s imageUrl, never a formula. contracts is the fingerprint the next release diffs against and is not rendered.

Checklist generation

Input: this release’s contract fingerprint against the previous published manifest’s.

Detected Tag Generated content

Variable added, removed, or default changed

env

A summary line naming the variable and a .env hunk

Image or default tag changed

compose

A summary line with old and new tag, plus a compose hunk

Entry added to the config list

newfile or config

A summary line naming the file and its target path

Ports or volumes changed

compose

A summary line plus a hunk

Compose template changed with no semantic delta

compose

A line flagging "template changed, review manually"

Nothing changed and the version is unchanged

none

A quiet row

dbmigr and breaking are never inferred — a schema change is not visible in a contract. Both are set by the release manager, and breaking is what promotes the version-table badge.

Generation and publication flow

Sprint. The finalize orchestration runs phases 0 through 7. Two insertions: the resolved Jira items are persisted during the changelog phase, and a draft is requested after the docs-portal phase and before the e-mail. Draft assembly resolves and freezes the catalogue, derives the Solution version, fills versions (released projects from the release record, everything else inherited from the previous snapshot, third-party from the contracts), splits the Jira items by issue type, generates the checklist and writes the fingerprint.

Hot-fix. The draft is created at build time, because the build history prunes after 10 days. Only the patched components appear, items are bugs only, no version table, and Compatibility narrows to the patched components plus their direct dependencies.

Publication. The reader is a support engineer at a customer site, so the artifact is published to the documentation portal rather than served from an authenticated admin tool; the authenticated UI exists for the release manager who curates the draft. The finalize e-mail carries a deep link, and it appears only when the manifest actually reached published state.

Surface Audience Content

Version Control tab

Release manager

Drafts, checklist editor, preview, publish

The published page on the portal

Support engineers

Every published manifest, both views, no login, offline-capable

Finalize e-mail

The announcement list

A deep link to the release

Consequence to accept: the published copy is a point-in-time export, so editing a published manifest means re-publishing. That is correct for a document whose value is being an authoritative record.

Delivery schedule

The full design is roughly 19 working days; the window from 31 July to 12 August is 9. Every phase ships at reduced depth. Tests are not part of what gets compressed.

Day Date Deliverable

1

Fri 31 Jul

Manifest storage with atomic writes; catalogue composition; dependency unification and alias map

2

Mon 3 Aug

Jira Assets spike; persist the resolved Jira items, split by issue type

3

Tue 4 Aug

Builder: versions, status, solution derivation, contract fingerprint

4

Wed 5 Aug

Contract reader and diff; generated checklist draft; the finalize hook

5

Thu 6 Aug

Renderer port: sidebar, release notes, version table, compatibility

6

Fri 7 Aug

Renderer port: checklist and diff viewer

7

Mon 10 Aug

Upgrade Path; checklist editor with edited-wins precedence

8

Tue 11 Aug

Hot-fix manifests; static export and portal publication

9

Wed 12 Aug

E-mail link; full three-layer suite, fixes, demo

The original plan had ten days and a dedicated stabilisation day. One day is already gone, so days 1 and 2 absorb what were three days of work, and the backfill has moved out of the window — it was the designated first casualty.

Reduced depth. The generated diff covers variables, image tags and config additions, not ports, volumes or template hashes. The layer attribute ships as a static map rather than a settings field. Backfilled releases get no checklist, because there is no earlier fingerprint to diff against. Hot-fix Compatibility may ship as the full graph with patched components highlighted. The customer-name spike is half a day; publication has no in-app preview mode.

What may not make the date, in the order it would go: Upgrade Path (a standalone view), customer names (already time-boxed), hot-fix manifests (the one with a decay cost). Backfill is already out. Generation at finalize and publication cannot slip.

External dependencies. A layer assigned to each of the 19 projects and the third-party list confirmed by the end of day 1; agreement on who writes the breaking-change prose at finalize. No dependency on the Postgres work — see D11.

Risks

# Risk Mitigation

1

Customer identity may need a second Assets call

Time-boxed spike; manual fallback; the existing flag keeps working

2

Contracts move from the deployments repository into service repositories

The contract reader reuses the established resolution order, so the move is configuration

3

Mock contracts may not match production files, so a generated diff can be wrong

Exactly why the human override is authoritative and every generated field is labelled

4

The Harbor tag convention may change

The manifest echoes the recorded image URL, so it follows automatically

5

The snapshot schema will evolve

schemaVersion in the document; the renderer refuses an unknown major and says so

6

The finalize script is large and carries an existing diff

One inserted call; the Playwright quality mark goes green first

7

Nine working days against roughly 19 days of design, with no buffer

Reduced depth per phase; a named drop order; generation and publication are protected

8

The constraints that keep the Postgres move additive erode under deadline pressure

They are testable, not aspirational: test_manifest_storage.py covers the round-trip and the no-direct-file-access boundary

9

An interrupted write truncates the whole history, because the storage library rewrites in place and reads swallow parse errors

Manifest storage writes to a temporary file and os.replace-s it in; an unreadable file raises rather than reporting an empty history

Open questions

  • Q1 — does the Jira Assets field carry a label, or is a second call required? Decided by the day-2 spike.

  • Q2 — who owns the breaking-change prose at finalize: the release manager, or the service team that made the change? Determines whether the editor needs per-component assignment.

  • Q3 — how far back should the backfill go? Upgrade Path is only useful across the Solutions customers actually run.

  • Q4 — do the per-release template guide pages referenced by the guide exist, or must publication generate them? Currently assumed: links are entered by hand, and unresolvable links are omitted.