slsa.provenance
===============

.. py:module:: slsa.provenance

.. autoapi-nested-parse::

   SLSA provenance package.

   Implementing https://slsa.dev/spec/v1.0/provenance.

   Purpose
   =======
   Describe how an artifact or set of artifacts was produced so that:

   - Consumers of the provenance can verify that the artifact was built according
     to expectations.
   - Others can rebuild the artifact, if desired.

   This predicate is the *RECOMMENDED* way to satisfy the
   `SLSA v1.0 provenance requirements
   <https://slsa.dev/spec/v1.0/requirements#provenance-generation>`_.

   .. _SLSA: https://slsa.dev

   .. |ResourceDescriptor| replace:: :class:`ResourceDescriptor`
   .. |ResourceURI| replace:: :class:`ResourceURI`
   .. |SLSA| replace:: `SLSA`_
   .. |bool| replace:: :class:`bool`
   .. |bytes| replace:: :class:`bytes`
   .. |datetime| replace:: :class:`~datetime.datetime`
   .. |dict| replace:: :class:`dict`
   .. |json.dumps| replace:: :func:`json.dumps()`
   .. |json.load| replace:: :func:`json.load()`
   .. |str| replace:: :class:`str`



Classes
-------

.. autoapisummary::

   slsa.provenance.Builder
   slsa.provenance.BuildMetadata
   slsa.provenance.Statement
   slsa.provenance.ResourceDescriptor
   slsa.provenance.Predicate
   slsa.provenance.TypeURI
   slsa.provenance.ResourceURI


Module Contents
---------------

.. py:class:: Builder(build_id: TypeURI | str, builder_dependencies: list[ResourceDescriptor], version: dict[str, str])

   Bases: :py:obj:`object`


   Predicate run details builder object.

   The build platform, or builder for short, represents the transitive closure
   of all the entities that are, by necessity,
   `trusted <https://slsa.dev/spec/v1.0/principles#trust-systems-verify-artifacts>`_
   to faithfully run the build and record the provenance.

   This includes not only the software but the hardware and people involved in
   running the service.

   For example, a particular instance of `Tekton <https://tekton.dev/>`_ could
   be a build platform, while Tekton itself is not. For more info, see
   `Build model <https://slsa.dev/spec/v1.0/terminology#build-model>`_.

   The |id| **MUST** reflect the trust base that consumers care about. How
   detailed to be is a judgement call. For example, GitHub Actions supports
   both GitHub-hosted runners and self-hosted runners. The GitHub-hosted runner
   might be a single identity because it’s all GitHub from the consumer’s
   perspective. Meanwhile, each self-hosted runner might have its own identity
   because not all runners are trusted by all consumers.

   Consumers MUST accept only specific signer-builder pairs. For example,
   ``GitHub`` can sign provenance for the ``GitHub Actions`` builder, and
   ``Google`` can sign provenance for the ``Google Cloud Build`` builder, but
   ``GitHub`` cannot sign for the ``Google Cloud Build`` builder.

   Design rationale
   ----------------
   The builder is distinct from the signer in order to support the case where
   one signer generates attestations for more than one builder, as in the
   ``GitHub Actions`` example above. The field is **REQUIRED**, even if it is
   implicit from the signer, to aid readability and debugging.

   It is an object to allow additional fields in the future, in case one URI is
   not sufficient.

   .. |builder.as_dict| replace:: :meth:`~Builder.as_dict`
   .. |builder.as_json| replace:: :meth:`~Builder.as_json`
   .. |id| replace:: :attr:`~Builder.id`
   .. |builder.load_dict| replace:: :meth:`~Builder.load_dict`
   .. |builder.load_json| replace:: :meth:`~Builder.load_json`


   .. py:attribute:: ATTR_BUILD_ID
      :type:  str
      :value: 'id'



   .. py:attribute:: ATTR_BUILDER_DEPENDENCIES
      :type:  str
      :value: 'builderDependencies'



   .. py:attribute:: ATTR_VERSION
      :type:  str
      :value: 'version'



   .. py:attribute:: __id
      :type:  TypeURI


   .. py:attribute:: __dependencies
      :type:  list[ResourceDescriptor]


   .. py:attribute:: __version
      :type:  dict[str, str]


   .. py:method:: __eq__(other: object) -> bool

      Check if this builder object is equal to *other*.

      :param other: The builder object to compare this with.

      :return: A |bool| set to **True** if both builders are equal, **False**
          else.



   .. py:property:: builder_dependencies
      :type: list[ResourceDescriptor]


      Builder dependencies.

      Dependencies used by the orchestrator that are not run within the
      workload and that do not affect the build, but might affect the
      provenance generation or security guarantees.



   .. py:property:: id
      :type: TypeURI


      Build platform ID.

      URI indicating the transitive closure of the trusted build platform.
      This is intended to be the sole determiner of the SLSA Build level.

      If a build platform has multiple modes of operations that have differing
      security attributes or SLSA Build levels, each mode **MUST** have a
      different |id| and **SHOULD** have a different signer identity. This is
      to minimize the risk that a less secure mode compromises a more secure
      one.

      The |id| URI **SHOULD** resolve to documentation explaining:

      - The scope of what this ID represents.
      - The claimed SLSA Build level.
      - The accuracy and completeness guarantees of the fields in the
        provenance.
      - Any fields that are generated by the tenant-controlled build process
        and not verified by the trusted control plane, except for the
        ``subject``.
      - The interpretation of any extension fields.




   .. py:property:: version
      :type: dict[str, str]


      Builder version mapping.

      Map of names of components of the build platform to their version.



   .. py:method:: as_dict() -> dict

      Get the dictionary representation of this builder.

      :return: The dictionary representation of this builder. This should
          be a valid JSON object (call to |json.load| succeeds).

      .. seealso:: |json.load|



   .. py:method:: as_json() -> str

      Get the representation of this builder as a JSON string.

      The dictionary representing this builder (as returned by
      |builder.as_dict|) is turned into a JSON string using |json.dumps| with
      *sort_keys* set to **True**.

      .. seealso:: |builder.as_dict|, |builder.load_json|



   .. py:method:: load_dict(initializer: dict[str, Any]) -> Builder
      :classmethod:


      Initialize a builder from a dictionary.

      :param initializer: The dictionary to initialize this builder with.

      :return: A builder object created from the input *initializer*.

      :raise ValueError: if the build ID is not defined in *initializer*.

      .. seealso:: |builder.as_dict|



   .. py:method:: load_json(initializer: str) -> Builder
      :classmethod:


      Initialize a builder from a JSON string.

      :param initializer: The JSON string to initialize this builder with.

      :return: A builder object created from the input *initializer*.

      :raise ValueError: if the build ID is not defined in *initializer*.

      .. seealso:: |builder.as_json|, |builder.load_dict|



