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 over RELEASES (not solution versions — FT numbers releases inside a minor, so 7.1.1 and 7.1.2 must be two points), chronological walk, later entry supersedes earlier, aggregated release notes, path badges, validation of same-release 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.

Re-verified on 31 July 2026 against the working tree and against the live Jira, Harbor and GitHub the application is configured for. Nothing in this table is missing: the row that previously read "needs a REST transport" was wrong, and the source of the deployment facts has moved from the contracts repository to the documentation portal — see D4 and D12.

Need Where it lives now

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

services/release_storage.py — create_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 variables, config lists, dependency edges

The deployment guides in the docs-portal repository: full-system-deployment.adoc (two [source,yaml] compose blocks, MySQL and Oracle variants, pinning postgres:16, clickhouse-server:24.8, jdbc-bridge:2.1.0, hazelcast:ft.5.5.0, oracle-xe:21; every FT image is latest) and full-system-env-reference.adoc (13 === <service>/.env sections, each a [source,properties] block with a comment above every variable)

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

ft-github-client.get_file_content (client.py:419) already does it — repos/{repo}/contents/{path}?ref= accepts a branch, a tag or a sha

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. The guide covers most of them; MySQL is built from mysql.Dockerfile in every source, so no machine-readable version exists for it anywhere.

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. Closed on 31 July: the identity is recoverable in one extra call, see deviation 2.

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). Measured on 31 July 2026 against the live instance, so no spike is needed. The issue payload carries no label:

"customfield_10183": [ { "workspaceId": "bdaadb3f-...", "id": "bdaadb3f-...:49", "objectId": "49" } ]

A second call resolves it, with the same credentials but a different host:

GET https://api.atlassian.com/jsm/assets/workspace/{workspaceId}/v1/object/{objectId}
-> { "label": "RIL - Central", "objectType": { "name": "Projects" } }

DEV bugs carry the field, so this applies to sprint releases and not only to PROD tickets — DEV-2765 resolves to Bharti Airtel Limited, DEV-2744 to SCG England (via Provu). Resolution: ft-jira-client gains a customers list resolved through that call, cached by objectId; a release touches a handful of distinct ids. The label is rendered verbatim — it is a "Projects" object and may carry a person’s name or a supply chain, and cleaning it up in code would be guessing. Corrections belong in Jira.

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 a static map, refined by the guide’s pinned tags where the guide states a version. latest is never a version. MySQL is always static. Layers come from the prototype, with Hazelcast in layer 1 to match the guide’s startup order; emulators and Superset are excluded because support does not deploy them. 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 the service names used in the guide’s compose block are reconciled through an alias map; a dependency on the 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

Guide diffs are computed between snapshots, not git refs. Each manifest stores the guide commit sha and the parsed fingerprint. The portal is unversioned (antora.yml carries version: ~, master only), so a sha is the only stable handle — and diffing the parsed structure rather than the text means prose edits, typos and section moves produce no rows. What survives is a semantic delta that may still belong to an earlier release or be a documentation correction, which no algorithm can tell apart; the commits between the two shas are stored alongside so the release manager can see what caused each row. The caption says "changed in the deployment guide since the previous manifest", not "changed in this release".

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.

D12

The deployment facts come from the documentation portal, not from the deployments repository. ft-deployments describes a development cluster and carries generic versions, so it is not authoritative; its 26 deployment.yaml files are also declared mock by Deployment Manager’s own report and live only on a feature branch. The guides in docs-portal are what support actually follows, they pin the third-party versions this feature needs, and the repository is already reachable from CI/CD Manager. Consequence accepted: the manifest is derived from a document it was meant to replace, so the guides stay alive and hand-maintained — the manifest indexes them.

D13

No backfill, one frozen baseline instead. The first manifest is for 7.1.2 and needs a 7.1.1 predecessor that never existed. A baseline record is written once — versions from Harbor by release-tag pattern, guide fingerprint by date — and then frozen. Rejected: a backfill importer, because a backfilled release has no earlier fingerprint and therefore no checklist, which is the part worth having; and lazy resolution at 7.1.2 finalize, which would swallow weeks of intervening pushes and report changed components as stable.

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" }
  ],

  "guide": {
    "repo": "Friendly-Technologies/docs-portal",
    "sha": "9f2c...",                       // the commit the guide was read at
    "readAt": "2026-07-30T09:00:00Z",
    "commitsSincePrevious": [ { "sha": "...", "date": "...", "author": "...", "subject": "..." } ],
    "services": {
      "ftacs": { "image": "hub.friendly-tech.com/ftacs/ftacs", "tag": "latest",
                 "variables": { "ACS_WEB_SERVICE_PORT": { "default": "8080" } },
                 "configs": ["config.d/..."], "ports": [8080],
                 "anchor": "env-ftacs" }
    }
  },

  "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. build is stored as the release recorded it (0.0.4); the prototype’s b. prefix is a display form the renderer applies, and freezing it into the document would be a rendering decision written into history. source also carries what the release manager has to look at: unversioned (components with no version from any source — every third-party entry until the guide is read), unmatched (a released project the catalogue does not know) and unresolved (a project key the alias map could not place). guide is the fingerprint the next release diffs against and is not rendered; anchor is the existing [[env-<service>]] id in full-system-env-reference.adoc, which is what the checklist links to. No per-release template pages are generated — they already exist.

