diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6c7706e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" + + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + groups: + python-dependencies: + patterns: + - "*" \ No newline at end of file diff --git a/.github/workflows/metadata.yml b/.github/workflows/metadata.yml new file mode 100644 index 0000000..09f9853 --- /dev/null +++ b/.github/workflows/metadata.yml @@ -0,0 +1,10 @@ +name: Validate software metadata + +on: [push, pull_request] + +jobs: + metadata: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: LUMC-DCC/rs-metadata@v1 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..91d145a --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,14 @@ +cff-version: 1.2.0 +title: diff-fuse +message: "If you use this software, please cite it using the metadata in this file." +type: software +abstract: >- + Tool for comparing multiple JSON documents side by side and producing a merged result interactively. +authors: + - family-names: Steeghs-Turchina + given-names: Mariia + affiliation: Leiden University Medical Center + orcid: https://orcid.org/0000-0002-0852-4752 +version: 1.0.0 +repository-code: https://github.com/bio-tools/diff-fuse +license: Apache-2.0 diff --git a/backend/poetry.lock b/backend/poetry.lock index 0fbcd01..e1290ae 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.0 and should not be changed by hand. [[package]] name = "annotated-doc" @@ -43,6 +43,18 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""] +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + [[package]] name = "black" version = "26.1.0" @@ -244,6 +256,43 @@ files = [ {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, + {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.3.6" +referencing = ">=0.28.4" +rpds-py = ">=0.25.0" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -256,6 +305,21 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "nameparser" +version = "2.4.0" +description = "A simple Python module for parsing human names into their individual components." +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "nameparser-2.4.0-py3-none-any.whl", hash = "sha256:662e5ef60d040b60c32d4328396e5e8238c8ec44e66cfbebb18439cb89fea6da"}, + {file = "nameparser-2.4.0.tar.gz", hash = "sha256:e5e56006d342a3d54ce09b5a91476ddbfb04ea0c9038b2649f7ba26c90c982e3"}, +] + +[package.extras] +ja = ["namedivider-python (>=0.4)"] + [[package]] name = "orjson" version = "3.11.7" @@ -346,7 +410,7 @@ version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -685,6 +749,89 @@ files = [ [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] +[[package]] +name = "pyyaml" +version = "6.0.3" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] + [[package]] name = "redis" version = "7.2.0" @@ -705,6 +852,179 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)" otel = ["opentelemetry-api (>=1.39.1)", "opentelemetry-exporter-otlp-proto-http (>=1.39.1)", "opentelemetry-sdk (>=1.39.1)"] xxhash = ["xxhash (>=3.6.0,<3.7.0)"] +[[package]] +name = "referencing" +version = "0.37.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, + {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "rpds-py" +version = "2026.9.1" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "rpds_py-2026.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2"}, + {file = "rpds_py-2026.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win32.whl", hash = "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12"}, + {file = "rpds_py-2026.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af"}, + {file = "rpds_py-2026.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win32.whl", hash = "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02"}, + {file = "rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9"}, + {file = "rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f"}, + {file = "rpds_py-2026.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b"}, + {file = "rpds_py-2026.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win32.whl", hash = "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-win32.whl", hash = "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e"}, + {file = "rpds_py-2026.9.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9"}, + {file = "rpds_py-2026.9.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win32.whl", hash = "sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-win32.whl", hash = "sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_i686.whl", hash = "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_10_12_x86_64.whl", hash = "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37"}, + {file = "rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12"}, +] + +[[package]] +name = "rs-metadata" +version = "1.0.0" +description = "Validate research software metadata against the LUMC CodeMeta profile, and keep codemeta.json consistent with CITATION.cff and other metadata." +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "rs_metadata-1.0.0-py3-none-any.whl", hash = "sha256:484d3059acbe517ab5ce66adf09dccdabc673c23625c0856233a2acd181278b1"}, + {file = "rs_metadata-1.0.0.tar.gz", hash = "sha256:5db82bfb46b8691c604a5811c8eb7e7b6e4bd9c143a7551654c8faf4974fbafc"}, +] + +[package.dependencies] +jsonschema = ">=4.21,<5" +nameparser = ">=2.2.0" +packaging = ">=23.0" +pyyaml = ">=6.0,<7" + [[package]] name = "ruff" version = "0.15.1" @@ -802,4 +1122,4 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3) [metadata] lock-version = "2.1" python-versions = ">=3.12,<4.0" -content-hash = "3b5ee41cb4286224251e947ac87deb3ea7670526806f0d5e1178f6c705d5cb71" +content-hash = "f4252ae9906b23f69e956b2d283b8b795300b65fd3b9cb3672c400c7cd7c4087" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 6ca4325..857cc9e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "diff-fuse" -version = "0.1.0" +version = "1.0.0" description = "" authors = [ {name = "Mariia Steeghs-Turchina"} @@ -15,6 +15,7 @@ dependencies = [ "pydantic-settings (>=2.12.0,<3.0.0)", "orjson (>=3.11.7,<4.0.0)", "redis (>=7.2.0,<8.0.0)", + "rs-metadata (>=1.0.0,<2.0.0)", ] [tool.poetry.group.dev.dependencies] diff --git a/backend/src/diff_fuse/main.py b/backend/src/diff_fuse/main.py index 9529119..c93352f 100644 --- a/backend/src/diff_fuse/main.py +++ b/backend/src/diff_fuse/main.py @@ -25,6 +25,7 @@ import uuid from contextlib import asynccontextmanager +from importlib.metadata import version from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError @@ -66,7 +67,8 @@ async def lifespan(app: FastAPI): yield -app = FastAPI(title=settings.app_name, version="0.1.0", lifespan=lifespan) +# Read from the installed package, so pyproject.toml is the only place to bump it. +app = FastAPI(title=settings.app_name, version=version("diff-fuse"), lifespan=lifespan) def _get_request_id(request: Request) -> str: diff --git a/codemeta.json b/codemeta.json new file mode 100644 index 0000000..2712168 --- /dev/null +++ b/codemeta.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://w3id.org/codemeta/3.1", + { + "schema": "https://schema.org/" + } + ], + "@type": "SoftwareSourceCode", + "name": "diff-fuse", + "description": "Tool for comparing multiple JSON documents side by side and producing a merged result interactively.", + "version": "1.0.0", + "identifier": "https://github.com/bio-tools/diff-fuse", + "author": [ + { + "@id": "https://orcid.org/0000-0002-0852-4752", + "@type": "Person", + "givenName": "Mariia", + "familyName": "Steeghs-Turchina", + "affiliation": { + "@type": "Organization", + "@id": "https://ror.org/05xvt9f17", + "name": "Leiden University Medical Center" + } + } + ], + "license": "https://spdx.org/licenses/Apache-2.0", + "codeRepository": "https://github.com/bio-tools/diff-fuse", + "programmingLanguage": [ + { + "@type": "ComputerLanguage", + "name": "Python", + "version": ">=3.12,<4.0" + }, + { + "@type": "ComputerLanguage", + "name": "TypeScript", + "version": "~5.9.3" + }, + { + "@type": "ComputerLanguage", + "name": "CSS" + } + ], + "applicationCategory": [ + "Web API", + "Web application" + ], + "schema:featureList": [ + "http://edamontology.org/operation_2945", + "http://edamontology.org/operation_1812", + "http://edamontology.org/operation_3436", + "http://edamontology.org/operation_0336" + ] +} diff --git a/frontend/openapi.json b/frontend/openapi.json index d1a5d6c..0c104bd 100644 --- a/frontend/openapi.json +++ b/frontend/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"diff-fuse","version":"0.1.0"},"paths":{"/":{"post":{"summary":"Create","description":"Create a new session.\n\nThis endpoint stores the provided documents server-side and returns\na `session_id` that can be used for subsequent operations such as:\n\n- diff computation\n- merge resolution\n- array key suggestions\n- export\n\nParameters\n----------\nreq : AddDocsSessionRequest\n Session creation payload containing the input documents.\n\nReturns\n-------\nSessionResponse\n Contains the generated `session_id` and document metadata.\n\nNotes\n-----\n- Document parsing errors (if any) are captured in the stored\n `DocumentResult` objects and surfaced in later operations.","operationId":"create__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDocsSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/add-docs":{"post":{"summary":"Add Docs","description":"Add documents to an existing session.\n\nThis endpoint allows clients to append new documents to an existing\nsession. The new documents are parsed, normalized, and stored\nalongside the existing ones.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\nreq : AddDocsSessionRequest\n Request payload containing the new documents to add.\n\nReturns\n-------\nSessionResponse\n Updated session metadata after adding the new documents.\n\nNotes\n-----\nThis operation mutates the session by appending new documents. The\nexisting documents remain unchanged.","operationId":"add_docs__session_id__add_docs_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDocsSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/remove-doc":{"post":{"summary":"Remove Doc","description":"Remove a document from an existing session.\n\nThis endpoint allows clients to remove a specific document from an\nexisting session by its `doc_id`. The session's stored documents are\nupdated accordingly.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\nreq : RemoveDocSessionRequest\n Request payload containing the `doc_id` of the document to remove.\n\nReturns\n-------\nSessionResponse\n Updated session metadata after removing the specified document.\n\nNotes\n-----\n- This operation mutates the session by removing the specified document. The\nremaining documents remain unchanged.","operationId":"remove_doc__session_id__remove_doc_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveDocSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/docs-meta":{"get":{"summary":"List Docs Meta","description":"List metadata for all documents in a session.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\n\nReturns\n-------\nSessionResponse\n Session metadata including document metadata for all documents in the session.","operationId":"list_docs_meta__session_id__docs_meta_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/full":{"get":{"summary":"Get Full Session State","description":"Retrieve the full session state, including all documents and their parse results.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\n\nReturns\n-------\nFullSessionResponse\n Contains the complete session state, including all documents and their parse results.\n\nNotes\n-----\nThis endpoint is primarily intended for debugging and development purposes,\nas it may return large payloads depending on the number and size of documents in the session.","operationId":"get_full_session_state__session_id__full_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/diff":{"post":{"summary":"Diff","description":"Compute the diff tree for a session.\n\nThis endpoint builds a structural diff across all documents stored in\nthe specified session. The result is a hierarchical tree of `DiffNode`\nobjects that the UI can render side-by-side.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to compare.\nreq : DiffRequest\n Diff configuration payload, containing per-array\n matching strategies.\n\nReturns\n-------\nDiffResponse\n The computed diff tree rooted at the document root.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\n- The diff is recomputed on each call using cached normalized documents.\n- Array handling behavior depends on `array_strategies`.\n- The returned tree uses stable canonical node IDs suitable for UI state.","operationId":"diff__session_id__diff_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiffRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiffResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/merge":{"post":{"summary":"Merge","description":"Produce a merged document for a session.\n\nThis endpoint applies the provided path selections to the session's\ndocuments and returns the synthesized merged output. Any unresolved\nconflicts are reported explicitly.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : MergeRequest\n Merge configuration including:\n - diff configuration (array strategies)\n - per-path merge selections\n\nReturns\n-------\nMergeResponse\n The merged document along with any unresolved conflict paths.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\n- Merge behavior is deterministic given the same selections.\n- Container nodes inherit selections down the tree unless overridden.\n- Paths listed in `unresolved_paths` require user intervention.","operationId":"merge__session_id__merge_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/arrays/suggest-keys":{"post":{"summary":"Suggest Keys","description":"Suggest candidate key fields for an array node within a session.\n\nThis endpoint analyzes the array located at the provided canonical node ID\nacross all session documents and returns ranked key candidates that may\nbe suitable for keyed array matching.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to analyze.\nreq : SuggestArrayKeysRequest\n Request containing:\n - `node_id`: canonical ID to the target array node\n - `top_k`: maximum number of suggestions to return\n\nReturns\n-------\nSuggestArrayKeysResponse\n Ranked list of suggested key fields with scoring metadata.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\nInvalidPath\n If the provided node is malformed or unsupported.\n\nNotes\n-----\n- Only arrays of objects are meaningful for key suggestion.\n- Documents that fail parsing are ignored for suggestion purposes.\n- Suggestions are heuristic and should be treated as guidance,\n not guarantees of uniqueness.","operationId":"suggest_keys__session_id__arrays_suggest_keys_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestArrayKeysRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestArrayKeysResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/export/text":{"post":{"summary":"Export Text","description":"Return the merged document as formatted text.\n\nThis endpoint is primarily intended for UI workflows such as:\n- copy-to-clipboard\n- inline preview\n- quick inspection of merge results\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : ExportRequest\n Export configuration including:\n - merge request (diff config + selections)\n - pretty-print preference\n - conflict requirements\n\nReturns\n-------\nExportTextResponse\n Textual representation of the merged JSON along with any unresolved\n conflict paths.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.","operationId":"export_text__session_id__export_text_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportTextResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/export/download":{"post":{"summary":"Export Download","description":"Download the merged document as a JSON file.\n\nThis endpoint returns the merged result as an HTTP attachment suitable\nfor saving to disk.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : ExportRequest\n Export configuration controlling merge behavior and formatting.\n\nReturns\n-------\nResponse\n Binary HTTP response with:\n - media type: application/json\n - content-disposition: attachment\n - body: UTF-8 encoded JSON\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\nThe downloaded JSON is identical to the text export output, aside from\ntransport encoding.","operationId":"export_download__session_id__export_download_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"Health check endpoint.\n\nReturns\n-------\ndict[str, str]\n Minimal health payload. Intended for load balancers and orchestration checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AddDocsSessionRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/InputDocument"},"type":"array","minItems":1,"title":"Documents"}},"additionalProperties":false,"type":"object","required":["documents"],"title":"AddDocsSessionRequest","description":"Request payload for creating a new session\nor adding documents to an existing session.\n\nA session stores the provided documents server-side and returns a\n``session_id`` that can be used to compute diffs, apply merges,\nrequest array key suggestions, and export results without resending\ndocument content.\n\nAttributes\n----------\ndocuments : list[InputDocument]\n The set of input documents to store in the session.\n Constraints:\n - Must contain at least one document (N-way comparisons, N ≥ 1).\n - Each document must include a stable ``doc_id`` that the client\n will later reference in merge selections.\n\nNotes\n-----\nDocuments are parsed and normalized during session creation so that\nsubsequent operations can reuse cached results efficiently."},"ArrayMeta":{"properties":{"strategy":{"$ref":"#/components/schemas/ArrayStrategy"}},"additionalProperties":false,"type":"object","required":["strategy"],"title":"ArrayMeta","description":"Extra metadata attached to array nodes.\n\nAttributes\n----------\nstrategy : ArrayStrategy\n The effective array strategy applied at this array node.\n\nNotes\n-----\nThis is only included when `DiffNode.kind == NodeKind.array`."},"ArraySelector":{"properties":{"mode":{"$ref":"#/components/schemas/ArrayStrategyMode"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"}},"additionalProperties":false,"type":"object","required":["mode"],"title":"ArraySelector","description":"Describes how an array element was selected/aligned across documents.\n\nAttributes\n----------\nmode : ArrayStrategyMode\n The mode of array strategy that determined the selection/alignment of this element.\nindex : int | None\n For index mode: the array index used for alignment.\nkey : str | None\n For keyed mode: the key value used for alignment.\nvalue : str | None\n Optional value used for UI labeling of the aligned element.\n Example: if mode=keyed and key=\"id\", value might be \"123\" to indicate that\n this group corresponds to elements with id=123.\n\nNotes\n-----\nThis is only included for array element nodes (i.e., nodes whose parent is an array).\n- For index mode, `index` is always present and `key` is None.\n- For keyed mode, `key` is always present and `index` is None.\n- For similarity mode, both `index` and `key` may be None depending on the specific alignment algorithm."},"ArrayStrategy":{"properties":{"mode":{"$ref":"#/components/schemas/ArrayStrategyMode","default":"index"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key","description":"Object field used for keyed matching (mode=keyed)."},"similarity_threshold":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Similarity Threshold","description":"Similarity threshold in [0.0, 1.0] (mode=similarity)."}},"additionalProperties":false,"type":"object","title":"ArrayStrategy","description":"Per-array matching configuration.\n\nThis model controls how a specific array path should be aligned during\ndiff computation.\n\nAttributes\n----------\nmode : ArrayStrategyMode\n Matching strategy to apply.\n\nkey : str | None\n Object field used for keyed matching (required when\n ``mode=\"keyed\"``).\n Example:\n - ``\"id\"``\n - ``\"name\"``\nsimilarity_threshold : float | None\n Threshold used by the similarity matcher (future feature).\n Must lie in the closed interval ``[0.0, 1.0]``.\n\nNotes\n-----\nIf a strategy is invalid for the actual data (e.g., keyed mode on\nnon-object arrays), the diff engine will surface an error\nat the corresponding array node."},"ArrayStrategyMode":{"type":"string","enum":["index","value","keyed","similarity"],"title":"ArrayStrategyMode","description":"Array element matching strategy.\n\nAttributes\n----------\nindex : str\n Match elements by positional index.\n Example:\n ``arr[0]`` aligns across all documents.\nvalue : str\n Match elements by value.\n Requirements:\n - Elements must be JSON scalars (string/number/boolean/null).\n - Values should be unique (per document) to avoid ambiguity.\nkeyed : str\n Match elements by a key field inside each object element.\n Requirements:\n - Elements must be JSON objects.\n - The configured key must exist in each element.\n - Key values should be unique (per document).\nsimilarity : str\n Match elements using a similarity heuristic (planned feature)."},"DiffNode":{"properties":{"node_id":{"type":"string","title":"Node Id","description":"Stable opaque id for this node (safe identifier)."},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id","description":"Stable opaque id of the parent node. Root uses None."},"path":{"type":"string","title":"Path","description":"Display path. Do not use for identity."},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key","description":"Display key/label."},"kind":{"$ref":"#/components/schemas/NodeKind"},"status":{"$ref":"#/components/schemas/DiffStatus"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Explanation for type errors or strategy failures."},"per_doc":{"additionalProperties":{"$ref":"#/components/schemas/ValuePresence"},"type":"object","title":"Per Doc","description":"Map doc_id -> presence/value at this node/path."},"children":{"items":{"$ref":"#/components/schemas/DiffNode"},"type":"array","title":"Children"},"array_meta":{"anyOf":[{"$ref":"#/components/schemas/ArrayMeta"},{"type":"null"}]},"parent_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Path","description":"Canonical parent path. Root uses None."},"array_selector":{"anyOf":[{"$ref":"#/components/schemas/ArraySelector"},{"type":"null"}],"description":"For array element nodes only: describes the selector used."}},"additionalProperties":false,"type":"object","required":["node_id","path","key","kind","status","per_doc"],"title":"DiffNode","description":"Node in the diff tree.\n\nEach node corresponds to a canonical node ID and describes:\n- structural kind (scalar/object/array)\n- diff status (same/diff/missing/type_error)\n- per-document presence/value information\n- child nodes for object/array structures\n\nAttributes\n----------\nnode_id : str\n Stable opaque ID for this node (safe identifier).\nparent_id : str | None\n Stable opaque ID of the parent node. Root uses None.\npath : str\n Canonical path identifier (e.g., ``\"a.b[0].c\"``). The root path is ``\"\"``.\nkey : str | None\n Key for object nodes, or None for the root and array nodes. This is used for display purposes.\nkind : NodeKind\n Structural kind of the node.\nstatus : DiffStatus\n Diff status of the node.\nmessage : str | None\n Optional explanation, typically used when `status == \"type_error\"`.\n Example messages:\n - ``\"type mismatch at 'x': number vs string\"``\n - ``\"Keyed mode requires 'key' at array path 'items'\"``\nper_doc : dict[str, ValuePresence]\n Mapping from ``doc_id`` to per-document presence/value information at this node.\nchildren : list[DiffNode]\n Child nodes for object and array nodes.\n Ordering guarantees:\n - Object children are sorted by object key.\n - Array children ordering depends on the applied array strategy (e.g., index\n ascending or stable keyed ordering).\narray_meta : ArrayMeta | None\n Present only for array nodes, to surface array strategy configuration.\nparent_path : str | None\n Canonical path of the parent node. The root node has `parent_path = None`.\narray_selector : ArraySelector | None\n For array element nodes, describes how this element was selected/aligned across documents.\n\nNotes\n-----\n- Container nodes generally omit embedded values in `per_doc[*].value`."},"DiffRequest":{"properties":{"array_strategies_by_node_id":{"additionalProperties":{"$ref":"#/components/schemas/ArrayStrategy"},"type":"object","title":"Array Strategies By Node Id"},"null_mode":{"$ref":"#/components/schemas/NullMode","description":"How JSON null is interpreted. 'missing' treats null as no value.","default":"missing"}},"additionalProperties":false,"type":"object","title":"DiffRequest","description":"Request payload for computing a diff within a session.\n\nAttributes\n----------\narray_strategies_by_node_id : dict[str, ArrayStrategy]\n Optional per-node overrides controlling how arrays are matched.\n Keys are canonical node IDs.\n Behavior:\n - Missing paths use the backend default strategy.\n - Provided paths override the strategy only at that location.\nnull_mode : NullMode\n How JSON null is interpreted when comparing documents. Defaults to\n `NullMode.missing`, where a null means \"no value\" just like an absent key.\n Use `NullMode.value` to treat null as an ordinary value whose type can\n conflict with others.\n\nNotes\n-----\nThis request does **not** include documents; documents are retrieved\nfrom the session identified in the route.\n\n`null_mode` should be sent consistently to the diff, merge and export\nendpoints: each rebuilds the tree from the request, and changing the mode\nchanges which nodes exist, so selections keyed by node id may no longer match."},"DiffResponse":{"properties":{"root":{"$ref":"#/components/schemas/DiffNode"}},"additionalProperties":false,"type":"object","required":["root"],"title":"DiffResponse","description":"Response payload containing the computed diff tree.\n\nAttributes\n----------\nroot : DiffNode\n Root of the hierarchical diff tree.\n The root node has:\n - ``path == \"\"``\n - ``key is None``"},"DiffStatus":{"type":"string","enum":["same","diff","missing","type_error"],"title":"DiffStatus","description":"Diff status for a node.\n\nOnly `same` means \"no difference\". The other three are all differences and\nare presented as such; they record *why* the documents differ, which\ndetermines whether the merge can resolve the node on its own.\n\nAttributes\n----------\nsame : str\n All documents that contain this node agree on the value, and no document\n is missing the node. Auto-resolvable.\ndiff : str\n At least two documents contain this node but disagree on its value.\n All present values share the same JSON type. Needs a selection.\nmissing : str\n At least one document has no value at this node/path, but all documents\n that do have one agree on it. Auto-resolvable: the real value wins.\n What counts as \"no value\" depends on the effective `NullMode`:\n - under `NullMode.missing` (default), an absent key and a JSON null are\n both \"no value\" and are treated identically\n - under `NullMode.value`, only an absent key is \"no value\"; a JSON null\n is an ordinary value of type \"null\"\ntype_error : str\n The documents hold incompatible shapes, so their values cannot be\n compared element by element. Needs a selection.\n Example scenarios:\n - One document has an object while another has a string at the same path.\n - An array strategy is invalid for the actual array contents (e.g. keyed\n strategy but elements are not objects).\n\nNotes\n-----\n`type_error` is aggregated upward by `_status_from_children`, so every\nancestor of an incompatible node reports it too. Only the *originating* node\nis childless, carries a `message`, and embeds its per-document values; a\npropagated one is an ordinary container node that still merges by recursion."},"DocMergeSelection":{"properties":{"kind":{"type":"string","const":"doc","title":"Kind","default":"doc"},"doc_id":{"type":"string","title":"Doc Id","description":"Required when kind='doc'."}},"additionalProperties":false,"type":"object","required":["doc_id"],"title":"DocMergeSelection","description":"Document-based merge selection.\n\nThis selection mode indicates that the value for a node should be taken\nfrom a specific source document.\n\nAttributes\n----------\nkind : Literal[\"doc\"]\n Discriminator for document-based selection.\ndoc_id : str\n Identifier of the source document to use for this node."},"DocumentFormat":{"type":"string","enum":["json"],"title":"DocumentFormat","description":"Supported input document formats.\n\nAttributes\n----------\njson : str\n JSON text input. This is currently the only supported format."},"DocumentMeta":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"ok":{"type":"boolean","title":"Ok","description":"Whether the document parsed successfully."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Parse/validation error message when ok=False."}},"additionalProperties":false,"type":"object","required":["doc_id","name","ok"],"title":"DocumentMeta","description":"Lightweight document status for API responses.\n\nThis model is returned to clients to report whether each document\nwas successfully parsed and normalized.\n\nAttributes\n----------\nok : bool\n Whether parsing and normalization succeeded.\nerror : str | None\n Human-readable error message when ``ok=False``."},"DocumentResult":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"ok":{"type":"boolean","title":"Ok","description":"Whether the document parsed successfully."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Parse/validation error message when ok=False."},"raw":{"type":"string","title":"Raw"},"normalized":{"anyOf":[{},{"type":"null"}],"title":"Normalized"}},"additionalProperties":false,"type":"object","required":["doc_id","name","ok","raw"],"title":"DocumentResult","description":"Full per-document processing result.\n\nThis model is stored inside sessions and represents the outcome of\nparsing and normalization. It is the canonical internal representation\nused by the operations.\n\nAttributes\n----------\nraw : str\n The original raw document content.\nnormalized : Any | None\n Parsed and normalized document content when ``ok=True``.\n The structure is backend-defined and treated as opaque by the API.\n\nNotes\n-----\n- When ``ok=False``, ``normalized`` is typically ``None``."},"ExportRequest":{"properties":{"merge_request":{"$ref":"#/components/schemas/MergeRequest","description":"Merge configuration reused during export."},"pretty":{"type":"boolean","title":"Pretty","default":true},"require_resolved":{"type":"boolean","title":"Require Resolved","default":false}},"additionalProperties":false,"type":"object","required":["merge_request"],"title":"ExportRequest","description":"Request payload for exporting the merged document.\n\nAttributes\n----------\nmerge_request : MergeRequest\n Full merge configuration reused during export. This includes\n array strategies and user selections.\n Rationale:\n Nesting the merge request ensures the frontend can reuse the\n exact same state object across merge and export operations.\npretty : bool, default=True\n Whether the exported text should be pretty-printed (indented).\n If False, the output is compact.\nrequire_resolved : bool, default=False\n If True, the export will fail when unresolved conflicts remain.\n Behavior:\n - False -> best-effort export with unresolved paths reported.\n - True -> export endpoint should return a conflict error when\n unresolved paths are present."},"ExportTextResponse":{"properties":{"unresolved_node_ids":{"items":{"type":"string"},"type":"array","title":"Unresolved Node Ids"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["text"],"title":"ExportTextResponse","description":"Response payload for text-based export.\n\nAttributes\n----------\ntext : str\n Serialized merged document (typically JSON).\n\nunresolved_node_ids : list[str]\n IDs of nodes that remained unresolved during merge.\n Interpretation:\n - Empty list -> fully resolved export.\n - Non-empty -> export was best-effort (unless strict mode\n prevented it).\n\nNotes\n-----\nThis response is primarily intended for clipboard workflows. File\ndownloads typically use a binary response instead."},"FullSessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"documents_results":{"items":{"$ref":"#/components/schemas/DocumentResult"},"type":"array","title":"Documents Results"}},"additionalProperties":false,"type":"object","required":["session_id","created_at","updated_at"],"title":"FullSessionResponse","description":"Extended session response including the full session state."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InputDocument":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"content":{"type":"string","title":"Content","description":"Raw document text."}},"additionalProperties":false,"type":"object","required":["doc_id","name","content"],"title":"InputDocument","description":"Client-supplied document payload.\n\nThis model represents the raw document submitted by the client for\noperations.\n\nAttributes\n----------\ncontent : str\n Raw document text. Parsing and normalization are performed later\n during session processing."},"KeySuggestion":{"properties":{"key":{"type":"string","title":"Key","description":"Object key name (e.g., 'id', 'name')."},"score":{"type":"number","title":"Score","description":"Overall heuristic score in [0, 1] (higher is better)."},"present_ratio":{"type":"number","title":"Present Ratio","description":"Fraction of object elements that contain this key."},"unique_ratio":{"type":"number","title":"Unique Ratio","description":"Average per-document uniqueness fraction for this key's values."},"scalar_ratio":{"type":"number","title":"Scalar Ratio","description":"Fraction of occurrences where the key's value is scalar-like."},"example_values":{"items":{"type":"string"},"type":"array","title":"Example Values","description":"Example observed values for this key (stringified)."}},"additionalProperties":false,"type":"object","required":["key","score","present_ratio","unique_ratio","scalar_ratio","example_values"],"title":"KeySuggestion","description":"Suggested candidate key for matching array elements.\n\nEach suggestion represents a heuristic evaluation of a field observed\nacross array elements. Higher scores indicate better suitability as a\nstable identifier for keyed array alignment.\n\nAttributes\n----------\nkey : str\n Object key name (e.g., ``\"id\"``, ``\"name\"``).\nscore : float\n Overall heuristic score in the range ``[0, 1]``. Higher is better.\npresent_ratio : float\n Fraction of object elements that contain this key.\nunique_ratio : float\n Average per-document uniqueness fraction of the key's values.\n Higher values indicate better candidate identifier fields.\nscalar_ratio : float\n Fraction of occurrences where the key's value is scalar-like\n (string/number/boolean/null).\nexample_values : list[str]\n Small sample of observed values for this key (stringified),\n intended for UI preview only.\n\nNotes\n-----\n- Scores are heuristic and not guarantees of correctness.\n- The backend does not enforce that suggested keys are valid for keyed\n array matching; validation occurs when the strategy is applied."},"ManualMergeSelection":{"properties":{"kind":{"type":"string","const":"manual","title":"Kind","default":"manual"},"manual_value":{"title":"Manual Value","description":"Required when kind='manual'. May be null."}},"additionalProperties":false,"type":"object","required":["manual_value"],"title":"ManualMergeSelection","description":"Manual override merge selection.\n\nThis selection mode indicates that the value for a node should be overridden\nwith a user-provided literal.\n\nAttributes\n----------\nkind : Literal[\"manual\"]\n Discriminator for manual override selection.\nmanual_value : Any\n Literal value to use for this node. Must be JSON-serializable."},"MergeRequest":{"properties":{"diff_request":{"$ref":"#/components/schemas/DiffRequest","description":"Diff configuration reused during merge."},"selections_by_node_id":{"additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/DocMergeSelection"},{"$ref":"#/components/schemas/ManualMergeSelection"}],"discriminator":{"propertyName":"kind","mapping":{"doc":"#/components/schemas/DocMergeSelection","manual":"#/components/schemas/ManualMergeSelection"}}},"type":"object","title":"Selections By Node Id","description":"Map node ID -> selection (doc/manual)."}},"additionalProperties":false,"type":"object","required":["diff_request"],"title":"MergeRequest","description":"Request payload for computing a merged document.\n\nAttributes\n----------\ndiff_request : DiffRequest\n Diff configuration reused during merge, primarily to supply\n per-path array strategies.\n Rationale:\n Keeping this nested ensures the frontend can reuse the same\n configuration object for both diff and merge operations.\nselections_by_node_id : dict[str, MergeSelection]\n Mapping from canonical node IDs -> user selection.\n Semantics:\n - Keys are canonical node IDs corresponding to nodes in the diff tree.\n - Each selection determines which document (or manual value)\n is chosen at that location.\n - Selections inherit down the subtree unless overridden.\n\nNotes\n-----\nMissing selections for conflicting nodes will result in unresolved\npaths in the response."},"MergeResponse":{"properties":{"merged":{"title":"Merged"},"unresolved_node_ids":{"items":{"type":"string"},"type":"array","title":"Unresolved Node Ids"},"resolved_ref_by_node_id":{"additionalProperties":{"$ref":"#/components/schemas/MergedNodeRef"},"type":"object","title":"Resolved Ref By Node Id"}},"additionalProperties":false,"type":"object","required":["merged"],"title":"MergeResponse","description":"Response payload containing the merged output.\n\nAttributes\n----------\nmerged : Any\n The merged JSON-like structure produced after applying\n selections. The structure matches the input document shape.\nunresolved_node_ids : list[str]\n Canonical node IDs that could not be resolved due to missing\n selections.\nresolved_ref_by_node_id : dict[str, MergedNodeRef]\n Machine-readable mapping describing where each diff node ended up\n in the merged output. This allows the frontend to render merged\n previews without relying on display paths.\n\nNotes\n-----\nThe backend performs a best-effort merge even when unresolved\nconflicts remain."},"MergedNodeRef":{"properties":{"present":{"type":"boolean","title":"Present","description":"Whether this node exists in the merged output."},"object_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Object Key","description":"Key under the parent merged object, when applicable."},"array_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Array Index","description":"Index under the parent merged array, when applicable."}},"additionalProperties":false,"type":"object","required":["present"],"title":"MergedNodeRef","description":"Machine-readable locator for a diff node inside the merged output.\n\nThis model tells how to resolve a node's merged value from its\nparent's merged value.\n\nAttributes\n----------\npresent : bool\n Whether this node is present in the merged output.\nobject_key : str | None\n If present=True and this node is under an object, the key to access it.\narray_index : int | None\n If present=True and this node is under an array, the index to access it.\n\nSemantics\n---------\npresent = False\n The node does not exist in the merged output.\npresent = True and object_key is not None\n The node is available at parent_merged[object_key].\npresent = True and array_index is not None\n The node is available at parent_merged[array_index].\n\nNotes\n-----\n- For non-root nodes, exactly one of `object_key` or `array_index` should\n be set when `present=True`.\n- The root node may be represented with only `present=True`."},"NodeKind":{"type":"string","enum":["scalar","object","array"],"title":"NodeKind","description":"Structural kind of a diff node.\n\nAttributes\n----------\nscalar : str\n A JSON scalar leaf (string/number/boolean/null).\nobject : str\n A JSON object.\narray : str\n A JSON array."},"NullMode":{"type":"string","enum":["missing","value"],"title":"NullMode","description":"How JSON null is interpreted when comparing documents.\n\nAttributes\n----------\nmissing : str\n A JSON null means \"no value\", exactly like an absent key. A document\n holding null does not take part in the type comparison at that node, so\n null never conflicts with another type. Examples, for values across\n documents:\n - null, absent -> `same` (both say \"no value\")\n - null, absent, 2 -> `missing` (one real value, some documents lack it)\n - null, absent, 2, \"pew\" -> `type_error` (two real types conflict)\nvalue : str\n A JSON null is an ordinary value of type \"null\". Comparing it against\n any other type produces a `type_error`."},"RemoveDocSessionRequest":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Document ID to remove from the session"}},"additionalProperties":false,"type":"object","required":["doc_id"],"title":"RemoveDocSessionRequest","description":"Request payload for removing a document from an existing session.\n\nAttributes\n----------\ndoc_id : str\n Document ID to remove from the session. Document ID must correspond\n to one previously added to the session.\n\nNotes\n-----\nRemoving documents updates the session state and may affect subsequent\ndiff and merge operations. Clients should ensure that removed document\nIDs are not referenced in future requests."},"SessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"documents_meta":{"items":{"$ref":"#/components/schemas/DocumentMeta"},"type":"array","title":"Documents Meta"}},"additionalProperties":false,"type":"object","required":["session_id","documents_meta"],"title":"SessionResponse","description":"Response payload returned after creating a session.\n\nAttributes\n----------\nsession_id : str\n Opaque identifier for the newly created session. Clients should\n treat this as an opaque token and not infer semantics from its\n contents.\ndocuments_meta : list[DocumentMeta]\n Metadata describing the stored documents, including their\n ``doc_id``, display name, declared format, and parse status.\n\nNotes\n-----\n- Session IDs may expire depending on server configuration (TTL).\n- Clients should persist the ``session_id`` for subsequent API calls."},"SuggestArrayKeysRequest":{"properties":{"node_id":{"type":"string","title":"Node Id","description":"Array node ID."},"top_k":{"type":"integer","maximum":50.0,"minimum":-1.0,"title":"Top K","default":-1}},"additionalProperties":false,"type":"object","required":["node_id"],"title":"SuggestArrayKeysRequest","description":"Request payload for array key suggestion.\n\nAttributes\n----------\nnode_id : str\n Canonical ID of the target array node. The node ID must resolve to an array in at least\n one document.\ntop_k : int\n Maximum number of suggestions to return.\n Constraints:\n - Minimum: -1 (no limit)\n - Maximum: 50"},"SuggestArrayKeysResponse":{"properties":{"node_id":{"type":"string","title":"Node Id"},"suggestions":{"items":{"$ref":"#/components/schemas/KeySuggestion"},"type":"array","title":"Suggestions"}},"additionalProperties":false,"type":"object","required":["node_id","suggestions"],"title":"SuggestArrayKeysResponse","description":"Response payload containing ranked key suggestions.\n\nAttributes\n----------\nnode_id : str\n The array node ID that was analyzed. Echoed from the request for\n convenience and client-side validation.\nsuggestions : list[KeySuggestion]\n Ranked list of candidate keys, ordered by descending confidence\n score. The list may be empty if no suitable keys were detected."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValuePresence":{"properties":{"present":{"type":"boolean","title":"Present"},"value":{"anyOf":[{},{"type":"null"}],"title":"Value"},"value_type":{"anyOf":[{"type":"string","enum":["object","array","string","number","boolean","null"]},{"type":"null"}],"title":"Value Type","description":"Normalized JSON type label."}},"additionalProperties":false,"type":"object","required":["present"],"title":"ValuePresence","description":"Per-document presence/value information for a single node.\n\nThis structure answers: for a given diff node (identified by its canonical\nnode ID), what does each document contain?\n\nAttributes\n----------\npresent : bool\n Whether the node exists in the document.\n - False means the node does not exist.\n - True means the node exists (even if the value is JSON null).\nvalue : Any | None\n The value at the node, when embedded in the response.\n Interpretation rules:\n - present=False always means \"missing\" regardless of `value`.\n - present=True and value=None can mean either:\n (a) the value is JSON null, or\n (b) the backend intentionally omitted a container value (object/array)\n to keep payloads small.\n Use `value_type` to tell these apart. Do not use the node's `kind`:\n at a type-error node the documents disagree on type, so `kind` does\n not describe any single document.\nvalue_type : JsonType | None\n Normalized JSON type label for the value.\n - When present=False, this is typically None.\n - When present=True, this is one of the JsonType literals.\n\nNotes\n-----\nImplementations commonly omit `value` for container nodes (object/array).\nThe exception is a type-error node, which has no children to merge through\nand therefore embeds its container values so a selection can resolve it."}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"diff-fuse","version":"1.0.0"},"paths":{"/":{"post":{"summary":"Create","description":"Create a new session.\n\nThis endpoint stores the provided documents server-side and returns\na `session_id` that can be used for subsequent operations such as:\n\n- diff computation\n- merge resolution\n- array key suggestions\n- export\n\nParameters\n----------\nreq : AddDocsSessionRequest\n Session creation payload containing the input documents.\n\nReturns\n-------\nSessionResponse\n Contains the generated `session_id` and document metadata.\n\nNotes\n-----\n- Document parsing errors (if any) are captured in the stored\n `DocumentResult` objects and surfaced in later operations.","operationId":"create__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDocsSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/add-docs":{"post":{"summary":"Add Docs","description":"Add documents to an existing session.\n\nThis endpoint allows clients to append new documents to an existing\nsession. The new documents are parsed, normalized, and stored\nalongside the existing ones.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\nreq : AddDocsSessionRequest\n Request payload containing the new documents to add.\n\nReturns\n-------\nSessionResponse\n Updated session metadata after adding the new documents.\n\nNotes\n-----\nThis operation mutates the session by appending new documents. The\nexisting documents remain unchanged.","operationId":"add_docs__session_id__add_docs_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDocsSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/remove-doc":{"post":{"summary":"Remove Doc","description":"Remove a document from an existing session.\n\nThis endpoint allows clients to remove a specific document from an\nexisting session by its `doc_id`. The session's stored documents are\nupdated accordingly.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\nreq : RemoveDocSessionRequest\n Request payload containing the `doc_id` of the document to remove.\n\nReturns\n-------\nSessionResponse\n Updated session metadata after removing the specified document.\n\nNotes\n-----\n- This operation mutates the session by removing the specified document. The\nremaining documents remain unchanged.","operationId":"remove_doc__session_id__remove_doc_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveDocSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/docs-meta":{"get":{"summary":"List Docs Meta","description":"List metadata for all documents in a session.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\n\nReturns\n-------\nSessionResponse\n Session metadata including document metadata for all documents in the session.","operationId":"list_docs_meta__session_id__docs_meta_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/full":{"get":{"summary":"Get Full Session State","description":"Retrieve the full session state, including all documents and their parse results.\n\nParameters\n----------\nsession_id : str\n Target session identifier.\n\nReturns\n-------\nFullSessionResponse\n Contains the complete session state, including all documents and their parse results.\n\nNotes\n-----\nThis endpoint is primarily intended for debugging and development purposes,\nas it may return large payloads depending on the number and size of documents in the session.","operationId":"get_full_session_state__session_id__full_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/diff":{"post":{"summary":"Diff","description":"Compute the diff tree for a session.\n\nThis endpoint builds a structural diff across all documents stored in\nthe specified session. The result is a hierarchical tree of `DiffNode`\nobjects that the UI can render side-by-side.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to compare.\nreq : DiffRequest\n Diff configuration payload, containing per-array\n matching strategies.\n\nReturns\n-------\nDiffResponse\n The computed diff tree rooted at the document root.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\n- The diff is recomputed on each call using cached normalized documents.\n- Array handling behavior depends on `array_strategies`.\n- The returned tree uses stable canonical node IDs suitable for UI state.","operationId":"diff__session_id__diff_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiffRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiffResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/merge":{"post":{"summary":"Merge","description":"Produce a merged document for a session.\n\nThis endpoint applies the provided path selections to the session's\ndocuments and returns the synthesized merged output. Any unresolved\nconflicts are reported explicitly.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : MergeRequest\n Merge configuration including:\n - diff configuration (array strategies)\n - per-path merge selections\n\nReturns\n-------\nMergeResponse\n The merged document along with any unresolved conflict paths.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\n- Merge behavior is deterministic given the same selections.\n- Container nodes inherit selections down the tree unless overridden.\n- Paths listed in `unresolved_paths` require user intervention.","operationId":"merge__session_id__merge_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/arrays/suggest-keys":{"post":{"summary":"Suggest Keys","description":"Suggest candidate key fields for an array node within a session.\n\nThis endpoint analyzes the array located at the provided canonical node ID\nacross all session documents and returns ranked key candidates that may\nbe suitable for keyed array matching.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to analyze.\nreq : SuggestArrayKeysRequest\n Request containing:\n - `node_id`: canonical ID to the target array node\n - `top_k`: maximum number of suggestions to return\n\nReturns\n-------\nSuggestArrayKeysResponse\n Ranked list of suggested key fields with scoring metadata.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\nInvalidPath\n If the provided node is malformed or unsupported.\n\nNotes\n-----\n- Only arrays of objects are meaningful for key suggestion.\n- Documents that fail parsing are ignored for suggestion purposes.\n- Suggestions are heuristic and should be treated as guidance,\n not guarantees of uniqueness.","operationId":"suggest_keys__session_id__arrays_suggest_keys_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestArrayKeysRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestArrayKeysResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/export/text":{"post":{"summary":"Export Text","description":"Return the merged document as formatted text.\n\nThis endpoint is primarily intended for UI workflows such as:\n- copy-to-clipboard\n- inline preview\n- quick inspection of merge results\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : ExportRequest\n Export configuration including:\n - merge request (diff config + selections)\n - pretty-print preference\n - conflict requirements\n\nReturns\n-------\nExportTextResponse\n Textual representation of the merged JSON along with any unresolved\n conflict paths.\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.","operationId":"export_text__session_id__export_text_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportTextResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{session_id}/export/download":{"post":{"summary":"Export Download","description":"Download the merged document as a JSON file.\n\nThis endpoint returns the merged result as an HTTP attachment suitable\nfor saving to disk.\n\nParameters\n----------\nsession_id : str\n Identifier of the session containing the documents to merge.\nreq : ExportRequest\n Export configuration controlling merge behavior and formatting.\n\nReturns\n-------\nResponse\n Binary HTTP response with:\n - media type: application/json\n - content-disposition: attachment\n - body: UTF-8 encoded JSON\n\nRaises\n------\nDomainError\n If the session does not exist or has expired.\n\nNotes\n-----\nThe downloaded JSON is identical to the text export output, aside from\ntransport encoding.","operationId":"export_download__session_id__export_download_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"Health check endpoint.\n\nReturns\n-------\ndict[str, str]\n Minimal health payload. Intended for load balancers and orchestration checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AddDocsSessionRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/InputDocument"},"type":"array","minItems":1,"title":"Documents"}},"additionalProperties":false,"type":"object","required":["documents"],"title":"AddDocsSessionRequest","description":"Request payload for creating a new session\nor adding documents to an existing session.\n\nA session stores the provided documents server-side and returns a\n``session_id`` that can be used to compute diffs, apply merges,\nrequest array key suggestions, and export results without resending\ndocument content.\n\nAttributes\n----------\ndocuments : list[InputDocument]\n The set of input documents to store in the session.\n Constraints:\n - Must contain at least one document (N-way comparisons, N ≥ 1).\n - Each document must include a stable ``doc_id`` that the client\n will later reference in merge selections.\n\nNotes\n-----\nDocuments are parsed and normalized during session creation so that\nsubsequent operations can reuse cached results efficiently."},"ArrayMeta":{"properties":{"strategy":{"$ref":"#/components/schemas/ArrayStrategy"}},"additionalProperties":false,"type":"object","required":["strategy"],"title":"ArrayMeta","description":"Extra metadata attached to array nodes.\n\nAttributes\n----------\nstrategy : ArrayStrategy\n The effective array strategy applied at this array node.\n\nNotes\n-----\nThis is only included when `DiffNode.kind == NodeKind.array`."},"ArraySelector":{"properties":{"mode":{"$ref":"#/components/schemas/ArrayStrategyMode"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"}},"additionalProperties":false,"type":"object","required":["mode"],"title":"ArraySelector","description":"Describes how an array element was selected/aligned across documents.\n\nAttributes\n----------\nmode : ArrayStrategyMode\n The mode of array strategy that determined the selection/alignment of this element.\nindex : int | None\n For index mode: the array index used for alignment.\nkey : str | None\n For keyed mode: the key value used for alignment.\nvalue : str | None\n Optional value used for UI labeling of the aligned element.\n Example: if mode=keyed and key=\"id\", value might be \"123\" to indicate that\n this group corresponds to elements with id=123.\n\nNotes\n-----\nThis is only included for array element nodes (i.e., nodes whose parent is an array).\n- For index mode, `index` is always present and `key` is None.\n- For keyed mode, `key` is always present and `index` is None.\n- For similarity mode, both `index` and `key` may be None depending on the specific alignment algorithm."},"ArrayStrategy":{"properties":{"mode":{"$ref":"#/components/schemas/ArrayStrategyMode","default":"index"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key","description":"Object field used for keyed matching (mode=keyed)."},"similarity_threshold":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Similarity Threshold","description":"Similarity threshold in [0.0, 1.0] (mode=similarity)."}},"additionalProperties":false,"type":"object","title":"ArrayStrategy","description":"Per-array matching configuration.\n\nThis model controls how a specific array path should be aligned during\ndiff computation.\n\nAttributes\n----------\nmode : ArrayStrategyMode\n Matching strategy to apply.\n\nkey : str | None\n Object field used for keyed matching (required when\n ``mode=\"keyed\"``).\n Example:\n - ``\"id\"``\n - ``\"name\"``\nsimilarity_threshold : float | None\n Threshold used by the similarity matcher (future feature).\n Must lie in the closed interval ``[0.0, 1.0]``.\n\nNotes\n-----\nIf a strategy is invalid for the actual data (e.g., keyed mode on\nnon-object arrays), the diff engine will surface an error\nat the corresponding array node."},"ArrayStrategyMode":{"type":"string","enum":["index","value","keyed","similarity"],"title":"ArrayStrategyMode","description":"Array element matching strategy.\n\nAttributes\n----------\nindex : str\n Match elements by positional index.\n Example:\n ``arr[0]`` aligns across all documents.\nvalue : str\n Match elements by value.\n Requirements:\n - Elements must be JSON scalars (string/number/boolean/null).\n - Values should be unique (per document) to avoid ambiguity.\nkeyed : str\n Match elements by a key field inside each object element.\n Requirements:\n - Elements must be JSON objects.\n - The configured key must exist in each element.\n - Key values should be unique (per document).\nsimilarity : str\n Match elements using a similarity heuristic (planned feature)."},"DiffNode":{"properties":{"node_id":{"type":"string","title":"Node Id","description":"Stable opaque id for this node (safe identifier)."},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id","description":"Stable opaque id of the parent node. Root uses None."},"path":{"type":"string","title":"Path","description":"Display path. Do not use for identity."},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key","description":"Display key/label."},"kind":{"$ref":"#/components/schemas/NodeKind"},"status":{"$ref":"#/components/schemas/DiffStatus"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Explanation for type errors or strategy failures."},"per_doc":{"additionalProperties":{"$ref":"#/components/schemas/ValuePresence"},"type":"object","title":"Per Doc","description":"Map doc_id -> presence/value at this node/path."},"children":{"items":{"$ref":"#/components/schemas/DiffNode"},"type":"array","title":"Children"},"array_meta":{"anyOf":[{"$ref":"#/components/schemas/ArrayMeta"},{"type":"null"}]},"parent_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Path","description":"Canonical parent path. Root uses None."},"array_selector":{"anyOf":[{"$ref":"#/components/schemas/ArraySelector"},{"type":"null"}],"description":"For array element nodes only: describes the selector used."}},"additionalProperties":false,"type":"object","required":["node_id","path","key","kind","status","per_doc"],"title":"DiffNode","description":"Node in the diff tree.\n\nEach node corresponds to a canonical node ID and describes:\n- structural kind (scalar/object/array)\n- diff status (same/diff/missing/type_error)\n- per-document presence/value information\n- child nodes for object/array structures\n\nAttributes\n----------\nnode_id : str\n Stable opaque ID for this node (safe identifier).\nparent_id : str | None\n Stable opaque ID of the parent node. Root uses None.\npath : str\n Canonical path identifier (e.g., ``\"a.b[0].c\"``). The root path is ``\"\"``.\nkey : str | None\n Key for object nodes, or None for the root and array nodes. This is used for display purposes.\nkind : NodeKind\n Structural kind of the node.\nstatus : DiffStatus\n Diff status of the node.\nmessage : str | None\n Optional explanation, typically used when `status == \"type_error\"`.\n Example messages:\n - ``\"type mismatch at 'x': number vs string\"``\n - ``\"Keyed mode requires 'key' at array path 'items'\"``\nper_doc : dict[str, ValuePresence]\n Mapping from ``doc_id`` to per-document presence/value information at this node.\nchildren : list[DiffNode]\n Child nodes for object and array nodes.\n Ordering guarantees:\n - Object children are sorted by object key.\n - Array children ordering depends on the applied array strategy (e.g., index\n ascending or stable keyed ordering).\narray_meta : ArrayMeta | None\n Present only for array nodes, to surface array strategy configuration.\nparent_path : str | None\n Canonical path of the parent node. The root node has `parent_path = None`.\narray_selector : ArraySelector | None\n For array element nodes, describes how this element was selected/aligned across documents.\n\nNotes\n-----\n- Container nodes generally omit embedded values in `per_doc[*].value`."},"DiffRequest":{"properties":{"array_strategies_by_node_id":{"additionalProperties":{"$ref":"#/components/schemas/ArrayStrategy"},"type":"object","title":"Array Strategies By Node Id"},"null_mode":{"$ref":"#/components/schemas/NullMode","description":"How JSON null is interpreted. 'missing' treats null as no value.","default":"missing"}},"additionalProperties":false,"type":"object","title":"DiffRequest","description":"Request payload for computing a diff within a session.\n\nAttributes\n----------\narray_strategies_by_node_id : dict[str, ArrayStrategy]\n Optional per-node overrides controlling how arrays are matched.\n Keys are canonical node IDs.\n Behavior:\n - Missing paths use the backend default strategy.\n - Provided paths override the strategy only at that location.\nnull_mode : NullMode\n How JSON null is interpreted when comparing documents. Defaults to\n `NullMode.missing`, where a null means \"no value\" just like an absent key.\n Use `NullMode.value` to treat null as an ordinary value whose type can\n conflict with others.\n\nNotes\n-----\nThis request does **not** include documents; documents are retrieved\nfrom the session identified in the route.\n\n`null_mode` should be sent consistently to the diff, merge and export\nendpoints: each rebuilds the tree from the request, and changing the mode\nchanges which nodes exist, so selections keyed by node id may no longer match."},"DiffResponse":{"properties":{"root":{"$ref":"#/components/schemas/DiffNode"}},"additionalProperties":false,"type":"object","required":["root"],"title":"DiffResponse","description":"Response payload containing the computed diff tree.\n\nAttributes\n----------\nroot : DiffNode\n Root of the hierarchical diff tree.\n The root node has:\n - ``path == \"\"``\n - ``key is None``"},"DiffStatus":{"type":"string","enum":["same","diff","missing","type_error"],"title":"DiffStatus","description":"Diff status for a node.\n\nOnly `same` means \"no difference\". The other three are all differences and\nare presented as such; they record *why* the documents differ, which\ndetermines whether the merge can resolve the node on its own.\n\nAttributes\n----------\nsame : str\n All documents that contain this node agree on the value, and no document\n is missing the node. Auto-resolvable.\ndiff : str\n At least two documents contain this node but disagree on its value.\n All present values share the same JSON type. Needs a selection.\nmissing : str\n At least one document has no value at this node/path, but all documents\n that do have one agree on it. Auto-resolvable: the real value wins.\n What counts as \"no value\" depends on the effective `NullMode`:\n - under `NullMode.missing` (default), an absent key and a JSON null are\n both \"no value\" and are treated identically\n - under `NullMode.value`, only an absent key is \"no value\"; a JSON null\n is an ordinary value of type \"null\"\ntype_error : str\n The documents hold incompatible shapes, so their values cannot be\n compared element by element. Needs a selection.\n Example scenarios:\n - One document has an object while another has a string at the same path.\n - An array strategy is invalid for the actual array contents (e.g. keyed\n strategy but elements are not objects).\n\nNotes\n-----\n`type_error` is aggregated upward by `_status_from_children`, so every\nancestor of an incompatible node reports it too. Only the *originating* node\nis childless, carries a `message`, and embeds its per-document values; a\npropagated one is an ordinary container node that still merges by recursion."},"DocMergeSelection":{"properties":{"kind":{"type":"string","const":"doc","title":"Kind","default":"doc"},"doc_id":{"type":"string","title":"Doc Id","description":"Required when kind='doc'."}},"additionalProperties":false,"type":"object","required":["doc_id"],"title":"DocMergeSelection","description":"Document-based merge selection.\n\nThis selection mode indicates that the value for a node should be taken\nfrom a specific source document.\n\nAttributes\n----------\nkind : Literal[\"doc\"]\n Discriminator for document-based selection.\ndoc_id : str\n Identifier of the source document to use for this node."},"DocumentFormat":{"type":"string","enum":["json"],"title":"DocumentFormat","description":"Supported input document formats.\n\nAttributes\n----------\njson : str\n JSON text input. This is currently the only supported format."},"DocumentMeta":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"ok":{"type":"boolean","title":"Ok","description":"Whether the document parsed successfully."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Parse/validation error message when ok=False."}},"additionalProperties":false,"type":"object","required":["doc_id","name","ok"],"title":"DocumentMeta","description":"Lightweight document status for API responses.\n\nThis model is returned to clients to report whether each document\nwas successfully parsed and normalized.\n\nAttributes\n----------\nok : bool\n Whether parsing and normalization succeeded.\nerror : str | None\n Human-readable error message when ``ok=False``."},"DocumentResult":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"ok":{"type":"boolean","title":"Ok","description":"Whether the document parsed successfully."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Parse/validation error message when ok=False."},"raw":{"type":"string","title":"Raw"},"normalized":{"anyOf":[{},{"type":"null"}],"title":"Normalized"}},"additionalProperties":false,"type":"object","required":["doc_id","name","ok","raw"],"title":"DocumentResult","description":"Full per-document processing result.\n\nThis model is stored inside sessions and represents the outcome of\nparsing and normalization. It is the canonical internal representation\nused by the operations.\n\nAttributes\n----------\nraw : str\n The original raw document content.\nnormalized : Any | None\n Parsed and normalized document content when ``ok=True``.\n The structure is backend-defined and treated as opaque by the API.\n\nNotes\n-----\n- When ``ok=False``, ``normalized`` is typically ``None``."},"ExportRequest":{"properties":{"merge_request":{"$ref":"#/components/schemas/MergeRequest","description":"Merge configuration reused during export."},"pretty":{"type":"boolean","title":"Pretty","default":true},"require_resolved":{"type":"boolean","title":"Require Resolved","default":false}},"additionalProperties":false,"type":"object","required":["merge_request"],"title":"ExportRequest","description":"Request payload for exporting the merged document.\n\nAttributes\n----------\nmerge_request : MergeRequest\n Full merge configuration reused during export. This includes\n array strategies and user selections.\n Rationale:\n Nesting the merge request ensures the frontend can reuse the\n exact same state object across merge and export operations.\npretty : bool, default=True\n Whether the exported text should be pretty-printed (indented).\n If False, the output is compact.\nrequire_resolved : bool, default=False\n If True, the export will fail when unresolved conflicts remain.\n Behavior:\n - False -> best-effort export with unresolved paths reported.\n - True -> export endpoint should return a conflict error when\n unresolved paths are present."},"ExportTextResponse":{"properties":{"unresolved_node_ids":{"items":{"type":"string"},"type":"array","title":"Unresolved Node Ids"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["text"],"title":"ExportTextResponse","description":"Response payload for text-based export.\n\nAttributes\n----------\ntext : str\n Serialized merged document (typically JSON).\n\nunresolved_node_ids : list[str]\n IDs of nodes that remained unresolved during merge.\n Interpretation:\n - Empty list -> fully resolved export.\n - Non-empty -> export was best-effort (unless strict mode\n prevented it).\n\nNotes\n-----\nThis response is primarily intended for clipboard workflows. File\ndownloads typically use a binary response instead."},"FullSessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"documents_results":{"items":{"$ref":"#/components/schemas/DocumentResult"},"type":"array","title":"Documents Results"}},"additionalProperties":false,"type":"object","required":["session_id","created_at","updated_at"],"title":"FullSessionResponse","description":"Extended session response including the full session state."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InputDocument":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Stable id provided by client (e.g., uuid)."},"name":{"type":"string","title":"Name","description":"Display name shown in the UI."},"format":{"$ref":"#/components/schemas/DocumentFormat","description":"Declared document format.","default":"json"},"content":{"type":"string","title":"Content","description":"Raw document text."}},"additionalProperties":false,"type":"object","required":["doc_id","name","content"],"title":"InputDocument","description":"Client-supplied document payload.\n\nThis model represents the raw document submitted by the client for\noperations.\n\nAttributes\n----------\ncontent : str\n Raw document text. Parsing and normalization are performed later\n during session processing."},"KeySuggestion":{"properties":{"key":{"type":"string","title":"Key","description":"Object key name (e.g., 'id', 'name')."},"score":{"type":"number","title":"Score","description":"Overall heuristic score in [0, 1] (higher is better)."},"present_ratio":{"type":"number","title":"Present Ratio","description":"Fraction of object elements that contain this key."},"unique_ratio":{"type":"number","title":"Unique Ratio","description":"Average per-document uniqueness fraction for this key's values."},"scalar_ratio":{"type":"number","title":"Scalar Ratio","description":"Fraction of occurrences where the key's value is scalar-like."},"example_values":{"items":{"type":"string"},"type":"array","title":"Example Values","description":"Example observed values for this key (stringified)."}},"additionalProperties":false,"type":"object","required":["key","score","present_ratio","unique_ratio","scalar_ratio","example_values"],"title":"KeySuggestion","description":"Suggested candidate key for matching array elements.\n\nEach suggestion represents a heuristic evaluation of a field observed\nacross array elements. Higher scores indicate better suitability as a\nstable identifier for keyed array alignment.\n\nAttributes\n----------\nkey : str\n Object key name (e.g., ``\"id\"``, ``\"name\"``).\nscore : float\n Overall heuristic score in the range ``[0, 1]``. Higher is better.\npresent_ratio : float\n Fraction of object elements that contain this key.\nunique_ratio : float\n Average per-document uniqueness fraction of the key's values.\n Higher values indicate better candidate identifier fields.\nscalar_ratio : float\n Fraction of occurrences where the key's value is scalar-like\n (string/number/boolean/null).\nexample_values : list[str]\n Small sample of observed values for this key (stringified),\n intended for UI preview only.\n\nNotes\n-----\n- Scores are heuristic and not guarantees of correctness.\n- The backend does not enforce that suggested keys are valid for keyed\n array matching; validation occurs when the strategy is applied."},"ManualMergeSelection":{"properties":{"kind":{"type":"string","const":"manual","title":"Kind","default":"manual"},"manual_value":{"title":"Manual Value","description":"Required when kind='manual'. May be null."}},"additionalProperties":false,"type":"object","required":["manual_value"],"title":"ManualMergeSelection","description":"Manual override merge selection.\n\nThis selection mode indicates that the value for a node should be overridden\nwith a user-provided literal.\n\nAttributes\n----------\nkind : Literal[\"manual\"]\n Discriminator for manual override selection.\nmanual_value : Any\n Literal value to use for this node. Must be JSON-serializable."},"MergeRequest":{"properties":{"diff_request":{"$ref":"#/components/schemas/DiffRequest","description":"Diff configuration reused during merge."},"selections_by_node_id":{"additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/DocMergeSelection"},{"$ref":"#/components/schemas/ManualMergeSelection"}],"discriminator":{"propertyName":"kind","mapping":{"doc":"#/components/schemas/DocMergeSelection","manual":"#/components/schemas/ManualMergeSelection"}}},"type":"object","title":"Selections By Node Id","description":"Map node ID -> selection (doc/manual)."}},"additionalProperties":false,"type":"object","required":["diff_request"],"title":"MergeRequest","description":"Request payload for computing a merged document.\n\nAttributes\n----------\ndiff_request : DiffRequest\n Diff configuration reused during merge, primarily to supply\n per-path array strategies.\n Rationale:\n Keeping this nested ensures the frontend can reuse the same\n configuration object for both diff and merge operations.\nselections_by_node_id : dict[str, MergeSelection]\n Mapping from canonical node IDs -> user selection.\n Semantics:\n - Keys are canonical node IDs corresponding to nodes in the diff tree.\n - Each selection determines which document (or manual value)\n is chosen at that location.\n - Selections inherit down the subtree unless overridden.\n\nNotes\n-----\nMissing selections for conflicting nodes will result in unresolved\npaths in the response."},"MergeResponse":{"properties":{"merged":{"title":"Merged"},"unresolved_node_ids":{"items":{"type":"string"},"type":"array","title":"Unresolved Node Ids"},"resolved_ref_by_node_id":{"additionalProperties":{"$ref":"#/components/schemas/MergedNodeRef"},"type":"object","title":"Resolved Ref By Node Id"}},"additionalProperties":false,"type":"object","required":["merged"],"title":"MergeResponse","description":"Response payload containing the merged output.\n\nAttributes\n----------\nmerged : Any\n The merged JSON-like structure produced after applying\n selections. The structure matches the input document shape.\nunresolved_node_ids : list[str]\n Canonical node IDs that could not be resolved due to missing\n selections.\nresolved_ref_by_node_id : dict[str, MergedNodeRef]\n Machine-readable mapping describing where each diff node ended up\n in the merged output. This allows the frontend to render merged\n previews without relying on display paths.\n\nNotes\n-----\nThe backend performs a best-effort merge even when unresolved\nconflicts remain."},"MergedNodeRef":{"properties":{"present":{"type":"boolean","title":"Present","description":"Whether this node exists in the merged output."},"object_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Object Key","description":"Key under the parent merged object, when applicable."},"array_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Array Index","description":"Index under the parent merged array, when applicable."}},"additionalProperties":false,"type":"object","required":["present"],"title":"MergedNodeRef","description":"Machine-readable locator for a diff node inside the merged output.\n\nThis model tells how to resolve a node's merged value from its\nparent's merged value.\n\nAttributes\n----------\npresent : bool\n Whether this node is present in the merged output.\nobject_key : str | None\n If present=True and this node is under an object, the key to access it.\narray_index : int | None\n If present=True and this node is under an array, the index to access it.\n\nSemantics\n---------\npresent = False\n The node does not exist in the merged output.\npresent = True and object_key is not None\n The node is available at parent_merged[object_key].\npresent = True and array_index is not None\n The node is available at parent_merged[array_index].\n\nNotes\n-----\n- For non-root nodes, exactly one of `object_key` or `array_index` should\n be set when `present=True`.\n- The root node may be represented with only `present=True`."},"NodeKind":{"type":"string","enum":["scalar","object","array"],"title":"NodeKind","description":"Structural kind of a diff node.\n\nAttributes\n----------\nscalar : str\n A JSON scalar leaf (string/number/boolean/null).\nobject : str\n A JSON object.\narray : str\n A JSON array."},"NullMode":{"type":"string","enum":["missing","value"],"title":"NullMode","description":"How JSON null is interpreted when comparing documents.\n\nAttributes\n----------\nmissing : str\n A JSON null means \"no value\", exactly like an absent key. A document\n holding null does not take part in the type comparison at that node, so\n null never conflicts with another type. Examples, for values across\n documents:\n - null, absent -> `same` (both say \"no value\")\n - null, absent, 2 -> `missing` (one real value, some documents lack it)\n - null, absent, 2, \"pew\" -> `type_error` (two real types conflict)\nvalue : str\n A JSON null is an ordinary value of type \"null\". Comparing it against\n any other type produces a `type_error`."},"RemoveDocSessionRequest":{"properties":{"doc_id":{"type":"string","title":"Doc Id","description":"Document ID to remove from the session"}},"additionalProperties":false,"type":"object","required":["doc_id"],"title":"RemoveDocSessionRequest","description":"Request payload for removing a document from an existing session.\n\nAttributes\n----------\ndoc_id : str\n Document ID to remove from the session. Document ID must correspond\n to one previously added to the session.\n\nNotes\n-----\nRemoving documents updates the session state and may affect subsequent\ndiff and merge operations. Clients should ensure that removed document\nIDs are not referenced in future requests."},"SessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"documents_meta":{"items":{"$ref":"#/components/schemas/DocumentMeta"},"type":"array","title":"Documents Meta"}},"additionalProperties":false,"type":"object","required":["session_id","documents_meta"],"title":"SessionResponse","description":"Response payload returned after creating a session.\n\nAttributes\n----------\nsession_id : str\n Opaque identifier for the newly created session. Clients should\n treat this as an opaque token and not infer semantics from its\n contents.\ndocuments_meta : list[DocumentMeta]\n Metadata describing the stored documents, including their\n ``doc_id``, display name, declared format, and parse status.\n\nNotes\n-----\n- Session IDs may expire depending on server configuration (TTL).\n- Clients should persist the ``session_id`` for subsequent API calls."},"SuggestArrayKeysRequest":{"properties":{"node_id":{"type":"string","title":"Node Id","description":"Array node ID."},"top_k":{"type":"integer","maximum":50.0,"minimum":-1.0,"title":"Top K","default":-1}},"additionalProperties":false,"type":"object","required":["node_id"],"title":"SuggestArrayKeysRequest","description":"Request payload for array key suggestion.\n\nAttributes\n----------\nnode_id : str\n Canonical ID of the target array node. The node ID must resolve to an array in at least\n one document.\ntop_k : int\n Maximum number of suggestions to return.\n Constraints:\n - Minimum: -1 (no limit)\n - Maximum: 50"},"SuggestArrayKeysResponse":{"properties":{"node_id":{"type":"string","title":"Node Id"},"suggestions":{"items":{"$ref":"#/components/schemas/KeySuggestion"},"type":"array","title":"Suggestions"}},"additionalProperties":false,"type":"object","required":["node_id","suggestions"],"title":"SuggestArrayKeysResponse","description":"Response payload containing ranked key suggestions.\n\nAttributes\n----------\nnode_id : str\n The array node ID that was analyzed. Echoed from the request for\n convenience and client-side validation.\nsuggestions : list[KeySuggestion]\n Ranked list of candidate keys, ordered by descending confidence\n score. The list may be empty if no suitable keys were detected."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValuePresence":{"properties":{"present":{"type":"boolean","title":"Present"},"value":{"anyOf":[{},{"type":"null"}],"title":"Value"},"value_type":{"anyOf":[{"type":"string","enum":["object","array","string","number","boolean","null"]},{"type":"null"}],"title":"Value Type","description":"Normalized JSON type label."}},"additionalProperties":false,"type":"object","required":["present"],"title":"ValuePresence","description":"Per-document presence/value information for a single node.\n\nThis structure answers: for a given diff node (identified by its canonical\nnode ID), what does each document contain?\n\nAttributes\n----------\npresent : bool\n Whether the node exists in the document.\n - False means the node does not exist.\n - True means the node exists (even if the value is JSON null).\nvalue : Any | None\n The value at the node, when embedded in the response.\n Interpretation rules:\n - present=False always means \"missing\" regardless of `value`.\n - present=True and value=None can mean either:\n (a) the value is JSON null, or\n (b) the backend intentionally omitted a container value (object/array)\n to keep payloads small.\n Use `value_type` to tell these apart. Do not use the node's `kind`:\n at a type-error node the documents disagree on type, so `kind` does\n not describe any single document.\nvalue_type : JsonType | None\n Normalized JSON type label for the value.\n - When present=False, this is typically None.\n - When present=True, this is one of the JsonType literals.\n\nNotes\n-----\nImplementations commonly omit `value` for container nodes (object/array).\nThe exception is a type-error node, which has no children to merge through\nand therefore embeds its container values so a selection can resolve it."}}}} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e67a25..280b00f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { "@floating-ui/react": "^0.27.18", "@tanstack/react-query": "^5.90.21", diff --git a/frontend/package.json b/frontend/package.json index 35c1944..6c47ff0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/api/generated/core/OpenAPI.ts b/frontend/src/api/generated/core/OpenAPI.ts index 0cfae5d..a0a9ed4 100644 --- a/frontend/src/api/generated/core/OpenAPI.ts +++ b/frontend/src/api/generated/core/OpenAPI.ts @@ -21,7 +21,7 @@ export type OpenAPIConfig = { export const OpenAPI: OpenAPIConfig = { BASE: '', - VERSION: '0.1.0', + VERSION: '1.0.0', WITH_CREDENTIALS: false, CREDENTIALS: 'include', TOKEN: undefined,