.. py:class:: BuildMetadata(invocation_id: str, started_on: datetime.datetime, finished_on: datetime.datetime)

   Bases: :py:obj:`object`


   Build metadata representation.

   When the timestamp parameters (*started_on* or *finished_on*) are strings,
   the |TIMESTAMP_FORMAT| format is used to convert them to |datetime| classes
   using |strptime|.

   :param invocation_id: Identifier of this particular build invocation.
   :param started_on: The timestamp of this build invocation start time.
   :param finished_on: The timestamp of this build invocation finish time.

   .. |TIMESTAMP_FORMAT| replace:: :attr:`TIMESTAMP_FORMAT`
   .. |bm.as_dict| replace:: :meth:`~BuildMetadata.as_dict`
   .. |bm.as_json| replace:: :meth:`~BuildMetadata.as_json`
   .. |bm.load_dict| replace:: :meth:`~BuildMetadata.load_dict`
   .. |bm.load_json| replace:: :meth:`~BuildMetadata.load_json`
   .. |strptime| replace:: :meth:`~datetime.strptime`


   .. py:attribute:: ATTR_INVOCATION_ID
      :type:  str
      :value: 'invocationId'



   .. py:attribute:: ATTR_STARTED_ON
      :type:  str
      :value: 'startedOn'



   .. py:attribute:: ATTR_FINISHED_ON
      :type:  str
      :value: 'finishedOn'



   .. py:attribute:: TIMESTAMP_FORMAT
      :type:  str
      :value: '%Y-%m-%dT%H:%M:%SZ'


      Timestamp format used to read/write |datetime| structures to strings.



   .. py:attribute:: __invocation_id
      :type:  str


   .. py:attribute:: __started_on
      :type:  datetime.datetime


   .. py:attribute:: __finished_on
      :type:  datetime.datetime


   .. py:method:: __eq__(other: object) -> bool

      Check if this build metadata object is equal to *other*.

      :param other: The build metadata object to compare this with.

      :return: A |bool| set to **True** if both build metadatas are equal,
          **False** else.



   .. py:property:: finished_on
      :type: datetime.datetime


      The timestamp of when the build completed.



   .. py:property:: invocation_id
      :type: str


      Build invocation identifier.

      Identifies this particular build invocation, which can be useful for
      finding associated logs or other ad-hoc analysis. The exact meaning and
      format is defined by |builder.id|; by default it is treated as opaque
      and case-sensitive.

      The value **SHOULD** be globally unique.

      .. |builder.id| replace:: :attr:`Builder.id`



   .. py:property:: started_on
      :type: datetime.datetime


      The timestamp of when the build started.



   .. py:method:: as_dict() -> dict

      Get the dictionary representation of this build metadata.

      :return: The dictionary representation of this build metadata. This
          should be a valid JSON object (call to |json.load| succeeds).

      .. seealso:: |bm.as_json|, |json.load|, |bm.load_dict|



   .. py:method:: as_json() -> str

      Get the representation of this build metadata as a JSON string.

      The dictionary representing this build metadata (as returned by
      |bm.as_dict|) is turned into a JSON string using |json.dumps| with
      *sort_keys* set to **True**.

      .. seealso:: |bm.as_dict|, |bm.load_json|



   .. py:method:: load_dict(initializer: dict[str, Any]) -> BuildMetadata
      :classmethod:


      Initialize a build metadata from a dictionary.

      :param initializer: The dictionary to initialize this build metadata
          with.

      :return: A build metadata object created from the input *initializer*.

      :raise ValueError: if the invocation ID is not defined in *initializer*,
          or if the timestamps are invalid.
      :raise TypeError: if the timestamps types are invalid.

      .. seealso:: |bm.as_dict|, |bm.load_json|



   .. py:method:: load_json(initializer: str) -> BuildMetadata
      :classmethod:


      Initialize a build metadata from a JSON string.

      :param initializer: The JSON string to initialize this build metadata
          with.

      :return: A build metadata object created from the input *initializer*.

      :raise ValueError: if the build ID is not defined in *initializer*.

      .. seealso:: |builder.as_json|, |builder.load_dict|



   .. py:method:: __validate_timestamp(timestamp: datetime.datetime) -> datetime.datetime
      :staticmethod:


      Validate a timestamp.



