agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
"""Frozen ``PluginEcosystemBridge/v1`` source contract.
|
|
2
|
+
|
|
3
|
+
An ecosystem bridge gives KsADK one lifecycle vocabulary while leaving Codex
|
|
4
|
+
and DeepSeek Harness in charge of their native
|
|
5
|
+
plugin ABI. The contract is deliberately declarative: it does not import
|
|
6
|
+
third-party plugin code, run install scripts, translate Agent loops, or grant
|
|
7
|
+
permissions. Inspection may report unknown versions and undeclared
|
|
8
|
+
permissions, but planning such an external transition fails closed.
|
|
9
|
+
|
|
10
|
+
The lifecycle is split into read-only discovery (``describe``/``probe``/
|
|
11
|
+
``inspect``), an immutable transition (``plan``/``stage``/``commit``), and
|
|
12
|
+
truthful observed state (``reconcile``/``rollback``/``dispose``). External
|
|
13
|
+
plugins without an explicit permission declaration are rejected fail-closed.
|
|
14
|
+
Secrets may only cross the boundary as references.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
from typing import Annotated, Literal, Protocol, cast, runtime_checkable
|
|
21
|
+
from urllib.parse import parse_qsl, urlsplit
|
|
22
|
+
|
|
23
|
+
from pydantic import AwareDatetime, ConfigDict, Field, RootModel, field_validator, model_validator
|
|
24
|
+
|
|
25
|
+
from ksadk.plugins.contracts import PluginContractModel
|
|
26
|
+
|
|
27
|
+
PluginEcosystem = Literal["codex", "dsh"]
|
|
28
|
+
PluginIntegrationMode = Literal["bridged", "linked"]
|
|
29
|
+
PluginSupportMaturity = Literal[
|
|
30
|
+
"detected",
|
|
31
|
+
"linked-ready",
|
|
32
|
+
"bridged-ready",
|
|
33
|
+
"unsupported",
|
|
34
|
+
"experimental",
|
|
35
|
+
]
|
|
36
|
+
PluginDesiredState = Literal["disabled", "enabled"]
|
|
37
|
+
PluginObservedState = Literal[
|
|
38
|
+
"resolved",
|
|
39
|
+
"admitted",
|
|
40
|
+
"staged",
|
|
41
|
+
"starting",
|
|
42
|
+
"ready",
|
|
43
|
+
"degraded",
|
|
44
|
+
"failed",
|
|
45
|
+
"draining",
|
|
46
|
+
"stopped",
|
|
47
|
+
"disposed",
|
|
48
|
+
"rejected",
|
|
49
|
+
]
|
|
50
|
+
BridgeAction = Literal[
|
|
51
|
+
"describe",
|
|
52
|
+
"probe",
|
|
53
|
+
"inspect",
|
|
54
|
+
"plan",
|
|
55
|
+
"stage",
|
|
56
|
+
"commit",
|
|
57
|
+
"reconcile",
|
|
58
|
+
"rollback",
|
|
59
|
+
"dispose",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
_ID = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$")
|
|
63
|
+
_SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$")
|
|
64
|
+
_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
65
|
+
_REF = re.compile(r"^[a-z][a-z0-9+.-]*://\S+$")
|
|
66
|
+
_SECRET_REF = re.compile(r"^(?:secret|env|credential|vault)://\S+$")
|
|
67
|
+
_PROTOCOL = re.compile(r"^[a-z][a-z0-9._-]*/v[1-9][0-9]*$")
|
|
68
|
+
_SENSITIVE_QUERY_KEY = re.compile(r"(?:secret|password|token|api[_-]?key)", re.IGNORECASE)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _validate_id(value: str, *, field: str) -> str:
|
|
72
|
+
if not _ID.fullmatch(value):
|
|
73
|
+
raise ValueError(f"{field} must be a lowercase qualified id")
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _validate_semver(value: str, *, field: str) -> str:
|
|
78
|
+
if not _SEMVER.fullmatch(value):
|
|
79
|
+
raise ValueError(f"{field} must use an exact semantic version")
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _validate_digest(value: str) -> str:
|
|
84
|
+
if not _DIGEST.fullmatch(value):
|
|
85
|
+
raise ValueError("digest must be a lowercase sha256: digest")
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _validate_ref(value: str, *, field: str) -> str:
|
|
90
|
+
if not _REF.fullmatch(value):
|
|
91
|
+
raise ValueError(f"{field} must be an absolute typed reference")
|
|
92
|
+
parsed = urlsplit(value)
|
|
93
|
+
if parsed.username is not None or parsed.password is not None:
|
|
94
|
+
raise ValueError(f"{field} must not embed credentials")
|
|
95
|
+
if any(_SENSITIVE_QUERY_KEY.search(key) for key, _ in parse_qsl(parsed.query)):
|
|
96
|
+
raise ValueError(f"{field} must not embed secret query parameters")
|
|
97
|
+
return value
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _unique(value: tuple[str, ...], *, field: str) -> tuple[str, ...]:
|
|
101
|
+
if any(not item for item in value):
|
|
102
|
+
raise ValueError(f"{field} entries must not be empty")
|
|
103
|
+
if len(value) != len(set(value)):
|
|
104
|
+
raise ValueError(f"{field} entries must be unique")
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class BridgeHostRequirement(PluginContractModel):
|
|
109
|
+
host_id: str = Field(min_length=2, max_length=128)
|
|
110
|
+
version_constraint: str = Field(min_length=1, max_length=128)
|
|
111
|
+
protocol: str = Field(min_length=3, max_length=128)
|
|
112
|
+
protocol_constraint: str = Field(min_length=1, max_length=128)
|
|
113
|
+
|
|
114
|
+
@field_validator("host_id")
|
|
115
|
+
@classmethod
|
|
116
|
+
def validate_host_id(cls, value: str) -> str:
|
|
117
|
+
return _validate_id(value, field="hostId")
|
|
118
|
+
|
|
119
|
+
@field_validator("protocol")
|
|
120
|
+
@classmethod
|
|
121
|
+
def validate_protocol(cls, value: str) -> str:
|
|
122
|
+
if not _PROTOCOL.fullmatch(value):
|
|
123
|
+
raise ValueError("protocol must be a versioned protocol id")
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BridgeHostObservation(PluginContractModel):
|
|
128
|
+
host_id: str = Field(min_length=2, max_length=128)
|
|
129
|
+
available: bool
|
|
130
|
+
version: str | None = None
|
|
131
|
+
protocol: str | None = Field(default=None, min_length=3, max_length=128)
|
|
132
|
+
protocol_version: str | None = None
|
|
133
|
+
digest: str | None = None
|
|
134
|
+
|
|
135
|
+
@field_validator("host_id")
|
|
136
|
+
@classmethod
|
|
137
|
+
def validate_host_id(cls, value: str) -> str:
|
|
138
|
+
return _validate_id(value, field="hostId")
|
|
139
|
+
|
|
140
|
+
@field_validator("version", "protocol_version")
|
|
141
|
+
@classmethod
|
|
142
|
+
def validate_optional_version(cls, value: str | None) -> str | None:
|
|
143
|
+
if value is not None:
|
|
144
|
+
return _validate_semver(value, field="host version")
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
@field_validator("protocol")
|
|
148
|
+
@classmethod
|
|
149
|
+
def validate_optional_protocol(cls, value: str | None) -> str | None:
|
|
150
|
+
if value is not None and not _PROTOCOL.fullmatch(value):
|
|
151
|
+
raise ValueError("host protocol must be a versioned protocol id")
|
|
152
|
+
return value
|
|
153
|
+
|
|
154
|
+
@field_validator("digest")
|
|
155
|
+
@classmethod
|
|
156
|
+
def validate_optional_digest(cls, value: str | None) -> str | None:
|
|
157
|
+
if value is not None:
|
|
158
|
+
return _validate_digest(value)
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
@model_validator(mode="after")
|
|
162
|
+
def validate_observation(self) -> "BridgeHostObservation":
|
|
163
|
+
trace = (self.version, self.protocol, self.protocol_version, self.digest)
|
|
164
|
+
if self.available and any(item is None for item in trace):
|
|
165
|
+
raise ValueError("available host observations require version, protocol, and digest")
|
|
166
|
+
if not self.available and any(item is not None for item in trace):
|
|
167
|
+
raise ValueError("unavailable host observations cannot claim host identity facts")
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class BridgeDescriptor(PluginContractModel):
|
|
172
|
+
descriptor_format: Literal["ksadk.plugin-ecosystem-bridge-descriptor/v1"] = (
|
|
173
|
+
"ksadk.plugin-ecosystem-bridge-descriptor/v1"
|
|
174
|
+
)
|
|
175
|
+
bridge_id: str = Field(min_length=3, max_length=128)
|
|
176
|
+
bridge_version: str
|
|
177
|
+
bridge_digest: str
|
|
178
|
+
ecosystem: PluginEcosystem
|
|
179
|
+
integration_mode: PluginIntegrationMode
|
|
180
|
+
maturity: PluginSupportMaturity
|
|
181
|
+
host_requirement: BridgeHostRequirement | None
|
|
182
|
+
supported_actions: tuple[BridgeAction, ...]
|
|
183
|
+
|
|
184
|
+
@field_validator("bridge_id")
|
|
185
|
+
@classmethod
|
|
186
|
+
def validate_bridge_id(cls, value: str) -> str:
|
|
187
|
+
return _validate_id(value, field="bridgeId")
|
|
188
|
+
|
|
189
|
+
@field_validator("bridge_version")
|
|
190
|
+
@classmethod
|
|
191
|
+
def validate_bridge_version(cls, value: str) -> str:
|
|
192
|
+
return _validate_semver(value, field="bridge version")
|
|
193
|
+
|
|
194
|
+
@field_validator("bridge_digest")
|
|
195
|
+
@classmethod
|
|
196
|
+
def validate_bridge_digest(cls, value: str) -> str:
|
|
197
|
+
return _validate_digest(value)
|
|
198
|
+
|
|
199
|
+
@field_validator("supported_actions")
|
|
200
|
+
@classmethod
|
|
201
|
+
def validate_actions(cls, value: tuple[BridgeAction, ...]) -> tuple[BridgeAction, ...]:
|
|
202
|
+
if not value or len(value) != len(set(value)):
|
|
203
|
+
raise ValueError("supportedActions must be non-empty and unique")
|
|
204
|
+
return value
|
|
205
|
+
|
|
206
|
+
@model_validator(mode="after")
|
|
207
|
+
def validate_mode(self) -> "BridgeDescriptor":
|
|
208
|
+
if self.host_requirement is None:
|
|
209
|
+
raise ValueError("bridged and linked bridges require an external host")
|
|
210
|
+
if self.maturity == "bridged-ready" and self.integration_mode != "bridged":
|
|
211
|
+
raise ValueError("bridged-ready maturity requires bridged integration")
|
|
212
|
+
if self.maturity == "linked-ready" and self.integration_mode != "linked":
|
|
213
|
+
raise ValueError("linked-ready maturity requires linked integration")
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class BridgeDescribeRequest(PluginContractModel):
|
|
218
|
+
request_format: Literal["ksadk.bridge-describe/v1"] = "ksadk.bridge-describe/v1"
|
|
219
|
+
ecosystem: PluginEcosystem
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class BridgeDescribeResult(PluginContractModel):
|
|
223
|
+
result_format: Literal["ksadk.bridge-describe-result/v1"] = "ksadk.bridge-describe-result/v1"
|
|
224
|
+
descriptor: BridgeDescriptor
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class PluginManifestCandidate(PluginContractModel):
|
|
228
|
+
ecosystem: PluginEcosystem
|
|
229
|
+
integration_mode: PluginIntegrationMode
|
|
230
|
+
maturity: PluginSupportMaturity
|
|
231
|
+
manifest_kind: Literal["codex-plugin", "dsh-bundle"]
|
|
232
|
+
manifest_ref: str = Field(min_length=4, max_length=4096)
|
|
233
|
+
manifest_digest: str
|
|
234
|
+
|
|
235
|
+
@field_validator("manifest_ref")
|
|
236
|
+
@classmethod
|
|
237
|
+
def validate_manifest_ref(cls, value: str) -> str:
|
|
238
|
+
return _validate_ref(value, field="manifestRef")
|
|
239
|
+
|
|
240
|
+
@field_validator("manifest_digest")
|
|
241
|
+
@classmethod
|
|
242
|
+
def validate_manifest_digest(cls, value: str) -> str:
|
|
243
|
+
return _validate_digest(value)
|
|
244
|
+
|
|
245
|
+
@model_validator(mode="after")
|
|
246
|
+
def validate_candidate_mode(self) -> "PluginManifestCandidate":
|
|
247
|
+
expected_kind = {"codex": "codex-plugin", "dsh": "dsh-bundle"}[
|
|
248
|
+
self.ecosystem
|
|
249
|
+
]
|
|
250
|
+
if self.manifest_kind != expected_kind:
|
|
251
|
+
raise ValueError("manifestKind does not match the detected ecosystem")
|
|
252
|
+
return self
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class BridgeProbeRequest(PluginContractModel):
|
|
256
|
+
request_format: Literal["ksadk.bridge-probe/v1"] = "ksadk.bridge-probe/v1"
|
|
257
|
+
source_ref: str = Field(min_length=4, max_length=4096)
|
|
258
|
+
source_digest: str
|
|
259
|
+
selected_manifest_ref: str | None = Field(default=None, min_length=4, max_length=4096)
|
|
260
|
+
|
|
261
|
+
@field_validator("source_ref", "selected_manifest_ref")
|
|
262
|
+
@classmethod
|
|
263
|
+
def validate_source_ref(cls, value: str | None) -> str | None:
|
|
264
|
+
if value is not None:
|
|
265
|
+
return _validate_ref(value, field="source reference")
|
|
266
|
+
return value
|
|
267
|
+
|
|
268
|
+
@field_validator("source_digest")
|
|
269
|
+
@classmethod
|
|
270
|
+
def validate_source_digest(cls, value: str) -> str:
|
|
271
|
+
return _validate_digest(value)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class BridgeRejection(PluginContractModel):
|
|
275
|
+
rejection_format: Literal["ksadk.bridge-rejection/v1"] = "ksadk.bridge-rejection/v1"
|
|
276
|
+
action: BridgeAction
|
|
277
|
+
code: Literal[
|
|
278
|
+
"ambiguous_manifest",
|
|
279
|
+
"permissions_undeclared",
|
|
280
|
+
"host_unavailable",
|
|
281
|
+
"host_incompatible",
|
|
282
|
+
"digest_mismatch",
|
|
283
|
+
"unsupported",
|
|
284
|
+
]
|
|
285
|
+
retryable: bool
|
|
286
|
+
message: str = Field(min_length=1, max_length=1024)
|
|
287
|
+
host: BridgeHostObservation | None = None
|
|
288
|
+
|
|
289
|
+
@model_validator(mode="after")
|
|
290
|
+
def validate_host_rejection(self) -> "BridgeRejection":
|
|
291
|
+
if self.code in {"host_unavailable", "host_incompatible"} and self.host is None:
|
|
292
|
+
raise ValueError("host rejection requires a typed host observation")
|
|
293
|
+
if self.code == "host_unavailable" and self.host is not None and self.host.available:
|
|
294
|
+
raise ValueError("host_unavailable cannot claim that the host is available")
|
|
295
|
+
return self
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class BridgeProbeResult(PluginContractModel):
|
|
299
|
+
result_format: Literal["ksadk.bridge-probe-result/v1"] = "ksadk.bridge-probe-result/v1"
|
|
300
|
+
candidates: tuple[PluginManifestCandidate, ...] = ()
|
|
301
|
+
selection_required: bool
|
|
302
|
+
selected_manifest_ref: str | None = Field(default=None, min_length=4, max_length=4096)
|
|
303
|
+
rejection: BridgeRejection | None = None
|
|
304
|
+
|
|
305
|
+
@field_validator("selected_manifest_ref")
|
|
306
|
+
@classmethod
|
|
307
|
+
def validate_selected_manifest_ref(cls, value: str | None) -> str | None:
|
|
308
|
+
if value is not None:
|
|
309
|
+
return _validate_ref(value, field="selectedManifestRef")
|
|
310
|
+
return value
|
|
311
|
+
|
|
312
|
+
@model_validator(mode="after")
|
|
313
|
+
def validate_candidates(self) -> "BridgeProbeResult":
|
|
314
|
+
refs = [item.manifest_ref for item in self.candidates]
|
|
315
|
+
if len(refs) != len(set(refs)):
|
|
316
|
+
raise ValueError("probe candidates must have unique manifestRef values")
|
|
317
|
+
if not refs:
|
|
318
|
+
if self.rejection is None or self.rejection.action != "probe":
|
|
319
|
+
raise ValueError("empty probe requires a typed probe rejection")
|
|
320
|
+
if self.selection_required or self.selected_manifest_ref is not None:
|
|
321
|
+
raise ValueError("rejected probe cannot claim a manifest selection")
|
|
322
|
+
return self
|
|
323
|
+
if self.rejection is not None:
|
|
324
|
+
raise ValueError("successful probe candidates cannot carry a rejection")
|
|
325
|
+
if self.selection_required and self.selected_manifest_ref is not None:
|
|
326
|
+
raise ValueError("selectionRequired cannot also claim a selected manifest")
|
|
327
|
+
if not self.selection_required:
|
|
328
|
+
if self.selected_manifest_ref is None:
|
|
329
|
+
raise ValueError("a completed probe requires selectedManifestRef")
|
|
330
|
+
if self.selected_manifest_ref not in refs:
|
|
331
|
+
raise ValueError("selectedManifestRef must identify a probe candidate")
|
|
332
|
+
return self
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class BridgeProbeExchange(PluginContractModel):
|
|
336
|
+
fixture_kind: Literal["probe"] = "probe"
|
|
337
|
+
request: BridgeProbeRequest
|
|
338
|
+
result: BridgeProbeResult
|
|
339
|
+
|
|
340
|
+
@model_validator(mode="after")
|
|
341
|
+
def validate_explicit_selection(self) -> "BridgeProbeExchange":
|
|
342
|
+
candidate_refs = {item.manifest_ref for item in self.result.candidates}
|
|
343
|
+
if not candidate_refs:
|
|
344
|
+
return self
|
|
345
|
+
selected = self.request.selected_manifest_ref
|
|
346
|
+
if len(candidate_refs) > 1 and selected is None:
|
|
347
|
+
if not self.result.selection_required or self.result.selected_manifest_ref is not None:
|
|
348
|
+
raise ValueError("multiple manifests require explicit selection")
|
|
349
|
+
else:
|
|
350
|
+
if selected is None:
|
|
351
|
+
selected = next(iter(candidate_refs))
|
|
352
|
+
if selected not in candidate_refs:
|
|
353
|
+
raise ValueError("requested manifest selection was not detected")
|
|
354
|
+
if self.result.selection_required or self.result.selected_manifest_ref != selected:
|
|
355
|
+
raise ValueError("probe result must preserve the explicit manifest selection")
|
|
356
|
+
return self
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class EcosystemPluginDescriptor(PluginContractModel):
|
|
360
|
+
descriptor_format: Literal["ksadk.ecosystem-plugin-descriptor/v1"] = (
|
|
361
|
+
"ksadk.ecosystem-plugin-descriptor/v1"
|
|
362
|
+
)
|
|
363
|
+
ecosystem: PluginEcosystem
|
|
364
|
+
plugin_id: str = Field(min_length=2, max_length=256)
|
|
365
|
+
plugin_version: str | None = None
|
|
366
|
+
integration_mode: PluginIntegrationMode
|
|
367
|
+
maturity: PluginSupportMaturity
|
|
368
|
+
source_ref: str = Field(min_length=4, max_length=4096)
|
|
369
|
+
artifact_digest: str
|
|
370
|
+
manifest_ref: str = Field(min_length=4, max_length=4096)
|
|
371
|
+
manifest_digest: str
|
|
372
|
+
host_requirement: BridgeHostRequirement | None
|
|
373
|
+
permissions_declared: bool
|
|
374
|
+
install_permissions: tuple[str, ...] = ()
|
|
375
|
+
runtime_permissions: tuple[str, ...] = ()
|
|
376
|
+
auth_scopes: tuple[str, ...] = ()
|
|
377
|
+
secret_refs: tuple[str, ...] = ()
|
|
378
|
+
components: tuple[str, ...] = ()
|
|
379
|
+
|
|
380
|
+
@field_validator("plugin_id")
|
|
381
|
+
@classmethod
|
|
382
|
+
def validate_plugin_id(cls, value: str) -> str:
|
|
383
|
+
return _validate_id(value, field="pluginId")
|
|
384
|
+
|
|
385
|
+
@field_validator("plugin_version")
|
|
386
|
+
@classmethod
|
|
387
|
+
def validate_plugin_version(cls, value: str | None) -> str | None:
|
|
388
|
+
if value is not None:
|
|
389
|
+
return _validate_semver(value, field="plugin version")
|
|
390
|
+
return value
|
|
391
|
+
|
|
392
|
+
@field_validator("source_ref", "manifest_ref")
|
|
393
|
+
@classmethod
|
|
394
|
+
def validate_descriptor_ref(cls, value: str) -> str:
|
|
395
|
+
return _validate_ref(value, field="descriptor reference")
|
|
396
|
+
|
|
397
|
+
@field_validator("artifact_digest", "manifest_digest")
|
|
398
|
+
@classmethod
|
|
399
|
+
def validate_descriptor_digest(cls, value: str) -> str:
|
|
400
|
+
return _validate_digest(value)
|
|
401
|
+
|
|
402
|
+
@field_validator("install_permissions", "runtime_permissions", "auth_scopes", "components")
|
|
403
|
+
@classmethod
|
|
404
|
+
def validate_unique_values(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
405
|
+
return _unique(value, field="descriptor list")
|
|
406
|
+
|
|
407
|
+
@field_validator("secret_refs")
|
|
408
|
+
@classmethod
|
|
409
|
+
def validate_secret_refs(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
410
|
+
_unique(value, field="secretRefs")
|
|
411
|
+
if any(not _SECRET_REF.fullmatch(item) for item in value):
|
|
412
|
+
raise ValueError("secretRefs may contain references only")
|
|
413
|
+
return value
|
|
414
|
+
|
|
415
|
+
@model_validator(mode="after")
|
|
416
|
+
def validate_external_shape(self) -> "EcosystemPluginDescriptor":
|
|
417
|
+
if self.integration_mode != "native" and self.host_requirement is None:
|
|
418
|
+
raise ValueError("bridged and linked plugins require an external host")
|
|
419
|
+
if self.integration_mode == "native" and self.host_requirement is not None:
|
|
420
|
+
raise ValueError("native plugins cannot require an external host")
|
|
421
|
+
return self
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class BridgeInspectRequest(PluginContractModel):
|
|
425
|
+
request_format: Literal["ksadk.bridge-inspect/v1"] = "ksadk.bridge-inspect/v1"
|
|
426
|
+
bridge_id: str = Field(min_length=3, max_length=128)
|
|
427
|
+
source_ref: str = Field(min_length=4, max_length=4096)
|
|
428
|
+
source_digest: str
|
|
429
|
+
candidate: PluginManifestCandidate
|
|
430
|
+
|
|
431
|
+
@field_validator("bridge_id")
|
|
432
|
+
@classmethod
|
|
433
|
+
def validate_bridge_id(cls, value: str) -> str:
|
|
434
|
+
return _validate_id(value, field="bridgeId")
|
|
435
|
+
|
|
436
|
+
@field_validator("source_ref")
|
|
437
|
+
@classmethod
|
|
438
|
+
def validate_source_ref(cls, value: str) -> str:
|
|
439
|
+
return _validate_ref(value, field="sourceRef")
|
|
440
|
+
|
|
441
|
+
@field_validator("source_digest")
|
|
442
|
+
@classmethod
|
|
443
|
+
def validate_source_digest(cls, value: str) -> str:
|
|
444
|
+
return _validate_digest(value)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class BridgeInspectResult(PluginContractModel):
|
|
448
|
+
result_format: Literal["ksadk.bridge-inspect-result/v1"] = "ksadk.bridge-inspect-result/v1"
|
|
449
|
+
status: Literal["accepted", "rejected"]
|
|
450
|
+
descriptor: EcosystemPluginDescriptor | None = None
|
|
451
|
+
rejection: BridgeRejection | None = None
|
|
452
|
+
|
|
453
|
+
@model_validator(mode="after")
|
|
454
|
+
def validate_outcome(self) -> "BridgeInspectResult":
|
|
455
|
+
if self.status == "accepted" and (self.descriptor is None or self.rejection is not None):
|
|
456
|
+
raise ValueError("accepted inspection requires descriptor only")
|
|
457
|
+
if self.status == "rejected" and (self.rejection is None or self.descriptor is not None):
|
|
458
|
+
raise ValueError("rejected inspection requires rejection only")
|
|
459
|
+
return self
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class BridgeInspectExchange(PluginContractModel):
|
|
463
|
+
fixture_kind: Literal["rejection"] = "rejection"
|
|
464
|
+
request: BridgeInspectRequest
|
|
465
|
+
result: BridgeInspectResult
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class BridgePlanRequest(PluginContractModel):
|
|
469
|
+
request_format: Literal["ksadk.bridge-plan/v1"] = "ksadk.bridge-plan/v1"
|
|
470
|
+
descriptor: EcosystemPluginDescriptor
|
|
471
|
+
operation: Literal["install", "update", "enable", "disable", "uninstall"]
|
|
472
|
+
desired_state: PluginDesiredState
|
|
473
|
+
bound_references: tuple[str, ...] = ()
|
|
474
|
+
authorization_ref: str = Field(min_length=4, max_length=2048)
|
|
475
|
+
accept_undeclared_permissions: bool = False
|
|
476
|
+
|
|
477
|
+
@field_validator("bound_references")
|
|
478
|
+
@classmethod
|
|
479
|
+
def validate_bound_references(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
480
|
+
return _unique(value, field="boundReferences")
|
|
481
|
+
|
|
482
|
+
@field_validator("authorization_ref")
|
|
483
|
+
@classmethod
|
|
484
|
+
def validate_authorization_ref(cls, value: str) -> str:
|
|
485
|
+
return _validate_ref(value, field="authorizationRef")
|
|
486
|
+
|
|
487
|
+
@model_validator(mode="after")
|
|
488
|
+
def validate_admission(self) -> "BridgePlanRequest":
|
|
489
|
+
if self.descriptor.plugin_version is None:
|
|
490
|
+
raise ValueError("planning requires an exact inspected plugin version")
|
|
491
|
+
permissions_undeclared = (
|
|
492
|
+
self.descriptor.integration_mode != "native"
|
|
493
|
+
and not self.descriptor.permissions_declared
|
|
494
|
+
)
|
|
495
|
+
if permissions_undeclared and not self.accept_undeclared_permissions:
|
|
496
|
+
raise ValueError(
|
|
497
|
+
"external plugin permissions are undeclared; explicit risk acceptance is required"
|
|
498
|
+
)
|
|
499
|
+
if permissions_undeclared and (
|
|
500
|
+
not self.descriptor.install_permissions
|
|
501
|
+
or "process:host-user" not in self.descriptor.runtime_permissions
|
|
502
|
+
):
|
|
503
|
+
raise ValueError(
|
|
504
|
+
"undeclared external permissions require conservative install permissions "
|
|
505
|
+
"and process:host-user runtime disclosure"
|
|
506
|
+
)
|
|
507
|
+
return self
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class BridgeTransitionPlan(PluginContractModel):
|
|
511
|
+
plan_format: Literal["ksadk.bridge-transition-plan/v1"] = "ksadk.bridge-transition-plan/v1"
|
|
512
|
+
plan_id: str = Field(min_length=1, max_length=256)
|
|
513
|
+
bridge_id: str = Field(min_length=3, max_length=128)
|
|
514
|
+
bridge_version: str
|
|
515
|
+
bridge_digest: str
|
|
516
|
+
ecosystem: PluginEcosystem
|
|
517
|
+
plugin_id: str = Field(min_length=2, max_length=256)
|
|
518
|
+
plugin_version: str
|
|
519
|
+
integration_mode: PluginIntegrationMode
|
|
520
|
+
operation: Literal["install", "update", "enable", "disable", "uninstall"]
|
|
521
|
+
desired_state: PluginDesiredState
|
|
522
|
+
descriptor_digest: str
|
|
523
|
+
artifact_digest: str
|
|
524
|
+
manifest_digest: str
|
|
525
|
+
install_permissions: tuple[str, ...] = ()
|
|
526
|
+
runtime_permissions: tuple[str, ...] = ()
|
|
527
|
+
auth_scopes: tuple[str, ...] = ()
|
|
528
|
+
authorization_ref: str = Field(min_length=4, max_length=2048)
|
|
529
|
+
permissions_declared: bool
|
|
530
|
+
undeclared_permissions_accepted: bool = False
|
|
531
|
+
bound_references: tuple[str, ...] = ()
|
|
532
|
+
host_requirement: BridgeHostRequirement | None
|
|
533
|
+
rollback_point_ref: str | None = Field(default=None, min_length=4, max_length=2048)
|
|
534
|
+
rollback_point_digest: str | None = None
|
|
535
|
+
plan_digest: str
|
|
536
|
+
created_at: AwareDatetime
|
|
537
|
+
|
|
538
|
+
@field_validator("bridge_id", "plugin_id")
|
|
539
|
+
@classmethod
|
|
540
|
+
def validate_ids(cls, value: str) -> str:
|
|
541
|
+
return _validate_id(value, field="plan id")
|
|
542
|
+
|
|
543
|
+
@field_validator("bridge_version", "plugin_version")
|
|
544
|
+
@classmethod
|
|
545
|
+
def validate_versions(cls, value: str) -> str:
|
|
546
|
+
return _validate_semver(value, field="plan version")
|
|
547
|
+
|
|
548
|
+
@field_validator(
|
|
549
|
+
"bridge_digest",
|
|
550
|
+
"descriptor_digest",
|
|
551
|
+
"artifact_digest",
|
|
552
|
+
"manifest_digest",
|
|
553
|
+
"rollback_point_digest",
|
|
554
|
+
"plan_digest",
|
|
555
|
+
)
|
|
556
|
+
@classmethod
|
|
557
|
+
def validate_digests(cls, value: str | None) -> str | None:
|
|
558
|
+
if value is not None:
|
|
559
|
+
return _validate_digest(value)
|
|
560
|
+
return value
|
|
561
|
+
|
|
562
|
+
@field_validator("authorization_ref", "rollback_point_ref")
|
|
563
|
+
@classmethod
|
|
564
|
+
def validate_plan_refs(cls, value: str | None) -> str | None:
|
|
565
|
+
if value is not None:
|
|
566
|
+
return _validate_ref(value, field="plan reference")
|
|
567
|
+
return value
|
|
568
|
+
|
|
569
|
+
@field_validator(
|
|
570
|
+
"install_permissions", "runtime_permissions", "auth_scopes", "bound_references"
|
|
571
|
+
)
|
|
572
|
+
@classmethod
|
|
573
|
+
def validate_plan_lists(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
574
|
+
return _unique(value, field="plan list")
|
|
575
|
+
|
|
576
|
+
@model_validator(mode="after")
|
|
577
|
+
def validate_rollback_pair(self) -> "BridgeTransitionPlan":
|
|
578
|
+
if (self.rollback_point_ref is None) != (self.rollback_point_digest is None):
|
|
579
|
+
raise ValueError("rollback point reference and digest must appear together")
|
|
580
|
+
if self.integration_mode != "native" and self.host_requirement is None:
|
|
581
|
+
raise ValueError("external transition plans require a host")
|
|
582
|
+
if self.permissions_declared and self.undeclared_permissions_accepted:
|
|
583
|
+
raise ValueError(
|
|
584
|
+
"undeclaredPermissionsAccepted must be false when permissions are declared"
|
|
585
|
+
)
|
|
586
|
+
if (
|
|
587
|
+
self.integration_mode != "native"
|
|
588
|
+
and not self.permissions_declared
|
|
589
|
+
and not self.undeclared_permissions_accepted
|
|
590
|
+
):
|
|
591
|
+
raise ValueError("external undeclared permissions require recorded risk acceptance")
|
|
592
|
+
return self
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class BridgePlanResult(PluginContractModel):
|
|
596
|
+
result_format: Literal["ksadk.bridge-plan-result/v1"] = "ksadk.bridge-plan-result/v1"
|
|
597
|
+
plan: BridgeTransitionPlan
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
class BridgeStageRequest(PluginContractModel):
|
|
601
|
+
request_format: Literal["ksadk.bridge-stage/v1"] = "ksadk.bridge-stage/v1"
|
|
602
|
+
plan: BridgeTransitionPlan
|
|
603
|
+
expected_plan_digest: str
|
|
604
|
+
|
|
605
|
+
@field_validator("expected_plan_digest")
|
|
606
|
+
@classmethod
|
|
607
|
+
def validate_expected_plan_digest(cls, value: str) -> str:
|
|
608
|
+
return _validate_digest(value)
|
|
609
|
+
|
|
610
|
+
@model_validator(mode="after")
|
|
611
|
+
def validate_plan_digest(self) -> "BridgeStageRequest":
|
|
612
|
+
if self.expected_plan_digest != self.plan.plan_digest:
|
|
613
|
+
raise ValueError("expectedPlanDigest does not match the immutable plan")
|
|
614
|
+
return self
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class BridgeStageResult(PluginContractModel):
|
|
618
|
+
result_format: Literal["ksadk.bridge-stage-result/v1"] = "ksadk.bridge-stage-result/v1"
|
|
619
|
+
stage_id: str = Field(min_length=1, max_length=256)
|
|
620
|
+
plan_id: str = Field(min_length=1, max_length=256)
|
|
621
|
+
plan_digest: str
|
|
622
|
+
bridge_version: str
|
|
623
|
+
bridge_digest: str
|
|
624
|
+
host: BridgeHostObservation
|
|
625
|
+
artifact_digest: str
|
|
626
|
+
manifest_digest: str
|
|
627
|
+
native_stage_ref: str = Field(min_length=4, max_length=2048)
|
|
628
|
+
native_stage_digest: str
|
|
629
|
+
staged_at: AwareDatetime
|
|
630
|
+
|
|
631
|
+
@field_validator(
|
|
632
|
+
"plan_digest", "bridge_digest", "artifact_digest", "manifest_digest", "native_stage_digest"
|
|
633
|
+
)
|
|
634
|
+
@classmethod
|
|
635
|
+
def validate_stage_digest(cls, value: str) -> str:
|
|
636
|
+
return _validate_digest(value)
|
|
637
|
+
|
|
638
|
+
@field_validator("bridge_version")
|
|
639
|
+
@classmethod
|
|
640
|
+
def validate_bridge_version(cls, value: str) -> str:
|
|
641
|
+
return _validate_semver(value, field="bridge version")
|
|
642
|
+
|
|
643
|
+
@field_validator("native_stage_ref")
|
|
644
|
+
@classmethod
|
|
645
|
+
def validate_native_stage_ref(cls, value: str) -> str:
|
|
646
|
+
return _validate_ref(value, field="nativeStageRef")
|
|
647
|
+
|
|
648
|
+
@model_validator(mode="after")
|
|
649
|
+
def validate_host_available(self) -> "BridgeStageResult":
|
|
650
|
+
if not self.host.available:
|
|
651
|
+
raise ValueError("successful stage result requires an available host")
|
|
652
|
+
return self
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
class BridgeCommitRequest(PluginContractModel):
|
|
656
|
+
request_format: Literal["ksadk.bridge-commit/v1"] = "ksadk.bridge-commit/v1"
|
|
657
|
+
stage_id: str = Field(min_length=1, max_length=256)
|
|
658
|
+
plan_id: str = Field(min_length=1, max_length=256)
|
|
659
|
+
plan_digest: str
|
|
660
|
+
native_stage_ref: str = Field(min_length=4, max_length=2048)
|
|
661
|
+
native_stage_digest: str
|
|
662
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
663
|
+
|
|
664
|
+
@field_validator("plan_digest", "native_stage_digest")
|
|
665
|
+
@classmethod
|
|
666
|
+
def validate_commit_digest(cls, value: str) -> str:
|
|
667
|
+
return _validate_digest(value)
|
|
668
|
+
|
|
669
|
+
@field_validator("native_stage_ref")
|
|
670
|
+
@classmethod
|
|
671
|
+
def validate_native_stage_ref(cls, value: str) -> str:
|
|
672
|
+
return _validate_ref(value, field="nativeStageRef")
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
class EcosystemInstallReceipt(PluginContractModel):
|
|
676
|
+
receipt_format: Literal["ksadk.ecosystem-install-receipt/v1"] = (
|
|
677
|
+
"ksadk.ecosystem-install-receipt/v1"
|
|
678
|
+
)
|
|
679
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
680
|
+
plan_id: str = Field(min_length=1, max_length=256)
|
|
681
|
+
plan_digest: str
|
|
682
|
+
stage_id: str = Field(min_length=1, max_length=256)
|
|
683
|
+
ecosystem: PluginEcosystem
|
|
684
|
+
plugin_id: str = Field(min_length=2, max_length=256)
|
|
685
|
+
plugin_version: str
|
|
686
|
+
integration_mode: PluginIntegrationMode
|
|
687
|
+
desired_state: PluginDesiredState
|
|
688
|
+
bridge_id: str = Field(min_length=3, max_length=128)
|
|
689
|
+
bridge_version: str
|
|
690
|
+
bridge_digest: str
|
|
691
|
+
host: BridgeHostObservation
|
|
692
|
+
artifact_digest: str
|
|
693
|
+
manifest_digest: str
|
|
694
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
695
|
+
native_receipt_digest: str
|
|
696
|
+
bound_references: tuple[str, ...] = ()
|
|
697
|
+
committed_at: AwareDatetime
|
|
698
|
+
|
|
699
|
+
@field_validator("plugin_id", "bridge_id")
|
|
700
|
+
@classmethod
|
|
701
|
+
def validate_receipt_ids(cls, value: str) -> str:
|
|
702
|
+
return _validate_id(value, field="receipt id")
|
|
703
|
+
|
|
704
|
+
@field_validator("plugin_version", "bridge_version")
|
|
705
|
+
@classmethod
|
|
706
|
+
def validate_receipt_versions(cls, value: str) -> str:
|
|
707
|
+
return _validate_semver(value, field="receipt version")
|
|
708
|
+
|
|
709
|
+
@field_validator(
|
|
710
|
+
"plan_digest",
|
|
711
|
+
"bridge_digest",
|
|
712
|
+
"artifact_digest",
|
|
713
|
+
"manifest_digest",
|
|
714
|
+
"native_receipt_digest",
|
|
715
|
+
)
|
|
716
|
+
@classmethod
|
|
717
|
+
def validate_receipt_digests(cls, value: str) -> str:
|
|
718
|
+
return _validate_digest(value)
|
|
719
|
+
|
|
720
|
+
@field_validator("native_receipt_ref")
|
|
721
|
+
@classmethod
|
|
722
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
723
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
724
|
+
|
|
725
|
+
@field_validator("bound_references")
|
|
726
|
+
@classmethod
|
|
727
|
+
def validate_bound_references(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
728
|
+
return _unique(value, field="boundReferences")
|
|
729
|
+
|
|
730
|
+
@model_validator(mode="after")
|
|
731
|
+
def validate_host_available(self) -> "EcosystemInstallReceipt":
|
|
732
|
+
if self.integration_mode != "native" and not self.host.available:
|
|
733
|
+
raise ValueError("external install receipt requires an available native host")
|
|
734
|
+
return self
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class BridgeCommitResult(PluginContractModel):
|
|
738
|
+
result_format: Literal["ksadk.bridge-commit-result/v1"] = "ksadk.bridge-commit-result/v1"
|
|
739
|
+
receipt: EcosystemInstallReceipt
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
class BridgeReconcileRequest(PluginContractModel):
|
|
743
|
+
request_format: Literal["ksadk.bridge-reconcile/v1"] = "ksadk.bridge-reconcile/v1"
|
|
744
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
745
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
746
|
+
native_receipt_digest: str
|
|
747
|
+
|
|
748
|
+
@field_validator("native_receipt_ref")
|
|
749
|
+
@classmethod
|
|
750
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
751
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
752
|
+
|
|
753
|
+
@field_validator("native_receipt_digest")
|
|
754
|
+
@classmethod
|
|
755
|
+
def validate_native_receipt_digest(cls, value: str) -> str:
|
|
756
|
+
return _validate_digest(value)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
class EcosystemPluginInventory(PluginContractModel):
|
|
760
|
+
inventory_format: Literal["ksadk.ecosystem-inventory/v1"] = "ksadk.ecosystem-inventory/v1"
|
|
761
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
762
|
+
ecosystem: PluginEcosystem
|
|
763
|
+
plugin_id: str = Field(min_length=2, max_length=256)
|
|
764
|
+
plugin_version: str
|
|
765
|
+
integration_mode: PluginIntegrationMode
|
|
766
|
+
desired_state: PluginDesiredState
|
|
767
|
+
observed_state: PluginObservedState
|
|
768
|
+
maturity: PluginSupportMaturity
|
|
769
|
+
bridge_id: str = Field(min_length=3, max_length=128)
|
|
770
|
+
bridge_version: str
|
|
771
|
+
bridge_digest: str
|
|
772
|
+
host: BridgeHostObservation | None
|
|
773
|
+
artifact_digest: str
|
|
774
|
+
manifest_digest: str
|
|
775
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
776
|
+
native_receipt_digest: str
|
|
777
|
+
bound_references: tuple[str, ...] = ()
|
|
778
|
+
reason_code: str | None = Field(default=None, max_length=128)
|
|
779
|
+
reconciled_at: AwareDatetime
|
|
780
|
+
|
|
781
|
+
@field_validator("plugin_id", "bridge_id")
|
|
782
|
+
@classmethod
|
|
783
|
+
def validate_inventory_ids(cls, value: str) -> str:
|
|
784
|
+
return _validate_id(value, field="inventory id")
|
|
785
|
+
|
|
786
|
+
@field_validator("plugin_version", "bridge_version")
|
|
787
|
+
@classmethod
|
|
788
|
+
def validate_inventory_versions(cls, value: str) -> str:
|
|
789
|
+
return _validate_semver(value, field="inventory version")
|
|
790
|
+
|
|
791
|
+
@field_validator("bridge_digest", "artifact_digest", "manifest_digest", "native_receipt_digest")
|
|
792
|
+
@classmethod
|
|
793
|
+
def validate_inventory_digest(cls, value: str) -> str:
|
|
794
|
+
return _validate_digest(value)
|
|
795
|
+
|
|
796
|
+
@field_validator("native_receipt_ref")
|
|
797
|
+
@classmethod
|
|
798
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
799
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
800
|
+
|
|
801
|
+
@field_validator("bound_references")
|
|
802
|
+
@classmethod
|
|
803
|
+
def validate_bound_references(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
804
|
+
return _unique(value, field="boundReferences")
|
|
805
|
+
|
|
806
|
+
@model_validator(mode="after")
|
|
807
|
+
def validate_observed_truth(self) -> "EcosystemPluginInventory":
|
|
808
|
+
if self.integration_mode != "native" and self.host is None:
|
|
809
|
+
raise ValueError("external inventory requires a host observation")
|
|
810
|
+
if self.host is not None and not self.host.available and self.observed_state == "ready":
|
|
811
|
+
raise ValueError("unavailable host cannot be reconciled as ready")
|
|
812
|
+
if self.observed_state in {"degraded", "failed", "rejected"} and not self.reason_code:
|
|
813
|
+
raise ValueError("unhealthy inventory requires reasonCode")
|
|
814
|
+
return self
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
class BridgeReconcileResult(PluginContractModel):
|
|
818
|
+
result_format: Literal["ksadk.bridge-reconcile-result/v1"] = "ksadk.bridge-reconcile-result/v1"
|
|
819
|
+
inventory: EcosystemPluginInventory
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
class BridgeRollbackRequest(PluginContractModel):
|
|
823
|
+
request_format: Literal["ksadk.bridge-rollback/v1"] = "ksadk.bridge-rollback/v1"
|
|
824
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
825
|
+
rollback_point_ref: str = Field(min_length=4, max_length=2048)
|
|
826
|
+
rollback_point_digest: str
|
|
827
|
+
expected_native_receipt_digest: str
|
|
828
|
+
|
|
829
|
+
@field_validator("rollback_point_ref")
|
|
830
|
+
@classmethod
|
|
831
|
+
def validate_rollback_point_ref(cls, value: str) -> str:
|
|
832
|
+
return _validate_ref(value, field="rollbackPointRef")
|
|
833
|
+
|
|
834
|
+
@field_validator("rollback_point_digest", "expected_native_receipt_digest")
|
|
835
|
+
@classmethod
|
|
836
|
+
def validate_rollback_digest(cls, value: str) -> str:
|
|
837
|
+
return _validate_digest(value)
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
class BridgeRollbackResult(PluginContractModel):
|
|
841
|
+
result_format: Literal["ksadk.bridge-rollback-result/v1"] = "ksadk.bridge-rollback-result/v1"
|
|
842
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
843
|
+
state: Literal["rolled-back"]
|
|
844
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
845
|
+
native_receipt_digest: str
|
|
846
|
+
rolled_back_at: AwareDatetime
|
|
847
|
+
|
|
848
|
+
@field_validator("native_receipt_ref")
|
|
849
|
+
@classmethod
|
|
850
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
851
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
852
|
+
|
|
853
|
+
@field_validator("native_receipt_digest")
|
|
854
|
+
@classmethod
|
|
855
|
+
def validate_native_receipt_digest(cls, value: str) -> str:
|
|
856
|
+
return _validate_digest(value)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
class BridgeDisposeRequest(PluginContractModel):
|
|
860
|
+
request_format: Literal["ksadk.bridge-dispose/v1"] = "ksadk.bridge-dispose/v1"
|
|
861
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
862
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
863
|
+
native_receipt_digest: str
|
|
864
|
+
|
|
865
|
+
@field_validator("native_receipt_ref")
|
|
866
|
+
@classmethod
|
|
867
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
868
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
869
|
+
|
|
870
|
+
@field_validator("native_receipt_digest")
|
|
871
|
+
@classmethod
|
|
872
|
+
def validate_native_receipt_digest(cls, value: str) -> str:
|
|
873
|
+
return _validate_digest(value)
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
class BridgeDisposeResult(PluginContractModel):
|
|
877
|
+
result_format: Literal["ksadk.bridge-dispose-result/v1"] = "ksadk.bridge-dispose-result/v1"
|
|
878
|
+
receipt_id: str = Field(min_length=1, max_length=256)
|
|
879
|
+
state: Literal["disposed"]
|
|
880
|
+
native_receipt_ref: str = Field(min_length=4, max_length=2048)
|
|
881
|
+
native_receipt_digest: str
|
|
882
|
+
disposed_at: AwareDatetime
|
|
883
|
+
|
|
884
|
+
@field_validator("native_receipt_ref")
|
|
885
|
+
@classmethod
|
|
886
|
+
def validate_native_receipt_ref(cls, value: str) -> str:
|
|
887
|
+
return _validate_ref(value, field="nativeReceiptRef")
|
|
888
|
+
|
|
889
|
+
@field_validator("native_receipt_digest")
|
|
890
|
+
@classmethod
|
|
891
|
+
def validate_native_receipt_digest(cls, value: str) -> str:
|
|
892
|
+
return _validate_digest(value)
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class PluginEcosystemBridgeTranscript(PluginContractModel):
|
|
896
|
+
fixture_kind: Literal["lifecycle"] = "lifecycle"
|
|
897
|
+
contract_format: Literal["ksadk.plugin-ecosystem-bridge/v1"] = (
|
|
898
|
+
"ksadk.plugin-ecosystem-bridge/v1"
|
|
899
|
+
)
|
|
900
|
+
describe_request: BridgeDescribeRequest
|
|
901
|
+
describe_result: BridgeDescribeResult
|
|
902
|
+
probe: BridgeProbeExchange
|
|
903
|
+
inspect_request: BridgeInspectRequest
|
|
904
|
+
inspect_result: BridgeInspectResult
|
|
905
|
+
plan_request: BridgePlanRequest
|
|
906
|
+
plan_result: BridgePlanResult
|
|
907
|
+
stage_request: BridgeStageRequest
|
|
908
|
+
stage_result: BridgeStageResult
|
|
909
|
+
commit_request: BridgeCommitRequest
|
|
910
|
+
commit_result: BridgeCommitResult
|
|
911
|
+
reconcile_request: BridgeReconcileRequest
|
|
912
|
+
reconcile_result: BridgeReconcileResult
|
|
913
|
+
rollback_request: BridgeRollbackRequest
|
|
914
|
+
rollback_result: BridgeRollbackResult
|
|
915
|
+
dispose_request: BridgeDisposeRequest
|
|
916
|
+
dispose_result: BridgeDisposeResult
|
|
917
|
+
|
|
918
|
+
@model_validator(mode="after")
|
|
919
|
+
def validate_traceability(self) -> "PluginEcosystemBridgeTranscript":
|
|
920
|
+
bridge = self.describe_result.descriptor
|
|
921
|
+
descriptor = self.inspect_result.descriptor
|
|
922
|
+
if descriptor is None:
|
|
923
|
+
raise ValueError("lifecycle transcript requires an accepted inspection")
|
|
924
|
+
plan = self.plan_result.plan
|
|
925
|
+
receipt = self.commit_result.receipt
|
|
926
|
+
inventory = self.reconcile_result.inventory
|
|
927
|
+
if self.describe_request.ecosystem != bridge.ecosystem:
|
|
928
|
+
raise ValueError("describe result ecosystem does not match request")
|
|
929
|
+
if descriptor.ecosystem != bridge.ecosystem or plan.ecosystem != bridge.ecosystem:
|
|
930
|
+
raise ValueError("ecosystem identity must be preserved through planning")
|
|
931
|
+
if self.plan_request.descriptor != descriptor:
|
|
932
|
+
raise ValueError("plan request must preserve the inspected plugin descriptor")
|
|
933
|
+
if (plan.bridge_id, plan.bridge_version, plan.bridge_digest) != (
|
|
934
|
+
bridge.bridge_id,
|
|
935
|
+
bridge.bridge_version,
|
|
936
|
+
bridge.bridge_digest,
|
|
937
|
+
):
|
|
938
|
+
raise ValueError("transition plan must pin the described bridge identity")
|
|
939
|
+
if descriptor.host_requirement != bridge.host_requirement:
|
|
940
|
+
raise ValueError("plugin descriptor must preserve the bridge host requirement")
|
|
941
|
+
if plan.host_requirement != bridge.host_requirement:
|
|
942
|
+
raise ValueError("transition plan must preserve the bridge host requirement")
|
|
943
|
+
if (
|
|
944
|
+
plan.permissions_declared != descriptor.permissions_declared
|
|
945
|
+
or plan.undeclared_permissions_accepted
|
|
946
|
+
!= self.plan_request.accept_undeclared_permissions
|
|
947
|
+
):
|
|
948
|
+
raise ValueError("transition plan must preserve permission disclosure and acceptance")
|
|
949
|
+
if (
|
|
950
|
+
plan.plugin_id,
|
|
951
|
+
plan.plugin_version,
|
|
952
|
+
plan.integration_mode,
|
|
953
|
+
plan.desired_state,
|
|
954
|
+
plan.bound_references,
|
|
955
|
+
) != (
|
|
956
|
+
descriptor.plugin_id,
|
|
957
|
+
descriptor.plugin_version,
|
|
958
|
+
descriptor.integration_mode,
|
|
959
|
+
self.plan_request.desired_state,
|
|
960
|
+
self.plan_request.bound_references,
|
|
961
|
+
):
|
|
962
|
+
raise ValueError("transition plan must preserve plugin identity and desired state")
|
|
963
|
+
expected_digests = (
|
|
964
|
+
descriptor.artifact_digest,
|
|
965
|
+
descriptor.manifest_digest,
|
|
966
|
+
)
|
|
967
|
+
observed_digest_pairs = (
|
|
968
|
+
(plan.artifact_digest, plan.manifest_digest),
|
|
969
|
+
(self.stage_result.artifact_digest, self.stage_result.manifest_digest),
|
|
970
|
+
(receipt.artifact_digest, receipt.manifest_digest),
|
|
971
|
+
(inventory.artifact_digest, inventory.manifest_digest),
|
|
972
|
+
)
|
|
973
|
+
if any(pair != expected_digests for pair in observed_digest_pairs):
|
|
974
|
+
raise ValueError("artifact and manifest digests must remain traceable")
|
|
975
|
+
if (
|
|
976
|
+
self.stage_result.plan_id != plan.plan_id
|
|
977
|
+
or self.stage_result.plan_digest != plan.plan_digest
|
|
978
|
+
):
|
|
979
|
+
raise ValueError("stage result must trace to the immutable transition plan")
|
|
980
|
+
if self.commit_request.stage_id != self.stage_result.stage_id:
|
|
981
|
+
raise ValueError("commit request must trace to the staged transition")
|
|
982
|
+
if receipt.plan_id != plan.plan_id or receipt.stage_id != self.stage_result.stage_id:
|
|
983
|
+
raise ValueError("install receipt must trace to plan and stage")
|
|
984
|
+
if self.reconcile_request.receipt_id != receipt.receipt_id:
|
|
985
|
+
raise ValueError("reconcile request must trace to the install receipt")
|
|
986
|
+
if inventory.receipt_id != receipt.receipt_id:
|
|
987
|
+
raise ValueError("inventory must trace to the install receipt")
|
|
988
|
+
host_observations = (self.stage_result.host, receipt.host, inventory.host)
|
|
989
|
+
if inventory.host is None or any(
|
|
990
|
+
item != self.stage_result.host for item in host_observations
|
|
991
|
+
):
|
|
992
|
+
raise ValueError("native host version, protocol, and digest must remain traceable")
|
|
993
|
+
if self.rollback_request.receipt_id != receipt.receipt_id:
|
|
994
|
+
raise ValueError("rollback request must trace to the install receipt")
|
|
995
|
+
if self.dispose_request.receipt_id != receipt.receipt_id:
|
|
996
|
+
raise ValueError("dispose request must trace to the install receipt")
|
|
997
|
+
native_digests = {
|
|
998
|
+
receipt.native_receipt_digest,
|
|
999
|
+
self.reconcile_request.native_receipt_digest,
|
|
1000
|
+
inventory.native_receipt_digest,
|
|
1001
|
+
self.rollback_request.expected_native_receipt_digest,
|
|
1002
|
+
}
|
|
1003
|
+
if len(native_digests) != 1:
|
|
1004
|
+
raise ValueError("native host receipt digest must remain traceable")
|
|
1005
|
+
if (
|
|
1006
|
+
self.dispose_request.native_receipt_ref != self.rollback_result.native_receipt_ref
|
|
1007
|
+
or self.dispose_request.native_receipt_digest
|
|
1008
|
+
!= self.rollback_result.native_receipt_digest
|
|
1009
|
+
):
|
|
1010
|
+
raise ValueError("dispose request must consume the rollback receipt")
|
|
1011
|
+
if (
|
|
1012
|
+
self.dispose_result.native_receipt_ref != self.dispose_request.native_receipt_ref
|
|
1013
|
+
or self.dispose_result.native_receipt_digest
|
|
1014
|
+
!= self.dispose_request.native_receipt_digest
|
|
1015
|
+
):
|
|
1016
|
+
raise ValueError("dispose result must preserve the disposed native receipt")
|
|
1017
|
+
return self
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
BridgeFixturePayload = Annotated[
|
|
1021
|
+
PluginEcosystemBridgeTranscript | BridgeProbeExchange | BridgeInspectExchange,
|
|
1022
|
+
Field(discriminator="fixture_kind"),
|
|
1023
|
+
]
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
class PluginEcosystemBridgeFixture(RootModel[BridgeFixturePayload]):
|
|
1027
|
+
"""Schema root shared by lifecycle, ambiguous-manifest and rejection goldens."""
|
|
1028
|
+
|
|
1029
|
+
model_config = ConfigDict(frozen=True)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def ecosystem_bridge_json_schema() -> dict[str, object]:
|
|
1033
|
+
"""Return the canonical JSON Schema for all v1 bridge conformance fixtures."""
|
|
1034
|
+
|
|
1035
|
+
schema = cast(dict[str, object], PluginEcosystemBridgeFixture.model_json_schema(by_alias=True))
|
|
1036
|
+
schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
|
|
1037
|
+
schema["$id"] = "https://ksadk.local/contracts/plugin/v1/plugin-ecosystem-bridge.schema.json"
|
|
1038
|
+
schema["title"] = "PluginEcosystemBridge/v1 conformance fixtures"
|
|
1039
|
+
return schema
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
@runtime_checkable
|
|
1043
|
+
class PluginEcosystemBridge(Protocol):
|
|
1044
|
+
"""Execution-neutral bridge SPI; implementations live outside this contract."""
|
|
1045
|
+
|
|
1046
|
+
async def describe(self, request: BridgeDescribeRequest) -> BridgeDescribeResult: ...
|
|
1047
|
+
|
|
1048
|
+
async def probe(self, request: BridgeProbeRequest) -> BridgeProbeResult: ...
|
|
1049
|
+
|
|
1050
|
+
async def inspect(self, request: BridgeInspectRequest) -> BridgeInspectResult: ...
|
|
1051
|
+
|
|
1052
|
+
async def plan(self, request: BridgePlanRequest) -> BridgePlanResult: ...
|
|
1053
|
+
|
|
1054
|
+
async def stage(self, request: BridgeStageRequest) -> BridgeStageResult: ...
|
|
1055
|
+
|
|
1056
|
+
async def commit(self, request: BridgeCommitRequest) -> BridgeCommitResult: ...
|
|
1057
|
+
|
|
1058
|
+
async def reconcile(self, request: BridgeReconcileRequest) -> BridgeReconcileResult: ...
|
|
1059
|
+
|
|
1060
|
+
async def rollback(self, request: BridgeRollbackRequest) -> BridgeRollbackResult: ...
|
|
1061
|
+
|
|
1062
|
+
async def dispose(self, request: BridgeDisposeRequest) -> BridgeDisposeResult: ...
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
__all__ = [
|
|
1066
|
+
"BridgeAction",
|
|
1067
|
+
"BridgeCommitRequest",
|
|
1068
|
+
"BridgeCommitResult",
|
|
1069
|
+
"BridgeDescribeRequest",
|
|
1070
|
+
"BridgeDescribeResult",
|
|
1071
|
+
"BridgeDescriptor",
|
|
1072
|
+
"BridgeDisposeRequest",
|
|
1073
|
+
"BridgeDisposeResult",
|
|
1074
|
+
"BridgeHostObservation",
|
|
1075
|
+
"BridgeHostRequirement",
|
|
1076
|
+
"BridgeInspectExchange",
|
|
1077
|
+
"BridgeInspectRequest",
|
|
1078
|
+
"BridgeInspectResult",
|
|
1079
|
+
"BridgePlanRequest",
|
|
1080
|
+
"BridgePlanResult",
|
|
1081
|
+
"BridgeProbeExchange",
|
|
1082
|
+
"BridgeProbeRequest",
|
|
1083
|
+
"BridgeProbeResult",
|
|
1084
|
+
"BridgeReconcileRequest",
|
|
1085
|
+
"BridgeReconcileResult",
|
|
1086
|
+
"BridgeRejection",
|
|
1087
|
+
"BridgeRollbackRequest",
|
|
1088
|
+
"BridgeRollbackResult",
|
|
1089
|
+
"BridgeStageRequest",
|
|
1090
|
+
"BridgeStageResult",
|
|
1091
|
+
"BridgeTransitionPlan",
|
|
1092
|
+
"EcosystemInstallReceipt",
|
|
1093
|
+
"EcosystemPluginDescriptor",
|
|
1094
|
+
"EcosystemPluginInventory",
|
|
1095
|
+
"PluginDesiredState",
|
|
1096
|
+
"PluginEcosystem",
|
|
1097
|
+
"PluginEcosystemBridge",
|
|
1098
|
+
"PluginEcosystemBridgeFixture",
|
|
1099
|
+
"PluginEcosystemBridgeTranscript",
|
|
1100
|
+
"PluginIntegrationMode",
|
|
1101
|
+
"PluginManifestCandidate",
|
|
1102
|
+
"PluginObservedState",
|
|
1103
|
+
"PluginSupportMaturity",
|
|
1104
|
+
"ecosystem_bridge_json_schema",
|
|
1105
|
+
]
|