Skip to content

Update spec to include tag history endpoint#606

Open
jcarter3 wants to merge 8 commits into
opencontainers:mainfrom
jcarter3:tag_history
Open

Update spec to include tag history endpoint#606
jcarter3 wants to merge 8 commits into
opencontainers:mainfrom
jcarter3:tag_history

Conversation

@jcarter3

@jcarter3 jcarter3 commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Updates distribution spec to include new endpoint for retrieving the history of a given tag. The response is a json list of descriptors,including the created timestamp and sorted in descending order (newest first).
The list can be paged.

Comment thread spec.md Outdated
@jcarter3 jcarter3 force-pushed the tag_history branch 2 times, most recently from ba1f62d to dd534be Compare May 4, 2026 19:24
@jcarter3 jcarter3 changed the title **WIP** Update spec to include tag history endpoint Update spec to include tag history endpoint May 4, 2026
@jcarter3 jcarter3 marked this pull request as ready for review May 4, 2026 19:25

@mikebrow mikebrow 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.

some thoughts

Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md
When `n` is zero, this endpoint MUST return an empty array.
When `n` is not specified, the registry MAY apply a default limit.

- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL

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.

maybe from or ot (older than)

Suggested change
- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL
- **`ot`** *string (RFC 3339 timestamp)*, OPTIONAL

Comment thread extensions/_oci.md Outdated

- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL

When provided, only entries whose `org.opencontainers.tag.created` value is strictly less than (i.e. older than) `before` will be returned.

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.

Suggested change
When provided, only entries whose `org.opencontainers.tag.created` value is strictly less than (i.e. older than) `before` will be returned.
When provided, only entries whose `org.opencontainers.tag.created` value is strictly less than (i.e. older than) the `ot` timestamp will be returned.

Comment thread extensions/_oci.md
When `n` is zero, this endpoint MUST return an empty array.
When `n` is not specified, the registry MAY apply a default limit.

- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL

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.

is there a chance to have two results with the exact same time stamp..

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Perhaps a registry could consider every update of a tag as a delete followed by a create and so the timestamp would be the same.

Comment thread extensions/_oci.md

When provided, only entries whose `org.opencontainers.tag.created` value is strictly less than (i.e. older than) `before` will be returned.
This is used for time-based pagination: pass the `org.opencontainers.tag.created` value of the last entry returned in the previous response to retrieve the next page.

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.

another idea would be to include a "newer-than" option, or a "between" timestamp1/timestamp2 allowing a client to query new tags or a time range..

@sudo-bmitch sudo-bmitch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here's some early feedback. I still need to spend some time implementing this before considering approving, and we need a conformance test.