.. py:class:: Statement(statement_type: TypeURI | str, subject: list[ResourceDescriptor], predicate_type: TypeURI | str = PREDICATE_TYPE_VALUE, predicate: Predicate | None = None)

   Bases: :py:obj:`object`


   SLSA statement object.

   The Statement is the middle layer of the attestation, binding it to a
   particular subject and unambiguously identifying the types of the
   |Predicate|.

   :param statement_type: The URI identifier for the schema of the Statement.
   :param subject: Set of software artifacts that the attestation applies to.
   :param predicate_type: URI identifying the type of *predicate*.
   :param predicate: Additional parameters of the |Predicate|.

   .. |Predicate| replace:: :class:`Predicate`
   .. |predicateType| replace:: :attr:`~Predicate.predicatType`
   .. |st.as_dict| replace:: :meth:`~Statement.as_dict`
   .. |st.as_json| replace:: :meth:`~Statement.as_json`
   .. |st.load_dict| replace:: :meth:`~Statement.load_dict`
   .. |st.load_json| replace:: :meth:`~Statement.load_json`


   .. py:attribute:: ATTR_PREDICATE
      :type:  str
      :value: 'predicate'



   .. py:attribute:: ATTR_PREDICATE_TYPE
      :type:  str
      :value: 'predicateType'



   .. py:attribute:: ATTR_SUBJECT
      :type:  str
      :value: 'subject'



   .. py:attribute:: ATTR_TYPE
      :type:  str
      :value: '_type'



   .. py:attribute:: SCHEMA_TYPE_VALUE
      :type:  str
      :value: 'https://in-toto.io/Statement/v1'



   .. py:attribute:: PREDICATE_TYPE_VALUE
      :type:  str
      :value: 'https://slsa.dev/provenance/v1'



   .. py:attribute:: __type
      :type:  TypeURI


   .. py:attribute:: __subject
      :type:  list[ResourceDescriptor]


   .. py:attribute:: __predicate_type
      :type:  TypeURI


   .. py:attribute:: __predicate
      :type:  Predicate | None
      :value: None



   .. py:method:: __eq__(other: object) -> bool

      Check if this statement is equal to *other*.

      :param other: The statement object to compare this with.

      :return: A |bool| set to **True** if both statements are equal,
          **False** else.



   .. py:property:: type
      :type: TypeURI


      Identifier for the schema of the Statement.

      Always https://in-toto.io/Statement/v1 for this version of the spec.



   .. py:property:: predicate
      :type: Predicate | None


      Additional parameters of the |Predicate|.

      Unset is treated the same as set-but-empty. **MAY** be omitted if
      |predicateType| fully describes the predicate.



   .. py:property:: predicate_type
      :type: TypeURI


      URI identifying the type of the |Predicate|.



   .. py:property:: subject
      :type: list[ResourceDescriptor]


      Set of software artifacts that the attestation applies to.

      Each element represents a single software artifact. Each element
      **MUST** have digest set.

      The name field may be used as an identifier to distinguish this artifact
      from others within the subject. Similarly, other |ResourceDescriptor|
      fields may be used as required by the context. The semantics are up to
      the producer and consumer and they **MAY** use them when evaluating
      policy.

      If the name is not meaningful, leave the field unset or use ``_``. For
      example, a SLSA Provenance attestation might use the name to specify
      output filename, expecting the consumer to only consider entries with a
      particular name. Alternatively, a vulnerability scan attestation might
      leave name unset because the results apply regardless of what the
      artifact is named.

      If set, name and uri **SHOULD** be unique within subject.

      .. warning:: Subject artifacts are matched purely by digest, regardless
                   of content type. If this matters to you, please comment on
                   `GitHub Issue #28
                   <https://github.com/in-toto/attestation/issues/28>`_



   .. py:method:: as_dict() -> dict

      Get the dictionary representation of this statement.

      :return: The dictionary representation of this statement. This should
          be a valid JSON object (call to |json.load| succeeds).

      .. seealso:: |json.load|



   .. py:method:: as_json() -> str

      Get the representation of this statement as a JSON string.

      The dictionary representing this statement (as returned by
      |st.as_dict|) is turned into a JSON string using |json.dumps| with
      *sort_keys* set to **True**.

      .. seealso:: |st.as_dict|, |st.load_json|



   .. py:method:: load_dict(initializer: dict[str, Any]) -> Statement
      :classmethod:


      Initialize a statement from a dictionary.

      :param initializer: The dictionary to initialize this statement with.

      :return: A statement object created from the input *initializer*.

      :raise ValueError: if the statement type is not defined in
          *initializer*.

      .. seealso:: |st.as_dict|, |st.load_json|



   .. py:method:: load_json(initializer: str) -> Statement
      :classmethod:


      Initialize a statement from a JSON string.

      :param initializer: The JSON string to initialize this statement with.

      :return: A statement object created from the input *initializer*.

      :raise ValueError: if the statement type is not defined in
          *initializer*.

      .. seealso:: |st.as_json|, |st.load_dict|



