Skip to content

Lifecycle SAF: align with OAS #5447, adopt externalLinks, define schema - #16

Open
miqui wants to merge 17 commits into
OAI:mainfrom
miqui:main
Open

miqui wants to merge 17 commits into
OAI:mainfrom
miqui:main

Conversation

@miqui

@miqui miqui commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

This PR brings the Lifecycle Specification in line with the SAF mechanism merged in OAI/OpenAPI-Specification#5447. It builds the Lifecycle Object on the Docs Links proposal, and replaces the stub schema with a real one. It supersedes #15.

SAF framing (operation-scoped, per discussion #5)

  • The Lifecycle Object describes one operation's deprecation and sunset. It is not a document-wide object; applying it across many operations is left to Overlays.
  • Adds a mapping from Lifecycle Object fields to the Deprecation (RFC 9745), Sunset (RFC 8594), and Link headers, and an informative appendix on AI agent / MCP use.

Alignment with OAS #5447

  • A document opts in through the root features map (features: { security: 1.0.0, lifecycle: 1.0.0 }). The spec notes that security must be listed explicitly once features is set.
  • The Lifecycle Object lives in a dedicated Operation Object lifecycle field, not in an operation-level features. This avoids reusing the root features name with a different type.

Adopting externalLinks (Docs Links proposal)

  • Breaking, pre-1.0.0: migration, replacement, and sunset.policy are replaced by a single lifecycle.externalLinks array of External Documentation Objects.
  • kind is required on each entry. deprecation, successor-version, and sunset are IANA link relation names, so each link maps directly to Link: <url>; rel="<kind>". Other kinds (for example changelog) are documentation only.
  • Lifecycle links go in lifecycle.externalLinks; general docs stay in the operation's own externalLinks.
  • Adds a complete OAS 3.3 example.

deprecated takes precedence

  • deprecated is the authoritative signal. An operation with lifecycle SHOULD set deprecated: true. If deprecated is absent or false, tools MUST treat the operation as not deprecated and ignore lifecycle: no headers, no deprecation annotations.
  • deprecated: true without lifecycle works as before, so APIs can adopt the SAF gradually.

Schema

  • The stub schema (properties: {} + additionalProperties: false) rejected every non-empty Lifecycle Object. The new schema validates the Lifecycle, Sunset, and External Documentation Objects, and requires kind in lifecycle.externalLinks.
  • Adds 5 pass and 19 fail fixtures; schema branch coverage is 100%.

Open questions for the SIG

  • Is lifecycle the right name for the Operation Object field?
  • Version: this spec is 1.0.0, but the #5447 example uses lifecycle: 1.1.0.
  • Still open: Header Mapping is labelled informative but uses SHOULD; there is no rule that sunset.date >= date; the compatibility [TODO] is unfilled.

Test plan

  • oai-spec-test tests/schema — 24 passed, 100% schema coverage
  • yarn validate-markdown (not run locally)

miqui and others added 14 commits September 13, 2026 16:53
Restructures the Lifecycle Object around the operation-scoped
deprecation/sunset model from discussion #5's April 2026+ comments
(the root-level, document-wide draft from earlier in the thread is
superseded) and places it under `features` on the Operation Object,
consistent with the Standardized API Feature (SAF) concept being
defined in OAI/OpenAPI-Specification#5310 and #5350.

Fills in the Introduction, Relationship to the OpenAPI Specification,
Definitions (Lifecycle Object, Sunset Object), a new informative
Header Mapping section (field -> Deprecation/Sunset/Link header
mapping per RFC 9745, RFC 8594, RFC 8288), Appendix A revision
history, and a new informative Appendix B on AI agent/MCP tooling
usage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAmP3nFaRwCARS7JygrV7r
Align lifecycle.md with SAF framing, using discussion #5 example data
minor changes
…ample

- Declare the SAF via the root `features` map (OAS PR #5447) and place the
  Lifecycle Object in a dedicated Operation Object `lifecycle` field
- Add a complete OAS 3.3 example using `externalLinks` from the Docs Links
  proposal alongside the Lifecycle Object
- Fix stale listPets_v2 reference in Appendix B

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Align lifecycle SAF with OAS #5447 features and add external links example
Swap `migration`, `replacement`, and `sunset.policy` for a single
`lifecycle.externalLinks` array of External Documentation Objects, per the
OAS Docs Links proposal. `kind` is required and uses IANA link relation
names (`deprecation`, `successor-version`, `sunset`), so each link maps
directly to a `Link` header; other kinds are documentation only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace named lifecycle link fields with an externalLinks array
The schema was a stub (`properties: {}` with `additionalProperties: false`)
that rejected every non-empty Lifecycle Object. It now validates the
Lifecycle, Sunset, and External Documentation Objects, requiring `kind` on
each `lifecycle.externalLinks` entry and rejecting the removed `migration`,
`replacement`, and `sunset.policy` fields.

Removes the unused Schema Object `$dynamicAnchor` hook and the matching
spec text, since the Lifecycle Object contains no Schema Objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Define the Lifecycle Object schema and add pass/fail tests
Define `deprecated` as the authoritative deprecation signal. Operations
with `lifecycle` SHOULD set `deprecated: true`; if `deprecated` is absent
or false, tools MUST treat the operation as not deprecated and MUST NOT
generate headers or present it as deprecated from the Lifecycle Object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make deprecated take precedence over the Lifecycle Object
Comment thread src/schemas/validation/schema.yaml Resolved
For consistency with the OAS schema, allow URI references, which are
resolved against the document's base URI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@miqui

miqui commented Oct 30, 2026

Copy link
Copy Markdown
Author

@karenetheridge thanks for the review — I switched url to uri-reference in 0c10911 (see the reply on your comment). Could you take another look when you have a chance?

@miqui
miqui requested a review from karenetheridge October 30, 2026 15:40
spec.openapis.org/oas/v3.3.0.html is not published yet, so the link check
fails. Link the SAF section to the v3.3-dev source and the Operation and
External Documentation Objects to the latest published OAS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@miqui

miqui commented Nov 5, 2026

Copy link
Copy Markdown
Author

pinning this here for review: OAI/OpenAPI-Specification#5585

Comment thread src/lifecycle.md
sunset:
date: "2025-07-01T00:00:00Z"
externalLinks:
- kind: deprecation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd be beneficial to also have examples of how those values map to the response headers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0371a89: the complete example now shows the Deprecation, Sunset, and Link headers a server would emit for GET /v1/flights, with a table mapping each header to the Lifecycle Object field it comes from. It also notes that the changelog/announcement entries and the Operation Object's own externalLinks generate no headers.

Comment thread src/lifecycle.md
operationId: listFlights_v1
summary: List all flights
deprecated: true
lifecycle:

@baywet baywet Nov 5, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably go under a sub-field named "features" (or something like this) to avoid mutating the schema for the core objects.

so essentially

get:
  features:
    # this is a reserved field name/key for lifecycle
    lifecycle:...

Security would be the only one diverging from this for compat reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a PR adding the container field to the OpenAPI Object, Path Item Object, and Operation Object (and maybe Response Object? I'm less sure of that so it can be added later). I think it would be great if someone other than me took a shot at this, but if no one is available I will do it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. #16 will move lifecycle under the container once the OAS defines it. Until then, 0371a89 adds an editor's note to the spec marking the current lifecycle placement on the Operation Object as provisional, and saying that it will be replaced by the container field.

@baywet

baywet commented Nov 5, 2026

Copy link
Copy Markdown
Member

another thing that came up during the meeting is that we should get generic tooling vendors and get their opinions on the current shape of the spec, especially for scenarios where the lifecycle information and what shows up at runtime disagree.

…ement

Add the Deprecation, Sunset, and Link headers a server would emit for the
complete example, with the Lifecycle Object field each comes from. Add an
editor's note that the Lifecycle Object will move under the SAF container
field once the OAS defines it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@miqui

miqui commented Nov 6, 2026 •

Copy link
Copy Markdown
Author

Thanks @baywet, agreed: vendor input on the current shape would be valuable before this settles. To make it concrete, here are the disagreement scenarios I'd suggest putting to them:

  1. Spec says deprecated, runtime doesn't: the operation has deprecated: true and a lifecycle object, but responses carry no Deprecation/Sunset headers. Which should a client or agent trust?
  2. Runtime says deprecated, spec doesn't: responses carry Deprecation/Sunset, but the OAD has no lifecycle and/or deprecated is absent. Should tooling surface the runtime signal anyway?
  3. Spec and runtime disagree on dates or links: for example, sunset.date in the OAD differs from the Sunset header, or Link relations point to different URLs.
  4. lifecycle present but deprecated absent or false: Lifecycle SAF: align with OAS #5447, adopt externalLinks, define schema #16 currently says deprecated takes precedence and lifecycle is ignored. Is that what tools would expect?
  5. Inconsistent dates: sunset.date earlier than date. Should this be invalid, or a warning?

For context, the spec currently treats the OAD as the design-time source and the headers as an optional runtime byproduct (see Header Mapping and Relationship to deprecated). It doesn't yet say which side wins when they disagree, apart from the deprecated precedence rule, and point 5 isn't covered at all.

Happy to collect the feedback in a discussion on this repo if that's useful. Are there specific vendors you'd suggest reaching out to?

@handrews handrews left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't gotten through all of this (and I am wondering if we could perhaps break this up to make it easier to resolve pieces and make progress?) but I wanted to publish this before tomorrow's call. My feedback is on details, the direction is good.

Comment thread src/lifecycle.md
Comment on lines +36 to +44
**Scope.** The Lifecycle Object applies at the Operation Object level only. An
earlier draft explored a root-level, document-wide object (see
[Appendix A](#appendix-a--revision-history)), but SIG discussion concluded
that a single OpenAPI document does not necessarily describe exactly one API,
so a document-wide deprecation signal does not generalize; see
[sig-lifecycle discussion #5](https://github.com/OAI/sig-lifecycle/discussions/5).
Applying the same lifecycle metadata uniformly across many operations remains
the job of [Overlays](https://spec.openapis.org/overlay/latest.html), not of
this object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think based on this we need to be explicit that trying to configure a lifecycle object at the other levels is an error. Otherwise it falls into the ambiguous area where things are maybe tolerated but ignored, which is not intuitive.

We do not need to include the history of the decision in the specification. Most readers wont' know what a "SIG discussion" is. I would just document the restriction and the Overlays approach for broader usage.

However, did we consider allowing it at the Path Item Object level? I'm not that familiar with how this will be used, but I have a vague sense that it's common to deprecate/sunset an entire endpoint, which would best be done at a Path Item level. There is also the question of deprecating only specific responses, but I feel like that's a "wait and see if anyone asks for it" thing.

Comment thread src/lifecycle.md
Comment on lines +46 to +49
**Non-goal.** This SAF does not replace `deprecated: true`, which
remains the coarse signal tooling already recognizes (for example, to gray out
an operation in Swagger UI). The Lifecycle Object is additional detail used
alongside it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my view, framing like "Non-goal" is more of a proposal sort of thing. Since this is addressed in detail where usage is described, I would just leave it there. Mentioning it in the introduction without context or crosslink is a bit confusing.

Comment thread src/lifecycle.md
Comment on lines +74 to +75
the OAS 3.2-compatible security behavior. As required by the OAS, all
documents in a multi-document OpenAPI Description MUST declare the same SAFs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd link to the relevant section here and say something like "see OAS section 1.2.3.4 for guidance on using SAFs across multiple OpenAPI Description documents" so that there's no chance of this paragraph drifting from the main section.

Comment thread src/lifecycle.md
Comment on lines +114 to +115
meant to complement — not replace — `deprecated: true` on the same Operation
Object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find "complement — not replace" to be a bit vague, but fortunately you have the more precise info available. Based on other text, the Lifecycle Object expands on the meaning of deprecated: true since that is required to be set in order for a Lifecycle Object to be usable.

Comment thread src/lifecycle.md
Comment on lines +90 to +96
**Editor's note.** The placement of the Lifecycle Object is provisional.
SAFs are expected to place their objects under a container field that the OAS
will define on the OpenAPI, Path Item, and Operation Objects, so that SAFs
do not add fields directly to core objects. Once the OAS defines that
container, the Lifecycle Object will move under it, and the `lifecycle` field
described in [Operation Object Fields](#operation-object-fields) will be
replaced.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go ahead and figure this out. Although it is reminding me that the reason I didn't use features for the declarations is that I wanted to use it for the fields... hmmm...

Comment thread src/lifecycle.md
Comment on lines +137 to +138
including its `summary` and `kind` fields. This specification therefore
requires an OpenAPI Specification version that defines those fields.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are going into 3.3 and there's no real expectation SAFs would work with an earlier version, I think we can cut this. Otherwise it kind-of implies that there are other versions that work with SAFs but not with this which is a bit confusing.

Comment thread src/lifecycle.md
Comment on lines +148 to +156
The Operation Object's `deprecated` field is the authoritative signal of
whether an operation is deprecated, and takes precedence over the Lifecycle
Object. The Lifecycle Object only adds detail to an operation that
`deprecated` already marks as deprecated.

An Operation Object with a `lifecycle` field SHOULD set `deprecated: true`.

If `deprecated` is absent or `false`, tools MUST treat the operation as not
deprecated, even if a `lifecycle` field is present. In that case, tools:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you're doing here in following how a lot of things in the OAS work. But a lot of those things would not be done the same way now. And many of them are like they are because we had to preserve compatibility. I would recommend:

  • A Lifecycle Object MUST result in an error if deprecated: true is not set. (however, if you know of a use case where this would be a problem, that would be a good reason to make this only a SHOULD).

Silently ignoring something is bad UX, and I've seen real production problems from the parts of the OAS that do that.

Comment thread src/lifecycle.md
Comment on lines +175 to +178
This specification defines the following `kind` values for entries in
`lifecycle.externalLinks`. Each value is the name of an IANA-registered link
relation, so a link's `kind` is also the `rel` of the `Link` header it maps to.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that it's an error to use these relation types in a Link header description on headers fields on a Response Objects associated with operations with a Lifecycle Object? What if the Lifecycle SAF is enabled but there isn't a Lifecycle Object? (I would lean towards them always being an error if lifecycle is enabled- clear errors over ambiguous multiple options).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants