Product Lifecycle Contract v1
Contract: stella.ops/productLifecycle@v1 Status: Active schema; 2026.9 lifecycle values approved for planning and live publication subject to endpoint/key/mailbox preflight in archived sprint SPRINT_20260430_006_DOCS_support_eol_policy.md. Sprint: SPRINT_20260430_006 Owner: Stella Ops manufacturer of record + Router Gateway owner Schema-pinning policy: Pinned per Stella minor release. Consumers MUST require the exact schemaVersion value they support and MUST NOT auto-follow a newer lifecycle schema. Deterministic fixture location: Router endpoint coverage lives under src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Integration/ProductMetadataEndpointsTests.cs. The serving code is src/Router/StellaOps.Gateway.WebService/Middleware/ProductMetadataMiddleware.cs and src/Router/StellaOps.Gateway.WebService/ProductMetadata/ProductMetadataDocumentProvider.cs. Replay/offline expectations: Lifecycle documents, release-manifest lifecycle blocks, and offline-kit metadata MUST validate without external network access.
Purpose
Defines the machine-readable lifecycle shape for Stella Ops support and end-of-life (EOL) metadata. It is the contract for anyone publishing, consuming, or offline-validating Stella release-train support windows — Router gateway operators, offline-kit tooling, and downstream consumers of the public lifecycle endpoint.
The same release-train fields are consumed by:
GET /api/v1/product/support-lifecycle(alsoHEAD) as served by the Router gatewayProductMetadataMiddlewarebefore generic/api/*dispatch./.well-known/stella-product-security.jsonsupport metadata (thesupportPolicy.currentMinorTrains[]block, also served byProductMetadataMiddleware).- The signed product update manifest (
stella.ops/productUpdateManifest@v1), whosesupportPolicy.releaseTrainblock is the authoritative source the live endpoint copies. Seeproduct-update-manifest-v1.md. - Offline-kit lifecycle metadata and deterministic replay tooling.
Implementation note: the live lifecycle endpoint does not generate or compute lifecycle values.
ProductMetadataDocumentProvider.GetSupportLifecycleJson()reads the singlesupportPolicy.releaseTrainrecord from the sealed, signature-verified release manifest and emits it verbatim. The staticpolicyblock below is hard-coded in the provider, andgeneratedAtis taken from thePlatform:Product:GeneratedAtUtcconfig value (not from manifest content).
The human-readable policy is ../legal/product-support-policy.md. The EOL transition runbook is ../legal/eol-policy.md.
Schema Version Metadata
| Field | Value |
|---|---|
schemaVersion | stella.ops/productLifecycle@v1 |
schemaPinningPolicy | per-stella-minor-release |
breakingChangesRequire | New vN+1 contract document plus migration notes. |
nonBreakingExtensions | Additive optional fields only when documented in this contract’s revision log. |
Any document carrying schemaVersion = stella.ops/productLifecycle@v1 MUST conform to the field definitions and validation rules below.
JSON Shape
{
"schemaVersion": "stella.ops/productLifecycle@v1",
"generatedAt": "2026-04-30T00:00:00Z",
"policy": {
"defaultWindowYears": 5,
"supportAnchor": "ga-minor-release-train",
"patchReleaseRule": "inherit-parent-minor-train-unless-explicitly-extended",
"ltsSelectionRule": "last-stable-minor-of-calendar-year-unless-overridden",
"eolDeprecationNoticeMonths": 12,
"extendedSupportTier": "commercial-option-outside-baseline-cra"
},
"releaseTrains": [
{
"version": "2026.9",
"releasedAt": "2026-09-01",
"securitySupportUntil": "2031-09-01",
"eolAt": "2031-09-01",
"lts": false
}
]
}
Field Definitions
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion | string | yes | Always stella.ops/productLifecycle@v1 for this contract. |
generatedAt | string (ISO-8601 timestamp, UTC) | yes | Timestamp when the lifecycle document was generated. In the live Router endpoint this is the Platform:Product:GeneratedAtUtc config value, normalised to UTC, not a value derived from manifest content. |
policy | object | yes | Static policy values approved by CRA-Q2. Hard-coded in ProductMetadataDocumentProvider.GetSupportLifecycleJson(). |
releaseTrains | array of objects | yes | Stella minor train lifecycle records. The schema permits multiple records (current plus historical), but the live Router endpoint currently emits exactly one element — the single supportPolicy.releaseTrain copied from the running build’s signed release manifest. |
policy
| Field | Type | Required | Description |
|---|---|---|---|
defaultWindowYears | integer | yes | Baseline security-support window from each GA minor train. Default and minimum: 5. |
supportAnchor | string enum | yes | Always ga-minor-release-train for v1. |
patchReleaseRule | string enum | yes | Always inherit-parent-minor-train-unless-explicitly-extended for v1. |
ltsSelectionRule | string enum | yes | Always last-stable-minor-of-calendar-year-unless-overridden for v1. |
eolDeprecationNoticeMonths | integer | yes | Minimum public notice before EOL. Default and minimum: 12. |
extendedSupportTier | string enum | yes | Always commercial-option-outside-baseline-cra for v1. |
releaseTrains[]
| Field | Type | Required | Description |
|---|---|---|---|
version | string | yes | Stella minor train identifier. Patch releases inherit the parent train and SHOULD NOT appear as separate v1 records unless an explicit extension changes train-level lifecycle data. |
releasedAt | string (ISO-8601 date, UTC) | yes | GA date for the minor train. The placeholder TBD-release-roadmap is allowed only before live publication. |
securitySupportUntil | string (ISO-8601 date, UTC) | yes | Last date for baseline security support. Computed as releasedAt + 5 years unless Stella publishes a later date. The placeholder TBD-release-roadmap is allowed only before live publication. |
eolAt | string (ISO-8601 date, UTC) | yes | End-of-life date for the train. MUST be greater than or equal to securitySupportUntil. The placeholder TBD-release-roadmap is allowed only before live publication. |
lts | boolean | yes | true when the train is the named LTS release for its calendar year. |
Validation Rules
schemaVersionMUST equalstella.ops/productLifecycle@v1.policy.defaultWindowYearsMUST be>= 5.policy.eolDeprecationNoticeMonthsMUST be>= 12.- Live documents MUST NOT contain
TBD-*placeholders. releasedAt,securitySupportUntil, andeolAtMUST be ISO-8601 calendar dates in live documents.securitySupportUntilMUST be at least five years afterreleasedAtunless a shorter product lifetime has been explicitly approved and documented outside this v1 baseline.eolAtMUST be greater than or equal tosecuritySupportUntil.- Exactly one release train per calendar year SHOULD have
lts = trueonce the year’s LTS selection is made. - Consumers MUST preserve unknown optional fields when signing or replaying a lifecycle document, but MUST NOT treat unknown fields as changing the v1 support commitment.
Release Manifest Integration
The signed product update manifest (stella.ops/productUpdateManifest@v1) MUST reference this contract when it carries support lifecycle data, and is the authoritative source the live lifecycle endpoint reads from. See product-update-manifest-v1.mdfor the full manifest contract. The manifest lifecycle block uses the following shape (field names verified against ProductMetadataDocumentProvider.LoadReleaseManifest):
{
"supportPolicy": {
"lifecycleSchema": "stella.ops/productLifecycle@v1",
"releaseTrain": {
"version": "2026.9",
"releasedAt": "2026-09-01",
"securitySupportUntil": "2031-09-01",
"eolAt": "2031-09-01",
"lts": false
}
}
}
The release manifest MUST copy lifecycle values from the same pinned source used to generate the public lifecycle endpoint. It MUST NOT hand-edit support dates independently of this contract. The Router gateway enforces manifest integrity at serve time: the manifest’s top-level version MUST match the running product version (Platform:Product:RunningProductVersion), and when Platform:Product:RunningImageDigest is set the manifest images[] array MUST contain that digest — otherwise the endpoint fails closed with HTTP 503.
Offline Determinism
Lifecycle JSON must be generated from release metadata committed or packaged with the release. Offline validators must be able to:
- Validate the schema version and required fields.
- Determine whether a release train is supported, security-only, or EOL using only bundled metadata.
- Compare the lifecycle block in a signed release manifest with the lifecycle endpoint payload.
- Replay historical lifecycle decisions without contacting Stella infrastructure.
Router Runtime Binding
ProductMetadataMiddleware (registered in Program.cs via app.UseMiddleware<ProductMetadataMiddleware>()) serves GET /api/v1/product/support-lifecycle from sealed, signature-verified release-manifest metadata, ahead of generic /api/* microservice dispatch. The endpoint is unauthenticated metadata served by middleware before routing/auth — no StellaOpsScopes scope gates it. The same middleware also serves /.well-known/security.txt and /.well-known/stella-product-security.json.
HTTP behaviour
- Methods:
GETandHEADare served. Any other verb returns HTTP405 Method Not AllowedwithAllow: GET, HEAD.HEADreturns headers (includingContent-Length) with no body. - Success (
200 OK):Content-Type: application/json; charset=utf-8, anETagheader, andCache-Control. TheCache-Controlvalue ispublic, max-age=<CacheMaxAgeSeconds>when the configured cache age is positive, otherwiseno-cache. - ETag: a strong ETag computed as the lowercase hex SHA-256 of the exact response bytes, wrapped in double quotes (
"<64-hex-chars>"). - Conditional requests: when an
If-None-Matchrequest header matches the current ETag (or is*), the endpoint returns304 Not Modifiedwith theETagandCache-Controlheaders and no body. - Fail closed (
503 Service Unavailable): empty body andCache-Control: no-store, with noETag.
Fail-closed conditions (HTTP 503)
The endpoint fails closed when TryBuildDocuments() cannot produce a verified document, including when:
Platform:Product:PublishMetadatais false (publication disabled).Platform:Product:SealedConfigis false (config not sealed).- Any of the eight operational preflight checks under
Platform:Product:Preflightis incomplete (MailboxDeliveryVerified,MailboxAccessVerified,KeyFreshnessVerified,KeyFingerprintVerified,PlacementAnchorVerified,SupportLifecycleVerified,RotationVerified,EscalationVerified). Platform:Product:ReleaseManifestSignatureVerifiedis false.Platform:Product:GeneratedAtUtcis not a parseable timestamp.Platform:Product:CanonicalBaseUrlis not an absolute HTTPS URL.Platform:Product:ReleaseManifestPathis unset or the file is missing.- The release manifest fails validation: wrong
schemaVersion(must bestella.ops/productUpdateManifest@v1), anyTBD-placeholder string anywhere in the document, manifestversiondoes not match the running product version, the running image digest is not covered byimages[], a wrongsupportPolicy.lifecycleSchema(must bestella.ops/productLifecycle@v1), missing/empty required fields, or unreadable/invalid JSON.
Configuration keys (Platform:Product section)
Bound by ProductMetadataOptions.Bind; each also accepts a flattened dotted key (e.g. platform.product.publishMetadata).
| Key | Type | Default | Purpose |
|---|---|---|---|
PublishMetadata | bool | false | Master enable for product metadata publication. |
SealedConfig | bool | false | Asserts the metadata config has been sealed. |
CanonicalBaseUrl | string | https://stella-ops.org | HTTPS base URL used in security.txt / product-security URLs. |
GeneratedAtUtc | string | 2026-04-30T00:00:00Z | Source of the lifecycle generatedAt field; must be parseable. |
CacheMaxAgeSeconds | int | 300 | Drives Cache-Control: public, max-age=. |
RunningProductVersion | string | assembly informational version | Must equal the manifest top-level version. |
RunningImageDigest | string? | unset | When set, must be present in manifest images[].digest. |
ReleaseManifestPath | string? | unset | Filesystem path to the signed product update manifest JSON. |
ReleaseManifestSignatureVerified | bool | false | Asserts the release manifest signature was verified out-of-band. |
Preflight:* | bool (×8) | false | Operational preflight gates; all must be true (see above). |
Revision Log
| Date (UTC) | Change | Notes |
|---|---|---|
| 2026-05-30 | Doc/code reconciliation against Router gateway implementation. | Documented HEAD/405/304 behaviour, ETag format (quoted lowercase hex SHA-256), full fail-closed condition list, and the complete Platform:Product config-key set; clarified that the live endpoint copies a single supportPolicy.releaseTrain from the signed manifest verbatim rather than generating values. Grounded in ProductMetadataMiddleware, ProductMetadataDocumentProvider, and ProductMetadataOptions. |
| 2026-04-30 | Filled 2026.9 lifecycle planning values. | 2026.9 releases on 2026-09-01; baseline support and EOL both run through 2031-09-01. |
| 2026-04-30 | v1 introduced. | Captures CRA-Q2 lifecycle fields: version, releasedAt, securitySupportUntil, eolAt, and lts. |