.. py:class:: ResourceDescriptor(uri: ResourceURI | str | None = None, digest: dict[str, str] | None = None, name: str | None = None, download_location: ResourceURI | str | None = None, media_type: str | None = None, content: bytes | None = None, resource_annotations: dict[str, Any] | None = None)

   Bases: :py:obj:`object`


   Resource descriptor object.

   A size-efficient description of any software artifact or resource (mutable
   or immutable).

   Though all fields are optional, a |ResourceDescriptor| **MUST** specify one
   of |uri|, |digest| or |content| at a minimum.

   Further, a context that uses the |ResourceDescriptor| can require one
   or more fields. For example, a predicate **MAY** require the name and digest
   fields.

   :param uri: see |uri|
   :param digest: see |digest|
   :param name: see |name|
   :param download_location: see |download_location|
   :param media_type: see |media_type|
   :param content: see |content|
   :param resource_annotations: see |annotations|

   .. note:: Those requirements cannot override the minimum requirement of one
             of |uri|, |digest|, or |content| specified here.

   .. |annotations| replace:: :attr:`~ResourceDescriptor.annotations`
   .. |rd.as_dict| replace:: :meth:`~ResourceDescriptor.as_dict`
   .. |rd.as_json| replace:: :meth:`~ResourceDescriptor.as_json`
   .. |content| replace:: :attr:`~ResourceDescriptor.content`
   .. |digest| replace:: :attr:`~ResourceDescriptor.digest`
   .. |download_location| replace::
       :attr:`~ResourceDescriptor.download_location`
   .. |is_valid| replace:: :attr:`~ResourceDescriptor.is_valid`
   .. |rd.load_dict| replace:: :meth:`~ResourceDescriptor.load_dict`
   .. |rd.load_json| replace:: :meth:`~ResourceDescriptor.load_json`
   .. |media_type| replace:: :attr:`~ResourceDescriptor.media_type`
   .. |name| replace:: :attr:`~ResourceDescriptor.name`
   .. |uri| replace:: :attr:`~ResourceDescriptor.uri`


   .. py:attribute:: ATTR_ANNOTATIONS
      :type:  str
      :value: 'annotations'



   .. py:attribute:: ATTR_CONTENT
      :type:  str
      :value: 'content'



   .. py:attribute:: ATTR_DIGEST
      :type:  str
      :value: 'digest'



   .. py:attribute:: ATTR_DOWNLOAD_LOCATION
      :type:  str
      :value: 'downloadLocation'



   .. py:attribute:: ATTR_MEDIA_TYPE
      :type:  str
      :value: 'mediaType'



   .. py:attribute:: ATTR_NAME
      :type:  str
      :value: 'name'



   .. py:attribute:: ATTR_URI
      :type:  str
      :value: 'uri'



   .. py:attribute:: ATTRIBUTES
      :type:  tuple

      JSON attributes returned by the |rd.as_dict| method (if the given attribute
      defines a value).



   .. py:attribute:: __annotations
      :type:  dict[str, Any]


   .. py:attribute:: __content
      :type:  bytes | None
      :value: None



   .. py:attribute:: __digest
      :type:  dict[str, str]


   .. py:attribute:: __download_location
      :type:  ResourceURI | None
      :value: None



   .. py:attribute:: __media_type
      :type:  str | None
      :value: None



   .. py:attribute:: __name
      :type:  str | None
      :value: None



   .. py:attribute:: __uri
      :type:  ResourceURI | None
      :value: None



   .. py:method:: __eq__(other: object) -> bool

      Check if this resource descriptor is equal to *other*.

      :param other: The resource descriptor object to compare this with.

      :return: A |bool| set to **True** if both resource descriptors are
          equal, **False** else.



   .. py:property:: annotations
      :type: dict[str, Any]


      Resource descriptor additional information.

      This field MAY be used to provide additional information or metadata
      about the resource or artifact that may be useful to the consumer when
      evaluating the attestation against a policy.

      For maximum flexibility annotations may be any mapping from a field name
      to any JSON value (string, number, object, array, boolean or null).

      The producer and consumer **SHOULD** agree on the semantics, and
      acceptable fields and values in the annotations map.

      Producers **SHOULD** follow the same naming conventions for annotation
      fields as for extension fields.



   .. py:property:: content
      :type: bytes | None


      The contents of the resource or artifact.

      This field is **REQUIRED** unless either |uri| or |digest| is set.

      The producer **MAY** use this field in scenarios where including the
      contents of the resource/artifact directly in the attestation is deemed
      more efficient for consumers than providing a pointer to another
      location.

      To maintain size efficiency, the size of content **SHOULD** be less than
      1KB.

      The semantics are up to the producer and consumer. The |uri| or
      |media_type| **MAY** be used by the producer as hints for how consumers
      should parse content.

      :raise TypeError: When setting this field to something else than a
          |bytes| or ``None``.



   .. py:property:: digest
      :type: dict[str, str]


      A set of digests for this resource descriptor.

      A set of cryptographic digests of the contents of the resource or
      artifact.

      This field is **REQUIRED** unless either |uri|, or |content| is set.

      When known, the producer **SHOULD** set this field to denote an
      immutable artifact or resource.

      The producer and consumer **SHOULD** agree on acceptable algorithms.

      :raise TypeError: When setting this field to something else than a
          |dict|.



   .. py:property:: download_location
      :type: ResourceURI | None


      Artifact download location.

      The location of the described resource or artifact, if different from
      the |uri|.

      To enable automated downloads by consumers, the specified location
      **SHOULD** be resolvable.

      :raise TypeError: When setting this field to something else than a
          |ResourceURI| or ``None``.



   .. py:property:: is_valid
      :type: bool


      Check if this resource descriptor is valid.

      To be valid, a resource descriptor should define at least one of the
      following:

      - |content|
      - |digest|
      - |uri|

      :return: A |bool| set to **True** if at least one of the above-mentioned
          field is defined, **False** else.



   .. py:property:: name
      :type: str | None


      Machine-readable identifier for distinguishing between descriptors.

      The semantics are up to the producer and consumer. The |name| name
      **SHOULD** be stable, such as a filename, to allow consumers to reliably
      use the |name| as part of their policy.



   .. py:property:: media_type
      :type: str | None


      This resource descriptor media type.

      The `MIME Type
      <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types>`_
      (i.e., media type) of the described resource or artifact.

      For resources or artifacts that do not have a standardized MIME type,
      producers **SHOULD** follow `RFC 6838 (Sections 3.2-3.4)
      <https://www.rfc-editor.org/rfc/rfc6838.html#section-3.2>`_ conventions
      of prefixing types with ``x.``, ``prs.``, or ``vnd.`` to avoid
      collisions with other producers.



   .. py:property:: uri
      :type: ResourceURI | None


      A URI used to identify the resource or artifact globally.

      This field is **REQUIRED** unless either digest or content is set.



   .. py:method:: add_digest(algorithm: str, digest: str) -> None

      Add a new digest to the digest set.

      :param algorithm: The algorithm the new digest has been computed with.
      :param digest: The new digest to add to the digest set.

      :raise KeyError: if *algorithm* already defines a digest in the current
          digest set.



   .. py:method:: as_dict() -> dict

      Get the dictionary representation of this resource descriptor.

      :return: The dictionary representation of this resource descriptor.
          This should be a valid JSON object.

      :raise ValueError: If this resource descriptor is not valid (see
          |is_valid|).

      .. seealso:: |rd.as_json|, |is_valid|, |rd.load_dict|



   .. py:method:: as_json() -> str

      Get the representation of this resource descriptor as a JSON string.

      The dictionary representing this resource descriptor (as returned by
      |rd.as_dict|) is turned into a JSON string using |json.dumps| with
      *sort_keys* set to **True**.

      .. seealso:: |rd.as_dict|, |rd.load_json|



   .. py:method:: dir_hash(path: pathlib.Path, algorithm: str) -> str
      :staticmethod:


      Directory hash.

      The `directory Hash1
      <https://cs.opensource.google/go/x/mod/+/refs/tags/v0.5.0:sumdb/dirhash/hash.go>`_
      function, omitting the ``h1:`` prefix and output in lowercase
      hexadecimal instead of base64.

      This algorithm was designed for go modules but can be used to digest the
      contents of an arbitrary archive or file tree.

      Equivalent to extracting the archive to an empty directory and running
      the following command in that directory::

          find . -type f | cut -c3- | LC_ALL=C sort | xargs -r sha256sum \\
                         | sha256sum | cut -f1 -d' '

      For example, the module dirhash
      ``h1:Khu2En+0gcYPZ2kuIihfswbzxv/mIHXgzPZ018Oty48=`` would be encoded as
      ``{"dirHash1":
      "2a1bb6127fb481c60f67692e22285fb306f3c6ffe62075e0ccf674d7c3adcb8f"}``.



   .. py:method:: load_dict(initializer: dict[str, Any]) -> ResourceDescriptor
      :classmethod:


      Initialize a resource descriptor from a dictionary.

      :param initializer: The dictionary to initialize this resource
          descriptor with.

      :return: A resource descriptor object created from the input
          *initializer*.

      :raise ValueError: If this resource descriptor is not valid once
          initialized with the dictionary content (see |is_valid|).

      .. seealso:: |rd.as_dict|, |is_valid|



   .. py:method:: load_json(initializer: str) -> ResourceDescriptor
      :classmethod:


      Initialize a resource descriptor from a JSON string.

      :param initializer: The JSON string to initialize this resource
          descriptor with.

      :return: A resource descriptor object created from the input
          *initializer*.

      :raise ValueError: If this resource descriptor is not valid once
          initialized with the dictionary content (see |is_valid|).

      .. seealso:: |rd.load_dict|, |is_valid|