Comment thread extensions/_oci.md
Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md Outdated
]
```

Results MUST be sorted in descending order by the history entry timestamp, which is either the `org.opencontainers.tag.created` annotation value or the `org.opencontainers.tag.deleted` annotation value (i.e. the most recent entry appears first).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In image spec, we use the prefix org.opencontainers.image. For distribution, perhaps we should standardize on org.opencontainers.distribution to avoid conflicts with other specs?

Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md
Comment thread extensions/_oci.md Outdated
Comment thread extensions/_oci.md Outdated
Comment on lines +155 to +177
- **`mediaType`** *string*, REQUIRED

MUST be `application/vnd.oci.empty.v1+json`.

- **`digest`** *string*, REQUIRED

MUST be `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`.

- **`size`** *integer*, REQUIRED

MUST be `2`.

- **`data`** *string*, REQUIRED

MUST be `e30=`.

- **`annotations`** *map of strings*, REQUIRED

Annotations associated with the historical tag entry. MUST include:

- **`org.opencontainers.tag.deleted`** *string*, REQUIRED

The RFC 3339 timestamp at which the tag was deleted. Used as the sort key and as the cursor for time-based pagination.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd rephrase to use the descriptor of the deleted entry. And now with so much overlap, I think we can probably merge the two create/delete sections and then document the two possible annotations.

Suggested change
- **`mediaType`** *string*, REQUIRED
MUST be `application/vnd.oci.empty.v1+json`.
- **`digest`** *string*, REQUIRED
MUST be `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`.
- **`size`** *integer*, REQUIRED
MUST be `2`.
- **`data`** *string*, REQUIRED
MUST be `e30=`.
- **`annotations`** *map of strings*, REQUIRED
Annotations associated with the historical tag entry. MUST include:
- **`org.opencontainers.tag.deleted`** *string*, REQUIRED
The RFC 3339 timestamp at which the tag was deleted. Used as the sort key and as the cursor for time-based pagination.
- **`mediaType`** *string*, REQUIRED
The media type of the manifest this tag previously pointed to at that point in time.
- **`size`** *integer*, REQUIRED
The size in bytes of the manifest.
- **`digest`** *string*, REQUIRED
The digest of the manifest, in the form `<algorithm>:<encoded>`.
- **`annotations`** *map of strings*, REQUIRED
Annotations associated with the historical tag entry. MUST include:
- **`org.opencontainers.distribution.tag.deleted`** *string*, REQUIRED
The RFC 3339 timestamp at which the tag was deleted. Used as the sort key and as the cursor for time-based pagination.

Comment thread extensions/_oci.md

##### Query Parameters

The following query parameters MAY be provided:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Perhaps there could also be a query parameter for "digest" such that a client could verify that a tag did point at the manifest when given an image reference like image:tag@sha256....

It would return 404 if the digest isn't in the history but otherwise returning the expected JSON array of descriptor objects that includes the digest. I'm not sure if that'd be just the one descriptor (or two if deleted?) or perhaps up to n. And it should respect the other query parameters like before and so also 404 if the digest is not before the specified date.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a comment for this above:

If a pinned digest is provided with the tag, the registry MUST return a `404 Not Found` if the digest is not part of the tag's history.

This might cover it? Or we could send digest as a separate query param... maybe that's better?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 I like pinned digest in the tag, no need for a query parameter when we already have the format. Oh, but I don't see the pinned digest format defined in spec.md? 🤔

I think it may need guidance on what they should return when passed a pinned digest? Probably the only guarantee is that it must be in the list of n (barring before making it no longer in the list and thus 404).

Comment thread extensions/_oci.md Outdated

A successful request MUST return a `200 OK` response code.
If the repository does not exist, the registry MUST return a `404 Not Found` response code.
If the tag has no history or the registry does not implement this extension, the registry MUST return a `404 Not Found` response code.

@yosifkit yosifkit Jul 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should this have a MUST/SHOULD respond with UNSUPPORTED in the error code for "registry does not implement this" case? Or perhaps some other recommended required error codes for the other 404 responses so that users of the API can know which it is. Or maybe the n=0 trick is the correct way to figure out if history is supported?


Related:

If the tag has no history [...] the registry MUST return a 404 Not Found

when n is zero, this endpoint MUST return an empty array

These seem conflicting. Perhaps a clarification is needed?

@yosifkit yosifkit Jul 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In other words, does n=0 override the "repository does not exist" 404 or just the "no history" 404 or "registry does not implement this extension" 404 and what about the pinned digest 404?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, those do some conflicting, good catch.

Currently most http routers on an unknown path would return 404 by default, hence the "return 404 if not supported". If we forced an UNSUPPORTED it'd require registries to explicitly update to say they aren't handling it, vs it being the default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the appropriate thing to do is return an empty response with 200 OK if there is no history - this seems to make more sense?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤔 Could it have no history? What does that mean? Does that mean nothing was ever tagged as this? Or just that the registry hasn't yet recorded history? But if it has at least the current tagged image and they support the history endpoint, it must have a minimum history of one, right? So, 404 is correct, the tag does not exist as an image, or it would have history. Returning an empty list for every tag that doesn't exist seems odd.

Comment thread extensions/_oci.md
`<name>` is the namespace of the repository, and `<tag>` is the name of the tag whose history is being queried.

A successful request MUST return a `200 OK` response code.
If the repository does not exist, the registry MUST return a `404 Not Found` response code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If the repository does not exist, the registry MUST return a 404 Not Found response code.

When n is zero, this endpoint MUST return an empty array, useful for determining if history is available (200) or not (404) for the tag query.

I think someone could interpret that this should they return 200 and an empty list when n=0 even if the repository does not exist.

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