@@ -299,9 +299,16 @@ system alone can neither publish a release nor discard a pending one.
299299Cancellation is protected because it is destructive: it discards the staged files and frees the name-version
300300pair. An index that left it at the upload-permission level would let a compromised upload credential delete a
301301release a maintainer has staged and is waiting to publish (a denial-of-release), or discard a session that
302- has been flagged and is awaiting review. An index that instead wants an automated uploader to be able to
303- abort its *own * in-progress uploads **MAY ** leave cancellation at the upload-permission level. How any
304- additional authorization is expressed, and whether it is offered at all, is determined by the index operator.
302+ has been flagged and is awaiting review. For a first release of a new project, cancellation also releases the
303+ temporary project-name reservation the index holds for the session, so an index that did not protect it would
304+ let a compromised upload credential discard the session and allow the name to be claimed by someone else. An
305+ index that instead wants an automated uploader to be able to abort its *own * in-progress uploads **MAY ** leave
306+ cancellation at the upload-permission level.
307+
308+ How any additional authorization is expressed, and whether it is offered at all, is determined by the index
309+ operator. An index **MAY ** keep this choice operator-wide, or expose it to individual project owners, for
310+ example by letting a project owner require the separate authorization for their own project, or allow a single
311+ principal to hold both.
305312
306313
307314.. _session-errors :
@@ -618,29 +625,36 @@ expected to remain available anyway, as this PEP does not propose a deprecation
618625an index **MAY ** allow a legacy upload to create a publishing session, so that from creation onward the session is the
619626same as one created directly.
620627
621- An index that supports this **MUST ** document it, and **SHOULD ** accept a ``staged `` field with the value
628+ An index that supports this **MUST ** document it, and **MUST ** accept a ``staged `` field with the value
622629``true `` in the legacy ``multipart/form-data `` upload request. When that field is present, the index creates a
623630publishing session in the ``open `` state for the uploaded file's project and version, adds the file to it as a
624- :ref: `completed <file-upload-session-states >` file upload, and does not publish it. The index **SHOULD **
631+ :ref: `completed <file-upload-session-states >` file upload, and does not publish it. The index **MUST **
625632return the :ref: `publishing session creation response body <publishing-session-response >` from that upload,
626633including the ``links `` and ``session-token `` keys, so that the publisher can then use the endpoints in this
627634PEP to :ref: `preview <staged-preview >`, :ref: `publish <publishing-session-completion >`, or :ref: `cancel
628- <publishing-session-cancellation>` the session. An index **MAY ** also create a session for an upload based on
635+ <publishing-session-cancellation>` the session. Returning the body is required because a client that sets
636+ ``staged=true `` but receives no session handle would be left unable to publish or cancel the staged release.
637+ Note that a client that sets ``staged=true `` without understanding the response may record it (for example in
638+ public CI logs); the ``session-token `` it contains grants only read-only :ref: `preview <staged-preview >`
639+ access, but indexes and publishers **SHOULD ** be aware that it can be exposed this way. An index **MAY ** also create a session for an upload based on
629640its own policy or the project's configuration, without the field being present; this allows a project to
630641require that its releases are staged in a way that an upload client cannot bypass. In this policy driven
631642case no client change is required at all; the ``staged `` field lets a publisher opt in per upload with only a
632643minimal change to existing tooling.
633644
634645Because the legacy API uploads a single file per request, subsequent legacy uploads for the same project and
635646version **SHOULD ** be added to the same open session when that session was itself created through a legacy
636- upload, so that the release is still published as a unit.
647+ upload, so that the release is still published as a unit. Once such a session exists, a subsequent legacy
648+ upload for that project and version is added to it whether or not it carries the ``staged `` field: the field
649+ is not required again, and the index **MUST NOT ** reject the upload for omitting it. The field is still
650+ permitted on these uploads as an explicit statement of intent.
637651
638652A session's creation path is fixed when it is created, and the two paths **MUST NOT ** be mixed.
639653If a non-terminal session already exists for a name-version pair (see :ref: `publishing-session-multiple `), a request to
640- contribute to it through the *other * path **MUST ** be rejected with a ``409 Conflict ``: a legacy
641- `` staged=true `` upload for a pair that already has an open session created through the Upload 2.0 API is
642- rejected , and an Upload 2.0 request that would add to a session created through a legacy upload is likewise
643- rejected.
654+ contribute to it through the *other * path **MUST ** be rejected with a ``409 Conflict ``: any legacy upload for
655+ a pair that already has an open session created through the Upload 2.0 API is rejected (whether or not it
656+ carries the `` staged `` field) , and an Upload 2.0 request that would add to a session created through a legacy
657+ upload is likewise rejected.
644658
645659Fixing the path at creation avoids ambiguity in how the session reaches a terminal state. An Upload 2.0
646660session is driven to completion by a client that holds its ``session-token `` and calls the control plane
@@ -826,6 +840,15 @@ change: deferred processing resolves to either ``published`` on success or ``err
826840resolves to ``error ``, the session remains editable and the reason is reported in the session's ``notices ``,
827841as described in :ref: `publishing-session-states `.
828842
843+ Reviewing an upload can produce two kinds of result, surfaced at different points. Correctness problems that
844+ can be decided about a single file in isolation, such as its size, checksum, metadata, or compatibility tags,
845+ are surfaced when that :ref: `file upload session <file-upload-session-states >` completes, moving the file to
846+ the ``error `` state, from which it cannot be repaired in place. Results that concern whether the release may
847+ become public, such as malware detection, instead gate the publish step and are described below. An index
848+ **MAY ** run malware review per file, as each file is uploaded, as well as (or instead of) at publication; a
849+ file remains uploadable to a stage for some time before the release is published, so reviewing each file as it
850+ arrives can shorten the window in which an unreviewed file sits in a stage.
851+
829852The ``processing `` state **MAY ** be used to run asynchronous review of a session's files before it is
830853published, such as malware scanning. If the review completes without an adverse result, the session resolves
831854to ``published `` as normal. If the review does not complete within a period chosen by the index, the server
@@ -849,7 +872,9 @@ change is left to the index: it might run to completion, rescan only the changed
849872Because editing a session can trigger re-review, an index **SHOULD ** guard against abuse of the review system.
850873For example, a client might repeatedly upload a file it knows will be flagged, delete it, and re-upload it to
851874force repeated scans. Mitigations such as rate-limiting reviews, capping the number of review attempts for a
852- session, or moving a persistently adverse session to a terminal state are left to the index.
875+ session, or moving a persistently adverse session to a terminal state are left to the index. An index can
876+ also avoid this class of abuse entirely by performing the review only at publication time, when the session's
877+ files are fixed for the duration of the publish and cannot be swapped out to force another scan.
853878
854879A publish attempt that fails *synchronously * (i.e. within the publish request itself) is returned to the
855880client as an :ref: `error response <session-errors >` and leaves the session in its current editable state; it
@@ -1955,16 +1980,21 @@ as experience is gained operating Upload 2.0.
19551980Change History
19561981==============
19571982
1958- * `01-Aug -2026 <https://discuss.python.org/t/pre-pep-staged-releases-separated-from-pep-694/107804/58 >`__
1983+ * `01-Oct -2026 <https://discuss.python.org/t/pre-pep-staged-releases-separated-from-pep-694/107804/58 >`__
19591984
19601985 * Add :ref: `Legacy Upload API Interoperability <legacy-interop >`, allowing a legacy upload to create a
19611986 publishing session (via a ``staged `` field, or by index or project configuration that an upload client
19621987 cannot bypass) so that staging is usable before upload tooling adopts this API, and allowing an index to
1963- publish such a session itself once automated processing resolves or its window elapses.
1988+ publish such a session itself once automated processing resolves or its window elapses. Require that an
1989+ index supporting this accept the ``staged `` field and return the session creation response body, specify
1990+ that subsequent legacy uploads to an open stage need not repeat the field, and forbid mixing the two
1991+ session-creation paths.
19641992 * Note that the ``processing `` state **MAY ** be used for asynchronous review such as malware scanning, with
1965- an index-chosen window after which the review is treated as having produced no adverse result.
1993+ an index-chosen window after which the review is treated as having produced no adverse result. Distinguish
1994+ per-file correctness results from release-level review, allow review per file or at publication, and note
1995+ that reviewing at publication time avoids re-scan abuse.
19661996 * Allow an index to require additional authorization, beyond upload permission, to publish or cancel a
1967- session, so that duties can be separated.
1997+ session, so that duties can be separated, optionally exposing that choice to project owners .
19681998
19691999* `29-Jul-2026 <https://discuss.python.org/t/pep-694-pypi-upload-api-2-0-round-4/108320 >`__
19702000
0 commit comments