.. py:class:: Predicate(build_definition: Predicate, run_details: Predicate)

   Bases: :py:obj:`object`


   Predicate object.

   .. |p.as_dict| replace:: :meth:`as_dict`
   .. |p.as_json| replace:: :meth:`as_json`
   .. |p.load_dict| replace:: :meth:`load_dict`
   .. |p.load_json| replace:: :meth:`load_json`


   .. py:attribute:: ATTR_BUILD_DEFINITION
      :type:  str
      :value: 'buildDefinition'



   .. py:attribute:: ATTR_RUN_DETAILS
      :type:  str
      :value: 'runDetails'



   .. py:class:: BuildDefinition(build_type: TypeURI | str, external_parameters: object, internal_parameters: object, resolved_dependencies: list[ResourceDescriptor])

      Bases: :py:obj:`object`


      The BuildDefinition describes all the inputs to the build.

      It **SHOULD** contain all the information necessary and sufficient to
      initialize the build and begin execution.

      The |externalParameters| and |internalParameters| are the top-level
      inputs to the template, meaning inputs not derived from another input.
      Each is an arbitrary JSON object, though it is **RECOMMENDED** to keep
      the structure simple with string values to aid verification.

      The same field name **SHOULD NOT** be used for both |externalParameters|
      and |internalParameters|.

      The parameters **SHOULD** only contain the actual values passed in
      through the interface to the build platform.

      Metadata about those parameter values, particularly digests of
      artifacts referenced by those parameters, **SHOULD** instead go in
      |resolvedDependencies|.

      The documentation for |buildType| **SHOULD** explain how to convert
      from a parameter to the dependency uri. For example::

          "externalParameters": {
              "repository": "https://github.com/octocat/hello-world",
              "ref": "refs/heads/main"
          },
          "resolvedDependencies": [{
              "uri": "git+https://github.com/octocat/hello-world@refs/heads/main",
              "digest": {"gitCommit": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"}
          }]


      Guidelines
      ----------

      - Maximize the amount of information that is implicit from the meaning
        of |buildType|. In particular, any value that is boilerplate and the
        same for every build **SHOULD** be implicit.
      - Reduce parameters by moving configuration to input artifacts whenever
        possible. For example, instead of passing in compiler flags via an
        external parameter that has to be verified separately, require the
        flags to live next to the source code or build configuration so that
        verifying the latter automatically verifies the compiler flags.
      - In some cases, additional external parameters might exist that do not
        impact the behavior of the build, such as a deadline or priority.
        These extra parameters **SHOULD** be excluded from the provenance
        after careful analysis that they indeed pose no security impact.
      - If possible, architect the build platform to use this definition as
        its sole top-level input, in order to guarantee that the information
        is sufficient to run the build.
      - When build configuration is evaluated client-side before being sent
        to the server, such as transforming version-controlled YAML into
        ephemeral JSON, some solution is needed to make verification
        practical. Consumers need a way to know what configuration is
        expected and the usual way to do that is to map it back to version
        control, but that is not possible if the server cannot verify the
        configuration’s origins. Possible solutions:

          - (**RECOMMENDED**) Rearchitect the build platform to read
            configuration directly from version control, recording the
            server-verified URI in |externalParameters| and the digest in
            |resolvedDependencies|.
          - Record the digest in the provenance and use a separate provenance
            attestation to link that digest back to version control. In this
            solution, the client-side evaluation is considered a separate
            *build* that **SHOULD** be independently secured using |SLSA|,
            though securing it can be difficult since it usually runs on an
            untrusted workstation.

      - The purpose of |resolvedDependencies| is to facilitate recursive
        analysis of the software supply chain. Where practical, it is
        valuable to record the URI and digest of artifacts that, if
        compromised, could impact the build. At |SLSA| Build L3, completeness
        is considered *best effort*.

      .. |buildType| replace:: :attr:`build_type`
      .. |bd.as_dict| replace:: :meth:`as_dict`
      .. |bd.as_json| replace:: :meth:`as_json`
      .. |externalParameters| replace:: :attr:`external_parameters`
      .. |internalParameters| replace:: :attr:`internal_parameters`
      .. |bd.load_dict| replace:: :meth:`load_dict`
      .. |bd.load_json| replace:: :meth:`load_json`
      .. |resolvedDependencies| replace:: :attr:`resolved_dependencies`


      .. py:attribute:: ATTR_BUILD_TYPE
         :type:  str
         :value: 'buildType'



      .. py:attribute:: ATTR_EXTERNAL_PARAMETERS
         :type:  str
         :value: 'externalParameters'



      .. py:attribute:: ATTR_INTERNAL_PARAMETERS
         :type:  str
         :value: 'internalParameters'



      .. py:attribute:: ATTR_RESOLVED_DEPENDENCIES
         :type:  str
         :value: 'resolvedDependencies'



      .. py:attribute:: __build_type
         :type:  TypeURI


      .. py:attribute:: __external_parameters
         :type:  object


      .. py:attribute:: __internal_parameters
         :type:  object


      .. py:attribute:: __resolved_dependencies
         :type:  list[ResourceDescriptor]


      .. py:method:: __eq__(other: object) -> bool

         Check if this build definition is equal to *other*.

         :param other: The build definition object to compare this with.

         :return: A |bool| set to **True** if both build definitions are
             equal, **False** else.



      .. py:property:: build_type
         :type: TypeURI | None


         Predicate build type.

         Identifies the template for how to perform the build and interpret
         the parameters and dependencies.

         The URI **SHOULD** resolve to a human-readable specification that
         includes:

         - overall description of the build type
         - schema for externalParameters and internalParameters
         - unambiguous instructions for how to initiate the build given this
           BuildDefinition, and a complete example.

         Example
         -------
         ::

             https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1



      .. py:property:: external_parameters
         :type: object | None


         The parameters that are under external control.

         Such as those set by a user or tenant of the build platform.
         They **MUST** be complete at SLSA Build L3, meaning that there is no
         additional mechanism for an external party to influence the build.
         (At lower SLSA Build levels, the completeness **MAY** be best
         effort.)

         The build platform **SHOULD** be designed to minimize the size and
         complexity of ``externalParameters``, in order to reduce fragility
         and ease verification.

         Consumers **SHOULD** have an expectation of what **good** looks
         like;  the more information that they need to check, the harder that
         task becomes.

         Verifiers **SHOULD** reject unrecognized or unexpected fields within
         ``externalParameters``.



      .. py:property:: internal_parameters
         :type: object | None


         Internal parameters.

         The parameters that are under the control of the entity represented
         by ``builder.id``.

         The primary intention of this field is for debugging, incident
         response, and vulnerability management.

         The values here **MAY** be necessary for reproducing the build.

         There is no need to verify these parameters because the build
         platform is already trusted, and in many cases it is not practical
         to do so.



      .. py:property:: resolved_dependencies
         :type: list[ResourceDescriptor]


         Unordered collection of artifacts needed at build time.

         Completeness is best effort, at least through SLSA Build L3.

         For example, if the build script fetches and executes
         ``example.com/foo.sh``, which in turn fetches
         ``example.com/bar.tar.gz``, then both ``foo.sh`` and ``bar.tar.gz``
         **SHOULD** be listed here.



      .. py:method:: as_dict() -> dict

         Get the dictionary representation of this build definition.

         :return: The dictionary representation of this build definition.
             This should be a valid JSON object (call to |json.load|
             succeeds).

         .. seealso:: |bd.as_json|, |json.load|, |bd.load_dict|



      .. py:method:: as_json() -> str

         Get the representation of this build definition as a JSON string.

         The dictionary representing this build defintion (as returned by
         |bd.as_dict|) is turned into a JSON string using |json.dumps| with
         *sort_keys* set to **True**.

         .. seealso:: |bd.as_dict|, |bd.load_json|



      .. py:method:: load_dict(initializer: dict[str, Any]) -> Predicate
         :classmethod:


         Initialize a build definition from a dictionary.

         :param initializer: The dictionary to initialize this build
             definition with.

         :return: A build definition object created from the input
             *initializer*.

         .. seealso:: |bd.as_dict|, |bd.load_json|



      .. py:method:: load_json(initializer: str) -> Predicate
         :classmethod:


         Initialize a build definition from a JSON string.

         :param initializer: The JSON string to initialize this build
             definition with.

         :return: A build definition object created from the input
             *initializer*.

         .. seealso:: |bd.as_json|, |bd.load_dict|




   .. py:class:: RunDetails(builder: Builder, metadata: BuildMetadata, by_products: list[ResourceDescriptor])

      Bases: :py:obj:`object`


      Details specific to this particular execution of the build.

      :param builder: Run details builder description.
      :param metadata: The metadata for this run details object.
      :param by_products: Run details additional artifacts.

      .. |rund.as_dict| replace:: :meth:`as_dict`
      .. |rund.as_json| replace:: :meth:`as_json`
      .. |rund.load_dict| replace:: :meth:`load_dict`
      .. |rund.load_json| replace:: :meth:`load_json`


      .. py:attribute:: ATTR_BUILDER
         :type:  str
         :value: 'builder'



      .. py:attribute:: ATTR_METADATA
         :type:  str
         :value: 'metadata'



      .. py:attribute:: ATTR_BY_PRODUCTS
         :type:  str
         :value: 'byproducts'



      .. py:attribute:: __builder
         :type:  Builder


      .. py:attribute:: __metadata
         :type:  BuildMetadata


      .. py:attribute:: __by_products
         :type:  list[ResourceDescriptor]


      .. py:method:: __eq__(other: object) -> bool

         Check if this run details object is equal to *other*.

         :param other: The run details object to compare this with.

         :return: A |bool| set to **True** if both run details are equal,
             **False** else.



      .. py:property:: builder
         :type: Builder


         Run details builder.

         Identifies the build platform that executed the invocation, which
         is trusted to have correctly performed the operation and populated
         this provenance.



      .. py:property:: by_products
         :type: list[ResourceDescriptor]


         Run details additional artifacts.

         Additional artifacts generated during the build that are not
         considered the **output** of the build but that might be needed
         during debugging or incident response.

         For example, this might reference logs generated during the build
         and/or a digest of the fully evaluated build configuration.

         In most cases, this **SHOULD NOT** contain all intermediate files
         generated during the build.

         Instead, this **SHOULD** only contain files that are likely to be
         useful later and that cannot be easily reproduced.



      .. py:property:: metadata
         :type: BuildMetadata


         Run details build metadata.

         Metadata about this particular execution of the build.



      .. py:method:: as_dict() -> dict

         Get the dictionary representation of this run details.

         :return: The dictionary representation of this run details. This
             should be a valid JSON object (call to |json.load| succeeds).

         .. seealso:: |rund.as_json|, |json.load|, |rund.load_dict|



      .. py:method:: as_json() -> str

         Get the representation of this run details as a JSON string.

         The dictionary representing this run details (as returned by
         |rund.as_dict|) is turned into a JSON string using |json.dumps| with
         *sort_keys* set to **True**.

         .. seealso:: |rund.as_dict|, |rund.load_json|



      .. py:method:: load_dict(initializer: dict[str, Any]) -> Predicate
         :classmethod:


         Initialize a run details from a dictionary.

         :param initializer: The dictionary to initialize this run details
             with.

         :return: A run details object created from the input *initializer*.

         .. seealso:: |rund.as_dict|, |rund.load_json|



      .. py:method:: load_json(initializer: str) -> Predicate
         :classmethod:


         Initialize a run details from a JSON string.

         :param initializer: The JSON string to initialize this run details
             with.

         :return: A run details object created from the input *initializer*.

         .. seealso:: |rund.as_json|, |rund.load_dict|




   .. py:attribute:: __build_definition
      :type:  Predicate


   .. py:attribute:: __run_details
      :type:  Predicate


   .. py:method:: __eq__(other: object) -> bool

      Check if this predicate is equal to *other*.

      :param other: The predicate object to compare this with.

      :return: A |bool| set to **True** if both predicates are equal,
          **False** else.



   .. py:property:: build_definition
      :type: Predicate


      The input to the build.

      The accuracy and completeness are implied by runDetails.builder.id.



   .. py:property:: run_details
      :type: Predicate


      Details specific to this particular execution of the build.



   .. py:method:: as_dict() -> dict

      Get the dictionary representation of this predicate.

      :return: The dictionary representation of this statement. This should
          be a valid JSON object (call to |json.load| succeeds).

      .. seealso:: |p.as_json|, |json.load|, |p.load_dict|



   .. py:method:: as_json() -> str

      Get the representation of this predicate as a JSON string.

      The dictionary representing this predicate (as returned by
      |p.as_dict|) is turned into a JSON string using |json.dumps| with
      *sort_keys* set to **True**.

      .. seealso:: |p.as_dict|, |p.load_json|



   .. py:method:: load_dict(initializer: dict[str, Any]) -> Predicate
      :classmethod:


      Initialize a predicate from a dictionary.

      :param initializer: The dictionary to initialize this predicat with.

      :return: A predicate object created from the input *initializer*.

      .. seealso:: |p.as_dict|, |p.load_json|



   .. py:method:: load_json(initializer: str) -> Predicate
      :classmethod:


      Initialize a predicate from a JSON string.

      :param initializer: The JSON string to initialize this predicate with.

      :return: A predictae object created from the input *initializer*.

      .. seealso:: |p.as_json|, |p.load_dict|



.. py:class:: TypeURI(uri: str)

   Bases: :py:obj:`object`


   Uniform Resource Identifier as specified in RFC 3986.

   Used as a collision-resistant type identifier.

   Format
   ------
   A TypeURI is represented as a case-sensitive string and **MUST** be case
   normalized as per section 6.2.2.1 of RFC 3986, meaning that the scheme and
   authority **MUST** be in lowercase.

   **SHOULD** resolve to a human-readable description, but **MAY** be
   unresolvable. **SHOULD** include a version number to allow for revisions.

   TypeURIs are not registered. The natural namespacing of URIs is sufficient
   to prevent collisions.

   Example
   -------
   ::

       https://in-toto.io/Statement/v1


   .. py:attribute:: __uri


   .. py:method:: __eq__(other: object) -> bool

      Check if this type uri is equal to *other*.



   .. py:method:: __str__() -> str

      Return the string representation of this TypeURI.



   .. py:property:: uri
      :type: str


      Actual URI of this TypeURI.

      :return: The actual URI of this TypeURI.



.. py:class:: ResourceURI(uri: str)

   Bases: :py:obj:`TypeURI`


   Uniform Resource Identifier as specified in RFC 3986.

    Used to identify and locate any resource, service, or software artifact.

   Format
   ------
   A ResourceURI is represented as a case-sensitive string and **MUST** be case
   normalized as per section 6.2.2.1 of RFC 3986, meaning that the scheme and
   authority **MUST** be in lowercase.

   **SHOULD** resolve to the artifact, but **MAY** be unresolvable.

   It is **RECOMMENDED** to use
   `Package URL <https://github.com/package-url/purl-spec/>`_ (``pkg:``) or
   `SPDX Download Location
   <https://spdx.github.io/spdx-spec/v2.3/package-information/#77-package-download-location-field>`_
   (e.g. ``git+https:``).

   Example
   -------
   ::

       pkg:deb/debian/stunnel@5.50-3?arch=amd64