Checklist generation

Input: this release’s guide fingerprint against the previous manifest’s — parsed structures, never the prose.

Three pages are read at one commit: the compose block in full-system-deployment.adoc (the primary one only — the Oracle variant is a second block and merging both would describe a deployment nobody runs), the === <service>/.env sections of full-system-env-reference.adoc, and the per-service config table in partials/per-service-config-table.adoc. The table is keyed by the config directory rather than by its display column, so it introduces no vocabulary of its own — but the guide does speak two dialects: the compose block names the container (ft-device-network-service) and the environment reference names the directory (ft-device-network). Both are in the alias map. The shared Root .env keeps its own entry: it belongs to no component and it is where a breaking deployment change usually lands. icon is a KEY (env, image, file, service, none), not a glyph — a snapshot is history, and a glyph frozen into it is a rendering decision nobody can revisit.

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 the guide. 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 guide), 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

Customer names through the Assets API; persist the resolved Jira items, split by issue type

3

Tue 4 Aug

Builder: versions, status, solution derivation, guide fingerprint; the frozen 7.1.1 baseline from Harbor

4

Wed 5 Aug

Guide 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

Renderer port finished; checklist editor with edited-wins precedence; Upgrade Path if the port leaves room

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. The grilling on 31 July returned about a day — the Jira spike collapsed into four minutes of measurement, the ft-github-client change turned out to be unnecessary, and the layer assignment was closed from the prototype rather than waited on — and that day went to the renderer port, which is the one estimate the code contradicted. Backfill is not deferred but cancelled; see D13.

12 August is the delivery date, not the operational one. The first real manifest is produced when 7.1.2 is finalized, expected end of August or early September. What must exist by that finalize, not by the 12th: storage with atomic writes, draft generation, and the frozen 7.1.1 baseline. A release state that has passed cannot be reassembled.

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. Hot-fix Compatibility may ship as the full graph with patched components highlighted. Publication has no in-app preview mode. The AI assist for the breaking-change prose is a clipboard button, not an integration: settings.json carries no LLM provider, and this is the one field where a confident hallucination is executed against a customer’s production.

What may not make the date, in the order it would go: Upgrade Path (a standalone view), hot-fix manifests (the one with a decay cost), then the generated part of the checklist — the manual editor stays either way. Customer names have left this list; they now cost about twenty lines. Generation at finalize, publication and atomic writes cannot slip.

External dependencies — all closed on 31 July, before day 1. Layers taken from the prototype with Hazelcast moved to layer 1; the third-party list is the seven entries the prototype carries and projects.json does not; emulators and Superset excluded; the breaking-change prose is written by the release manager. No dependency on the Postgres work — see D11 — and none on ft-deployments — see D12.

Risks

# Risk Mitigation

1

Customer identity needs a second Assets call

Closed: measured, implemented, cached by objectId; the existing flag keeps working

2

The deployment guides stop being maintained once the manifest exists, and the generator silently produces empty checklists

The guide sha and its commit date are stored; an unchanged guide is reported as guideUnchanged sha=…​ since=…​ and shown to the release manager as a banner, never as "no changes"

3

A generated row may be a documentation correction or belong to an earlier release

Structure is diffed rather than prose, the causing commits are stored beside the rows, and the human override is authoritative — every generated field is labelled

4

The Harbor tag convention may change

The manifest echoes the recorded image URL, so it follows automatically. Confirmed in the registry: two formats coexist (v1.0.2-b0.0.1, legacy 5.0.1_0.0.6) and tags may carry a free-text suffix (v7.1.1-b0.0.2-fix415), which no formula could reproduce

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 — the cicd-manager project is 184 green as of 31 July

10

The baseline picks a branch build as a version — the newest artifact in most repositories is dev, feature-DEV-1709 or a bare sha

Only tags matching the release pattern are considered, ordered by version rather than push time; a project with no release tag is surfaced for manual entry instead of being skipped, and the whole baseline stays editable

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

Answered questions

Closed during the design review on 31 July 2026, before the first line of code.

  • Q1 — does the Jira Assets field carry a label? No. It carries objectId, and a second call to api.atlassian.com/jsm/assets/…​ resolves the label with the same credentials. Measured, not estimated; see deviation 2.

  • Q2 — who owns the breaking-change prose? The release manager, as the single author, with an AI assist through the clipboard. Per-component assignment to service teams is out: it needs a "team was asked, team answered" loop that does not exist yet, and it would leave the manifest unpublishable at finalize time. Every entry records editedBy, so the question can be reopened later on evidence.

  • Q3 — how far back should the backfill go? Nowhere; there is no backfill. One frozen baseline record for 7.1.1 instead — see D13.

  • Q4 — do the per-release template pages exist? The question is void: full-system-env-reference.adoc already carries stable anchors ([[env-ftacs]], [[env-northbound-api]], …​ [[compose-file]]), so the checklist links to those. Nothing is generated. Unresolvable links are still omitted.

Still open, and neither blocks day 1: the exact date of the 7.1.2 finalize (end of August or early September), and who physically fills in the first checklist.