agentworkshop 0.7.71 → 0.8.0
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.
- package/.AgentWorkShop/prompts/host-tools.json +332 -0
- package/.output/.AgentWorkShop/prompts/host-tools.json +332 -0
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/BH8RvpVy.js +1 -0
- package/.output/public/_nuxt/Byf4IxU2.js +14 -0
- package/.output/public/_nuxt/CxVhG0Xx.js +1 -0
- package/.output/public/_nuxt/D0mzKkZr.js +1 -0
- package/.output/public/_nuxt/DIFkTkdS.js +1 -0
- package/.output/public/_nuxt/DgrK8UCQ.js +0 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/7a79a7e4-d691-4cf5-ae3a-3b01814479fb.json +1 -0
- package/.output/public/_nuxt/{default.BzOcvmSE.css → default.BEXGWZFQ.css} +1 -1
- package/.output/public/_nuxt/{entry.CL2i0F8o.css → entry.C7koBPom.css} +1 -1
- package/.output/public/_nuxt/ontology.QMGeh66l.css +1 -0
- package/.output/server/chunks/_/access.mjs +1 -1
- package/.output/server/chunks/_/binding-authz.mjs +1 -1
- package/.output/server/chunks/_/catalog-view.mjs +1 -1
- package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
- package/.output/server/chunks/_/chart-repo.mjs +1 -1
- package/.output/server/chunks/_/dashboard-repo.mjs +1 -1
- package/.output/server/chunks/_/data-export.mjs +6 -6
- package/.output/server/chunks/_/data-query.mjs +3 -3
- package/.output/server/chunks/_/eda-run.repo.mjs +2 -2
- package/.output/server/chunks/_/entity.mjs +1 -1
- package/.output/server/chunks/_/features.mjs +5 -5
- package/.output/server/chunks/_/golden.mjs +3 -3
- package/.output/server/chunks/_/host-bindings.mjs +1 -1
- package/.output/server/chunks/_/index.mjs +1 -1
- package/.output/server/chunks/_/mes-console.mjs +1 -1
- package/.output/server/chunks/_/nitro.mjs +5362 -2240
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/_/ocap.mjs +1 -1
- package/.output/server/chunks/_/rest-auth.mjs +1 -1
- package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
- package/.output/server/chunks/_/spc.mjs +2 -2
- package/.output/server/chunks/build/TownView-BWMDbosY.mjs +1 -1
- package/.output/server/chunks/build/api-BEW_ebY_.mjs +64 -0
- package/.output/server/chunks/build/api-BEW_ebY_.mjs.map +1 -0
- package/.output/server/chunks/build/daq-CdxcfY_G.mjs +1 -1
- package/.output/server/chunks/build/dcw-BlDQtS4q.mjs +1 -1
- package/.output/server/chunks/build/{default-BeB88U7y.mjs → default-CJkOcmrm.mjs} +14 -8
- package/.output/server/chunks/build/{default-BeB88U7y.mjs.map → default-CJkOcmrm.mjs.map} +1 -1
- package/.output/server/chunks/build/ontology-C3HS5pMo.mjs +977 -0
- package/.output/server/chunks/build/ontology-C3HS5pMo.mjs.map +1 -0
- package/.output/server/chunks/build/types-D6J7slEa.mjs +6 -2
- package/.output/server/chunks/build/useDaqAddNode-D_Rkju0f.mjs +1 -1
- package/.output/server/chunks/build/useDaqDetailNode-DeOsDxK5.mjs +1 -1
- package/.output/server/chunks/build/useDaqDetailParams-Cm9a9r5G.mjs +1 -1
- package/.output/server/chunks/build/useDaqFilters-CctmO03p.mjs +1 -1
- package/.output/server/chunks/build/useDaqStream-B9ZBVQrI.mjs +1 -1
- package/.output/server/chunks/build/useDaqTemplates-CK37sMfi.mjs +1 -1
- package/.output/server/chunks/build/useDcwAddNode-DACVcEHs.mjs +1 -1
- package/.output/server/chunks/build/useDcwStream-Dir5ufAU.mjs +1 -1
- package/.output/server/chunks/routes/api/mcp/workshop.mjs +1 -1
- package/.output/server/chunks/routes/api/system/backup/run.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +17 -4
- package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/grant.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/revoke.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/analysis/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/analysis/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/analysis/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index2.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/tools/reset.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +7 -7
- package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/export/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index7.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/mes-hook-test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/mes-test-read.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/commonality.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/features/run.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/index3.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/index6.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/index6.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/runs/_id/labels.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/runs.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/vm/predict.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/exp/confirmations/_id/decide.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/exp/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.mjs +62 -0
- package/.output/server/chunks/routes/api/workshop/index.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index10.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index11.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index12.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index8.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/mes/datasets.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/mes/preview.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs +54 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs +51 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs +67 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs +49 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs +35 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs +49 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs +35 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs +70 -0
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs +64 -0
- package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/index.mjs +44 -0
- package/.output/server/chunks/routes/api/workshop/ontology/index.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs +36 -0
- package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs +38 -0
- package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs +40 -0
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs +63 -0
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/optimization/capabilities.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index2.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/profiles/_id/publish.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/crosscheck.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/dispatch.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/solve.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/experiments.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/index.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/specs/_id/publish.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/train.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/tools/_name_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/tools/sync.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +2 -2
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +234 -3
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +27 -6
- package/README.md +25 -4
- package/app/components/AppSidebar.vue +1 -0
- package/app/pages/ontology/api.ts +18 -0
- package/app/pages/ontology/index.vue +779 -0
- package/app/pages/ontology/types.ts +122 -0
- package/config.yml +1 -1
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/locales/en.ts +58 -0
- package/i18n/locales/zh-CN.ts +58 -0
- package/mcp/aw-mcp-server.mjs +35 -1
- package/package.json +1 -1
- package/scripts/docs-shots.mjs +36 -0
- package/scripts/e2e-model-loop-live.mjs +731 -0
- package/scripts/e2e-ontology-live.mjs +774 -0
- package/scripts/e2e-ontology.mjs +328 -0
- package/scripts/ontology-idd-rag-e2e.mjs +449 -0
- package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +16 -0
- package/server/api/workshop/ontology/drafts/[draftId]/compile.ts +39 -0
- package/server/api/workshop/ontology/drafts/[draftId]/evidence.ts +33 -0
- package/server/api/workshop/ontology/drafts/[draftId]/publish.ts +44 -0
- package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval-decision.ts +28 -0
- package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval.ts +17 -0
- package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/transition.ts +32 -0
- package/server/api/workshop/ontology/drafts/[draftId]/validate.ts +16 -0
- package/server/api/workshop/ontology/drafts/[draftId].ts +52 -0
- package/server/api/workshop/ontology/drafts/index.ts +20 -0
- package/server/api/workshop/ontology/export/[ontologyId].ts +50 -0
- package/server/api/workshop/ontology/index.ts +38 -0
- package/server/api/workshop/ontology/scene.ts +21 -0
- package/server/api/workshop/ontology/traces/[ontologyId].ts +19 -0
- package/server/api/workshop/ontology/versions/[ontologyId]/compare.ts +21 -0
- package/server/api/workshop/ontology/versions/[ontologyId].ts +42 -0
- package/server/services/workshop/agents/hitl-decision/dispatcher.ts +15 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +13 -1
- package/server/services/workshop/agents/industrial/grant-guard.ts +59 -0
- package/server/services/workshop/agents/industrial/index.ts +38 -32
- package/server/services/workshop/agents/industrial/ontology-tools.ts +320 -0
- package/server/services/workshop/agents/industrial/optimizer-tools.ts +79 -0
- package/server/services/workshop/agents/industrial/tool-verify.ts +32 -2
- package/server/services/workshop/agents/tool-reference-spec.ts +1 -1
- package/server/services/workshop/aml/twin/physics-spec.ts +6 -22
- package/server/services/workshop/daq/storage/index.ts +6 -5
- package/server/services/workshop/daq/storage/sqlite.adapter.ts +25 -13
- package/server/services/workshop/db/database/open.ts +2 -0
- package/server/services/workshop/db/database/schema.ts +155 -1
- package/server/services/workshop/ontology/compiler.ts +474 -0
- package/server/services/workshop/ontology/contracts.ts +441 -0
- package/server/services/workshop/ontology/guards.ts +139 -0
- package/server/services/workshop/ontology/index.ts +10 -0
- package/server/services/workshop/ontology/ontology.service.ts +892 -0
- package/server/services/workshop/ontology/repo.ts +449 -0
- package/server/services/workshop/ontology/semantic-binding.service.ts +226 -0
- package/server/services/workshop/ontology/validator.ts +218 -0
- package/server/services/workshop/optimization/binding.repo.ts +7 -0
- package/server/services/workshop/optimization/contracts.ts +14 -0
- package/server/services/workshop/optimization/dispatch.service.ts +16 -0
- package/server/services/workshop/optimization/profile-adopt.service.ts +256 -0
- package/server/services/workshop/optimization/profile.repo.ts +19 -3
- package/server/services/workshop/optimization/python/__pycache__/problems.cpython-312.pyc +0 -0
- package/shared/dimensions.ts +124 -0
- package/.output/public/_nuxt/B7s2qeyT.js +0 -14
- package/.output/public/_nuxt/LHIav-Kv.js +0 -1
- package/.output/public/_nuxt/RuMLNeQE.js +0 -1
- package/.output/public/_nuxt/builds/meta/0ae8cb0f-6ce5-4096-9692-9b1fc197b30d.json +0 -1
- /package/.output/public/_nuxt/{62J4vljm2.js → 62J4vljm.js} +0 -0
- /package/.output/public/_nuxt/{BO0BJx3w.js → BO0BJx3w2.js} +0 -0
- /package/.output/public/_nuxt/{C-MY-JQE2.js → C-MY-JQE.js} +0 -0
- /package/.output/public/_nuxt/{D5kCimyw.js → D5kCimyw2.js} +0 -0
- /package/.output/public/_nuxt/{DGrrucbG2.js → DGrrucbG.js} +0 -0
- /package/.output/public/_nuxt/{Y4sMSPrS.js → Y4sMSPrS2.js} +0 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ontology 仓储(版本快照不可变 + 草稿单活跃 + 证据/日志/溯源追加行)。
|
|
3
|
+
*
|
|
4
|
+
* 约定对齐 process_model_specs / optimizer_profiles:
|
|
5
|
+
* - (id, version) 主键;published 行服务层拒改;
|
|
6
|
+
* - 草稿是**整份 JSON 文档**(服务层不散改行),发布时物化为不可变快照;
|
|
7
|
+
* - 证据、发布日志、规则溯源为 append-only 追加行。
|
|
8
|
+
*/
|
|
9
|
+
import type { DatabaseSync } from 'node:sqlite'
|
|
10
|
+
import { AppError } from '../../../utils/errors'
|
|
11
|
+
import { getAmlRuntime } from '../aml/runtime'
|
|
12
|
+
import { withTransaction } from '../db/transaction'
|
|
13
|
+
import type { ControlGuard, OntologyDraftDoc, OntologyEntity, OntologyRelation, OntologyRule, OntologyRuleTrace, BindingDraft } from './contracts'
|
|
14
|
+
|
|
15
|
+
export type VersionStatus = 'draft' | 'published' | 'retired'
|
|
16
|
+
|
|
17
|
+
export interface DraftRow {
|
|
18
|
+
id: string
|
|
19
|
+
ontologyId: string
|
|
20
|
+
baseVersion: number | null
|
|
21
|
+
rev: number
|
|
22
|
+
draftJson: string
|
|
23
|
+
ownerUserId: string
|
|
24
|
+
createdAt: string
|
|
25
|
+
updatedAt: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface VersionRow {
|
|
29
|
+
id: string
|
|
30
|
+
version: number
|
|
31
|
+
name: string
|
|
32
|
+
lineId: string
|
|
33
|
+
parentVersion: number | null
|
|
34
|
+
contentHash: string
|
|
35
|
+
compiledJson: string
|
|
36
|
+
changelogJson: string
|
|
37
|
+
status: VersionStatus
|
|
38
|
+
ownerUserId: string
|
|
39
|
+
publishedBy: string
|
|
40
|
+
createdAt: string
|
|
41
|
+
updatedAt: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EvidenceRow {
|
|
45
|
+
id: string
|
|
46
|
+
ontologyId: string
|
|
47
|
+
targetKind: string
|
|
48
|
+
targetKey: string
|
|
49
|
+
level: string
|
|
50
|
+
sourceType: string
|
|
51
|
+
sourceRef: string
|
|
52
|
+
sourceRefHash: string
|
|
53
|
+
detailJson: string
|
|
54
|
+
createdBy: string
|
|
55
|
+
createdAt: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PublishLogRow {
|
|
59
|
+
id: string
|
|
60
|
+
ontologyId: string
|
|
61
|
+
version: number
|
|
62
|
+
action: string
|
|
63
|
+
actor: string
|
|
64
|
+
actorKind: string
|
|
65
|
+
soloOperator: number
|
|
66
|
+
approvalId: string | null
|
|
67
|
+
detailJson: string
|
|
68
|
+
createdAt: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface BindingRow {
|
|
72
|
+
id: string
|
|
73
|
+
versionId: string
|
|
74
|
+
version: number
|
|
75
|
+
entityKey: string
|
|
76
|
+
platformKind: string
|
|
77
|
+
platformId: string
|
|
78
|
+
targetSub: string
|
|
79
|
+
scope: string
|
|
80
|
+
direction: string
|
|
81
|
+
unitDeclared: string
|
|
82
|
+
unitTarget: string
|
|
83
|
+
scale: number
|
|
84
|
+
offset: number
|
|
85
|
+
status: string
|
|
86
|
+
lastVerifiedAt: string | null
|
|
87
|
+
createdAt: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DRAFT_COLS = `id, ontology_id AS ontologyId, base_version AS baseVersion, rev,
|
|
91
|
+
draft_json AS draftJson, owner_user_id AS ownerUserId, created_at AS createdAt, updated_at AS updatedAt`
|
|
92
|
+
|
|
93
|
+
const VERSION_COLS = `id, version, name, line_id AS lineId, parent_version AS parentVersion,
|
|
94
|
+
content_hash AS contentHash, compiled_json AS compiledJson, changelog_json AS changelogJson,
|
|
95
|
+
status, owner_user_id AS ownerUserId, published_by AS publishedBy,
|
|
96
|
+
created_at AS createdAt, updated_at AS updatedAt`
|
|
97
|
+
|
|
98
|
+
export function createOntologyRepo(db: DatabaseSync) {
|
|
99
|
+
const repo = {
|
|
100
|
+
// ===== 草稿 =====
|
|
101
|
+
insertDraft(input: { id: string, ontologyId: string, baseVersion: number | null, draftJson: string, ownerUserId: string }): DraftRow {
|
|
102
|
+
const now = new Date().toISOString()
|
|
103
|
+
const existing = db.prepare(`SELECT ${DRAFT_COLS} FROM ontology_drafts WHERE ontology_id = ? AND owner_user_id = ?`)
|
|
104
|
+
.get(input.ontologyId, input.ownerUserId) as DraftRow | undefined
|
|
105
|
+
if (existing) {
|
|
106
|
+
throw new AppError(409, 'DRAFT_ALREADY_ACTIVE',
|
|
107
|
+
`本体 ${input.ontologyId} 已存在活跃草稿(${existing.id}, rev=${existing.rev});请继续编辑或先删除该草稿`)
|
|
108
|
+
}
|
|
109
|
+
db.prepare(`INSERT INTO ontology_drafts (id, ontology_id, base_version, rev, draft_json, owner_user_id, created_at, updated_at)
|
|
110
|
+
VALUES (?, ?, ?, 1, ?, ?, ?, ?)`)
|
|
111
|
+
.run(input.id, input.ontologyId, input.baseVersion, input.draftJson, input.ownerUserId, now, now)
|
|
112
|
+
return repo.getDraft(input.id)!
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
getDraft(id: string): DraftRow | undefined {
|
|
116
|
+
return db.prepare(`SELECT ${DRAFT_COLS} FROM ontology_drafts WHERE id = ?`).get(id) as DraftRow | undefined
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
getActiveDraft(ontologyId: string, ownerUserId: string): DraftRow | undefined {
|
|
120
|
+
return db.prepare(`SELECT ${DRAFT_COLS} FROM ontology_drafts WHERE ontology_id = ? AND owner_user_id = ?`)
|
|
121
|
+
.get(ontologyId, ownerUserId) as DraftRow | undefined
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
listDrafts(filter: { ownerUserId?: string, ontologyId?: string } = {}): DraftRow[] {
|
|
125
|
+
const where: string[] = []
|
|
126
|
+
const params: Array<string> = []
|
|
127
|
+
if (filter.ownerUserId) {
|
|
128
|
+
where.push('owner_user_id = ?')
|
|
129
|
+
params.push(filter.ownerUserId)
|
|
130
|
+
}
|
|
131
|
+
if (filter.ontologyId) {
|
|
132
|
+
where.push('ontology_id = ?')
|
|
133
|
+
params.push(filter.ontologyId)
|
|
134
|
+
}
|
|
135
|
+
const sql = `SELECT ${DRAFT_COLS} FROM ontology_drafts${where.length ? ` WHERE ${where.join(' AND ')}` : ''} ORDER BY updated_at DESC LIMIT 200`
|
|
136
|
+
return db.prepare(sql).all(...params) as unknown as DraftRow[]
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** 乐观锁:rev 不匹配 → 409(并发编辑必须显式重读) */
|
|
140
|
+
updateDraft(id: string, draftJson: string, expectedRev: number): DraftRow {
|
|
141
|
+
const current = repo.getDraft(id)
|
|
142
|
+
if (!current) throw new AppError(404, 'DRAFT_NOT_FOUND', `草稿 ${id} 不存在`)
|
|
143
|
+
if (current.rev !== expectedRev) {
|
|
144
|
+
throw new AppError(409, 'DRAFT_CONCURRENT_MODIFY',
|
|
145
|
+
`草稿 ${id} 已被修改(期望 rev=${expectedRev},当前 rev=${current.rev});请重新读取后再提交`)
|
|
146
|
+
}
|
|
147
|
+
const now = new Date().toISOString()
|
|
148
|
+
const changes = db.prepare('UPDATE ontology_drafts SET draft_json = ?, rev = rev + 1, updated_at = ? WHERE id = ? AND rev = ?')
|
|
149
|
+
.run(draftJson, now, id, expectedRev).changes
|
|
150
|
+
if (changes === 0) throw new AppError(409, 'DRAFT_CONCURRENT_MODIFY', `草稿 ${id} 并发修改冲突`)
|
|
151
|
+
return repo.getDraft(id)!
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
updateDraftMeta(id: string, patch: { baseVersion?: number | null }): void {
|
|
155
|
+
const now = new Date().toISOString()
|
|
156
|
+
if (patch.baseVersion !== undefined) {
|
|
157
|
+
db.prepare('UPDATE ontology_drafts SET base_version = ?, updated_at = ? WHERE id = ?').run(patch.baseVersion, now, id)
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
deleteDraft(id: string): boolean {
|
|
162
|
+
return db.prepare('DELETE FROM ontology_drafts WHERE id = ?').run(id).changes > 0
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// ===== 版本 =====
|
|
166
|
+
getVersion(id: string, version: number): VersionRow | undefined {
|
|
167
|
+
return db.prepare(`SELECT ${VERSION_COLS} FROM ontology_versions WHERE id = ? AND version = ?`)
|
|
168
|
+
.get(id, version) as VersionRow | undefined
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
listVersions(id: string): VersionRow[] {
|
|
172
|
+
return db.prepare(`SELECT ${VERSION_COLS} FROM ontology_versions WHERE id = ? ORDER BY version DESC`)
|
|
173
|
+
.all(id) as unknown as VersionRow[]
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
latestVersion(id: string, status?: VersionStatus): VersionRow | undefined {
|
|
177
|
+
const row = status
|
|
178
|
+
? db.prepare(`SELECT ${VERSION_COLS} FROM ontology_versions WHERE id = ? AND status = ? ORDER BY version DESC LIMIT 1`).get(id, status)
|
|
179
|
+
: db.prepare(`SELECT ${VERSION_COLS} FROM ontology_versions WHERE id = ? ORDER BY version DESC LIMIT 1`).get(id)
|
|
180
|
+
return row as VersionRow | undefined
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
nextVersionNumber(id: string): number {
|
|
184
|
+
const row = db.prepare('SELECT MAX(version) AS maxVersion FROM ontology_versions WHERE id = ?').get(id) as { maxVersion: number | null } | undefined
|
|
185
|
+
return (row?.maxVersion ?? 0) + 1
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/** 某产线当前生效的已发布版本(该线最新的 published;退役版本自然被跳过) */
|
|
189
|
+
latestPublishedForLine(lineId: string): VersionRow | undefined {
|
|
190
|
+
return db.prepare(`SELECT ${VERSION_COLS} FROM ontology_versions
|
|
191
|
+
WHERE line_id = ? AND status = 'published' ORDER BY version DESC, updated_at DESC LIMIT 1`)
|
|
192
|
+
.get(lineId) as VersionRow | undefined
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
listAllOntologyIds(lineId?: string): Array<{ id: string, lineId: string, versions: number, latest: number, status: string }> {
|
|
196
|
+
const sql = `SELECT v.id AS id, MAX(v.line_id) AS lineId, COUNT(*) AS versions, MAX(v.version) AS latest,
|
|
197
|
+
(SELECT status FROM ontology_versions v2 WHERE v2.id = v.id ORDER BY v2.version DESC LIMIT 1) AS status
|
|
198
|
+
FROM ontology_versions v ${lineId ? 'WHERE v.line_id = ?' : ''} GROUP BY v.id ORDER BY MAX(v.updated_at) DESC LIMIT 200`
|
|
199
|
+
return (lineId ? db.prepare(sql).all(lineId) : db.prepare(sql).all()) as Array<{ id: string, lineId: string, versions: number, latest: number, status: string }>
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/** 发布快照:版本行 + 实体/关系/规则/绑定 + 溯源,单事务原子落库 */
|
|
203
|
+
publishSnapshot(input: {
|
|
204
|
+
version: VersionRow
|
|
205
|
+
entities: OntologyEntity[]
|
|
206
|
+
relations: OntologyRelation[]
|
|
207
|
+
rules: OntologyRule[]
|
|
208
|
+
bindings: BindingDraft[]
|
|
209
|
+
traces: OntologyRuleTrace[]
|
|
210
|
+
guards: ControlGuard[]
|
|
211
|
+
createdBy: string
|
|
212
|
+
}): VersionRow {
|
|
213
|
+
return withTransaction(db, () => {
|
|
214
|
+
const now = new Date().toISOString()
|
|
215
|
+
db.prepare(`INSERT INTO ontology_versions
|
|
216
|
+
(id, version, name, line_id, parent_version, content_hash, compiled_json, changelog_json,
|
|
217
|
+
status, owner_user_id, published_by, created_at, updated_at)
|
|
218
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
219
|
+
.run(input.version.id, input.version.version, input.version.name, input.version.lineId,
|
|
220
|
+
input.version.parentVersion, input.version.contentHash, input.version.compiledJson,
|
|
221
|
+
input.version.changelogJson, input.version.status, input.version.ownerUserId,
|
|
222
|
+
input.version.publishedBy, now, now)
|
|
223
|
+
|
|
224
|
+
const entityStmt = db.prepare(`INSERT INTO ontology_entities
|
|
225
|
+
(version_id, version, entity_key, kind, name, description, unit, dimension, role, attributes_json, created_at)
|
|
226
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
227
|
+
for (const entity of input.entities) {
|
|
228
|
+
entityStmt.run(input.version.id, input.version.version, entity.key, entity.kind, entity.name,
|
|
229
|
+
entity.description, entity.unit, entity.dimension, entity.role ?? '', JSON.stringify(entity.attrs), now)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const relationStmt = db.prepare(`INSERT INTO ontology_relations
|
|
233
|
+
(id, version_id, version, src_key, dst_key, predicate, properties_json, status, created_at)
|
|
234
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
235
|
+
for (const rel of input.relations) {
|
|
236
|
+
relationStmt.run(rel.id, input.version.id, input.version.version, rel.srcKey, rel.dstKey, rel.kind,
|
|
237
|
+
JSON.stringify({ ...rel.properties, evidence: rel.evidence }), rel.status, now)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const ruleStmt = db.prepare(`INSERT INTO ontology_rules
|
|
241
|
+
(id, version_id, version, rule_key, kind, dsl_json, constraint_level, severity, status, description, created_at)
|
|
242
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
243
|
+
for (const rule of input.rules) {
|
|
244
|
+
ruleStmt.run(rule.id, input.version.id, input.version.version, rule.key, rule.kind,
|
|
245
|
+
JSON.stringify({ dsl: rule.dsl, evidence: rule.evidence, scope: rule.scope }),
|
|
246
|
+
rule.constraintLevel, rule.severity, rule.status, rule.description, now)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const bindingStmt = db.prepare(`INSERT INTO ontology_semantic_bindings
|
|
250
|
+
(id, version_id, version, entity_key, platform_kind, platform_id, target_sub, scope, direction,
|
|
251
|
+
unit_declared, unit_target, scale, offset, status, last_verified_at, created_at)
|
|
252
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)`)
|
|
253
|
+
for (const binding of input.bindings) {
|
|
254
|
+
bindingStmt.run(binding.id, input.version.id, input.version.version, binding.entityKey,
|
|
255
|
+
binding.platformKind, binding.platformId, binding.targetSub, binding.scope, binding.direction,
|
|
256
|
+
binding.unitDeclared, binding.unitTarget, binding.scale, binding.offset, now, now)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const traceStmt = db.prepare(`INSERT INTO ontology_rule_traces
|
|
260
|
+
(id, ontology_id, version, rule_pack_hash, rule_id, rule_revision, rule_kind, evidence_level,
|
|
261
|
+
constraint_level, decision, decision_code, message, bindings_json, artifacts_json, scope_json, row_hash, created_at)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
263
|
+
for (const trace of input.traces) {
|
|
264
|
+
traceStmt.run(trace.id, trace.ontologyId, trace.version, trace.rulePackHash, trace.ruleId,
|
|
265
|
+
trace.ruleRevision, trace.ruleKind, trace.evidenceLevel, trace.constraintLevel, trace.decision,
|
|
266
|
+
trace.decisionCode, trace.message, JSON.stringify(trace.bindings), JSON.stringify(trace.artifacts),
|
|
267
|
+
JSON.stringify(trace.scope), trace.rowHash, trace.createdAt)
|
|
268
|
+
}
|
|
269
|
+
return repo.getVersion(input.version.id, input.version.version)!
|
|
270
|
+
})
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/** 状态原子转移(条件 UPDATE;changes=0 → 409 由调用方判定) */
|
|
274
|
+
transitionVersion(id: string, version: number, to: VersionStatus, from: VersionStatus): boolean {
|
|
275
|
+
const now = new Date().toISOString()
|
|
276
|
+
return db.prepare('UPDATE ontology_versions SET status = ?, updated_at = ? WHERE id = ? AND version = ? AND status = ?')
|
|
277
|
+
.run(to, now, id, version, from).changes > 0
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
listEntities(versionId: string, version: number): OntologyEntity[] {
|
|
281
|
+
const rows = db.prepare(`SELECT entity_key AS key, kind, name, description, unit, dimension, role, attributes_json AS attrsJson
|
|
282
|
+
FROM ontology_entities WHERE version_id = ? AND version = ? ORDER BY entity_key`).all(versionId, version) as Array<Record<string, string>>
|
|
283
|
+
return rows.map(r => ({
|
|
284
|
+
key: r.key!, kind: r.kind as OntologyEntity['kind'], name: r.name!, description: r.description ?? '',
|
|
285
|
+
unit: r.unit ?? '', dimension: r.dimension ?? '',
|
|
286
|
+
role: (r.role || undefined) as OntologyEntity['role'],
|
|
287
|
+
attrs: JSON.parse(r.attrsJson || '{}') as OntologyEntity['attrs'],
|
|
288
|
+
}))
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
listRelations(versionId: string, version: number): OntologyRelation[] {
|
|
292
|
+
const rows = db.prepare(`SELECT id, src_key AS srcKey, dst_key AS dstKey, predicate, properties_json AS propsJson, status
|
|
293
|
+
FROM ontology_relations WHERE version_id = ? AND version = ? ORDER BY id`).all(versionId, version) as Array<Record<string, string>>
|
|
294
|
+
return rows.map((r) => {
|
|
295
|
+
const props = JSON.parse(r.propsJson || '{}') as Record<string, unknown>
|
|
296
|
+
return {
|
|
297
|
+
id: r.id!, kind: r.predicate as OntologyRelation['kind'], srcKey: r.srcKey!, dstKey: r.dstKey!,
|
|
298
|
+
status: (r.status ?? 'proposed') as OntologyRelation['status'],
|
|
299
|
+
evidence: Array.isArray(props.evidence) ? props.evidence as OntologyRelation['evidence'] : [],
|
|
300
|
+
properties: {
|
|
301
|
+
sign: props.sign as OntologyRelation['properties']['sign'],
|
|
302
|
+
gainPerUnit: props.gainPerUnit as number | undefined,
|
|
303
|
+
lagMs: props.lagMs as number | undefined,
|
|
304
|
+
evidenceTier: (props.evidenceTier ?? 'candidate_influence') as OntologyRelation['properties']['evidenceTier'],
|
|
305
|
+
variabilityChecked: !!props.variabilityChecked,
|
|
306
|
+
transportDistanceM: props.transportDistanceM as number | undefined,
|
|
307
|
+
nominalSpeedMps: props.nominalSpeedMps as number | undefined,
|
|
308
|
+
controlApplicability: props.controlApplicability as OntologyRelation['properties']['controlApplicability'],
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
listRules(versionId: string, version: number): OntologyRule[] {
|
|
315
|
+
const rows = db.prepare(`SELECT id, rule_key AS ruleKey, kind, dsl_json AS dslJson, constraint_level AS constraintLevel,
|
|
316
|
+
severity, status, description FROM ontology_rules WHERE version_id = ? AND version = ? ORDER BY id`)
|
|
317
|
+
.all(versionId, version) as Array<Record<string, string>>
|
|
318
|
+
return rows.map((r) => {
|
|
319
|
+
const parsed = JSON.parse(r.dslJson || '{}') as { dsl?: unknown, evidence?: OntologyRule['evidence'], scope?: OntologyRule['scope'] }
|
|
320
|
+
return {
|
|
321
|
+
id: r.id!, key: r.ruleKey!, kind: r.kind as OntologyRule['kind'],
|
|
322
|
+
dsl: parsed.dsl as OntologyRule['dsl'],
|
|
323
|
+
constraintLevel: (r.constraintLevel ?? 'soft') as OntologyRule['constraintLevel'],
|
|
324
|
+
severity: (r.severity ?? 'error') as OntologyRule['severity'],
|
|
325
|
+
status: (r.status ?? 'proposed') as OntologyRule['status'],
|
|
326
|
+
evidence: Array.isArray(parsed.evidence) ? parsed.evidence : [],
|
|
327
|
+
description: r.description ?? '',
|
|
328
|
+
scope: parsed.scope ?? { lineId: '', recipeId: '', conditionId: '' },
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
listBindings(versionId: string, version: number): BindingRow[] {
|
|
334
|
+
return db.prepare(`SELECT id, version_id AS versionId, version, entity_key AS entityKey,
|
|
335
|
+
platform_kind AS platformKind, platform_id AS platformId, target_sub AS targetSub, scope, direction,
|
|
336
|
+
unit_declared AS unitDeclared, unit_target AS unitTarget, scale, offset, status,
|
|
337
|
+
last_verified_at AS lastVerifiedAt, created_at AS createdAt
|
|
338
|
+
FROM ontology_semantic_bindings WHERE version_id = ? AND version = ? ORDER BY entity_key, platform_kind`)
|
|
339
|
+
.all(versionId, version) as unknown as BindingRow[]
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
listBindingsByTarget(platformKind: string, platformId: string): BindingRow[] {
|
|
343
|
+
return db.prepare(`SELECT id, version_id AS versionId, version, entity_key AS entityKey,
|
|
344
|
+
platform_kind AS platformKind, platform_id AS platformId, target_sub AS targetSub, scope, direction,
|
|
345
|
+
unit_declared AS unitDeclared, unit_target AS unitTarget, scale, offset, status,
|
|
346
|
+
last_verified_at AS lastVerifiedAt, created_at AS createdAt
|
|
347
|
+
FROM ontology_semantic_bindings WHERE platform_kind = ? AND platform_id = ? AND status = 'active'`)
|
|
348
|
+
.all(platformKind, platformId) as unknown as BindingRow[]
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
markBindingStatus(id: string, status: 'active' | 'drifted' | 'retired'): void {
|
|
352
|
+
const now = new Date().toISOString()
|
|
353
|
+
db.prepare('UPDATE ontology_semantic_bindings SET status = ?, last_verified_at = ? WHERE id = ?').run(status, now, id)
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
// ===== 证据 =====
|
|
357
|
+
appendEvidence(row: EvidenceRow): void {
|
|
358
|
+
db.prepare(`INSERT INTO ontology_evidence
|
|
359
|
+
(id, ontology_id, target_kind, target_key, level, source_type, source_ref, source_ref_hash, detail_json, created_by, created_at)
|
|
360
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
361
|
+
.run(row.id, row.ontologyId, row.targetKind, row.targetKey, row.level, row.sourceType,
|
|
362
|
+
row.sourceRef, row.sourceRefHash, row.detailJson, row.createdBy, row.createdAt)
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
listEvidence(ontologyId: string, filter: { targetKind?: string, targetKey?: string, limit?: number } = {}): EvidenceRow[] {
|
|
366
|
+
const where = ['ontology_id = ?']
|
|
367
|
+
const params: Array<string | number> = [ontologyId]
|
|
368
|
+
if (filter.targetKind) {
|
|
369
|
+
where.push('target_kind = ?')
|
|
370
|
+
params.push(filter.targetKind)
|
|
371
|
+
}
|
|
372
|
+
if (filter.targetKey) {
|
|
373
|
+
where.push('target_key = ?')
|
|
374
|
+
params.push(filter.targetKey)
|
|
375
|
+
}
|
|
376
|
+
params.push(filter.limit ?? 200)
|
|
377
|
+
return db.prepare(`SELECT id, ontology_id AS ontologyId, target_kind AS targetKind, target_key AS targetKey,
|
|
378
|
+
level, source_type AS sourceType, source_ref AS sourceRef, source_ref_hash AS sourceRefHash,
|
|
379
|
+
detail_json AS detailJson, created_by AS createdBy, created_at AS createdAt
|
|
380
|
+
FROM ontology_evidence WHERE ${where.join(' AND ')} ORDER BY created_at DESC LIMIT ?`)
|
|
381
|
+
.all(...params) as unknown as EvidenceRow[]
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
// ===== 发布日志 =====
|
|
385
|
+
appendPublishLog(row: PublishLogRow): void {
|
|
386
|
+
db.prepare(`INSERT INTO ontology_publish_log
|
|
387
|
+
(id, ontology_id, version, action, actor, actor_kind, solo_operator, approval_id, detail_json, created_at)
|
|
388
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
389
|
+
.run(row.id, row.ontologyId, row.version, row.action, row.actor, row.actorKind,
|
|
390
|
+
row.soloOperator, row.approvalId, row.detailJson, row.createdAt)
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
listPublishLog(ontologyId: string, limit = 100): PublishLogRow[] {
|
|
394
|
+
return db.prepare(`SELECT id, ontology_id AS ontologyId, version, action, actor, actor_kind AS actorKind,
|
|
395
|
+
solo_operator AS soloOperator, approval_id AS approvalId, detail_json AS detailJson, created_at AS createdAt
|
|
396
|
+
FROM ontology_publish_log WHERE ontology_id = ? ORDER BY created_at DESC LIMIT ?`)
|
|
397
|
+
.all(ontologyId, limit) as unknown as PublishLogRow[]
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
// ===== 规则溯源 =====
|
|
401
|
+
listTraces(ontologyId: string, version?: number, limit = 500): OntologyRuleTrace[] {
|
|
402
|
+
const where = ['ontology_id = ?']
|
|
403
|
+
const params: Array<string | number> = [ontologyId]
|
|
404
|
+
if (version !== undefined) {
|
|
405
|
+
where.push('version = ?')
|
|
406
|
+
params.push(version)
|
|
407
|
+
}
|
|
408
|
+
params.push(limit)
|
|
409
|
+
const rows = db.prepare(`SELECT id, ontology_id AS ontologyId, version, rule_pack_hash AS rulePackHash,
|
|
410
|
+
rule_id AS ruleId, rule_revision AS ruleRevision, rule_kind AS ruleKind, evidence_level AS evidenceLevel,
|
|
411
|
+
constraint_level AS constraintLevel, decision, decision_code AS decisionCode, message,
|
|
412
|
+
bindings_json AS bindingsJson, artifacts_json AS artifactsJson, scope_json AS scopeJson,
|
|
413
|
+
row_hash AS rowHash, created_at AS createdAt
|
|
414
|
+
FROM ontology_rule_traces WHERE ${where.join(' AND ')} ORDER BY rule_id LIMIT ?`)
|
|
415
|
+
.all(...params) as Array<Record<string, string | number>>
|
|
416
|
+
return rows.map(r => ({
|
|
417
|
+
id: String(r.id), ontologyId: String(r.ontologyId), version: Number(r.version),
|
|
418
|
+
rulePackHash: String(r.rulePackHash), ruleId: String(r.ruleId), ruleKey: String(r.ruleId),
|
|
419
|
+
ruleRevision: String(r.ruleRevision), ruleKind: String(r.ruleKind) as OntologyRuleTrace['ruleKind'],
|
|
420
|
+
evidenceLevel: String(r.evidenceLevel ?? '') as OntologyRuleTrace['evidenceLevel'],
|
|
421
|
+
constraintLevel: String(r.constraintLevel) as OntologyRuleTrace['constraintLevel'],
|
|
422
|
+
decision: String(r.decision) as OntologyRuleTrace['decision'],
|
|
423
|
+
decisionCode: String(r.decisionCode), message: String(r.message),
|
|
424
|
+
bindings: JSON.parse(String(r.bindingsJson || '[]')) as OntologyRuleTrace['bindings'],
|
|
425
|
+
artifacts: JSON.parse(String(r.artifactsJson || '[]')) as OntologyRuleTrace['artifacts'],
|
|
426
|
+
scope: JSON.parse(String(r.scopeJson || '{}')) as OntologyRuleTrace['scope'],
|
|
427
|
+
rowHash: String(r.rowHash), createdAt: String(r.createdAt),
|
|
428
|
+
}))
|
|
429
|
+
},
|
|
430
|
+
}
|
|
431
|
+
return repo
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let cached: ReturnType<typeof createOntologyRepo> | null = null
|
|
435
|
+
|
|
436
|
+
/** 进程级仓储(单连接:workshop 只有一条主连接) */
|
|
437
|
+
export function getOntologyRepo(): ReturnType<typeof createOntologyRepo> {
|
|
438
|
+
if (!cached) cached = createOntologyRepo(getAmlRuntime().db)
|
|
439
|
+
return cached
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** 测试/重连后重置(单测在 fresh home 打开新库时调用) */
|
|
443
|
+
export function resetOntologyRepo(): void {
|
|
444
|
+
cached = null
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function parseDraftRow(row: DraftRow): OntologyDraftDoc {
|
|
448
|
+
return JSON.parse(row.draftJson) as OntologyDraftDoc
|
|
449
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 语义绑定服务 —— 本体实体 ↔ 现有平台对象(daq_node / dcw_node / recipe_param)。
|
|
3
|
+
*
|
|
4
|
+
* 铁律:本体不复制系统对象。绑定只登记「语义 → 真实 id」的映射,存在性、归属、
|
|
5
|
+
* 单位、读写能力每次编辑与发布都**实时**回查活体对象(与 tool-verify 同口径)。
|
|
6
|
+
*
|
|
7
|
+
* 单位:换算唯一边界是 shared/units.ts;量纲相容性由 shared/dimensions 判定。
|
|
8
|
+
* 同量纲不同字面单位(degc↔k)必须显式给 scale/offset,否则 422 UNIT_TRANSFORM_REQUIRED。
|
|
9
|
+
*/
|
|
10
|
+
import { AppError } from '../../../utils/errors'
|
|
11
|
+
import { getDaqNodeRepo } from '../daq/daq-node.repo'
|
|
12
|
+
import { getDcwController } from '../dcw/dcw-controller'
|
|
13
|
+
import type { BindingDraft, OntologyDraftDoc, OntologyEntity } from './contracts'
|
|
14
|
+
import { unitsCompatible } from './contracts'
|
|
15
|
+
import { dimensionOf, normalizeUnit, unitRelation } from '@/shared/dimensions'
|
|
16
|
+
import type { BindingRow } from './repo'
|
|
17
|
+
|
|
18
|
+
export interface ResolvedTarget {
|
|
19
|
+
platformKind: BindingDraft['platformKind']
|
|
20
|
+
platformId: string
|
|
21
|
+
targetSub: string
|
|
22
|
+
name: string
|
|
23
|
+
unit: string
|
|
24
|
+
min: number | null
|
|
25
|
+
max: number | null
|
|
26
|
+
stepLimit: number | null
|
|
27
|
+
writable: boolean
|
|
28
|
+
lineId: string
|
|
29
|
+
enabled: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function num(value: unknown): number | null {
|
|
33
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 解析绑定目标(不存在返回 null;不抛错,便于批量报告) */
|
|
37
|
+
export function resolveBindingTarget(binding: Pick<BindingDraft, 'platformKind' | 'platformId' | 'targetSub'>): ResolvedTarget | null {
|
|
38
|
+
if (binding.platformKind === 'daq_node') {
|
|
39
|
+
const node = getDaqNodeRepo().byId(binding.platformId)
|
|
40
|
+
if (!node) return null
|
|
41
|
+
return {
|
|
42
|
+
platformKind: 'daq_node', platformId: node.id, targetSub: '',
|
|
43
|
+
name: node.name, unit: node.unit ?? '', min: num(node.min), max: num(node.max), stepLimit: null,
|
|
44
|
+
writable: false, lineId: node.lineId ?? '', enabled: node.enabled !== false,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (binding.platformKind === 'dcw_node') {
|
|
48
|
+
const node = getDcwController().byId(binding.platformId)
|
|
49
|
+
if (!node) return null
|
|
50
|
+
return {
|
|
51
|
+
platformKind: 'dcw_node', platformId: node.id, targetSub: '',
|
|
52
|
+
name: node.name, unit: node.unit ?? '', min: num(node.min), max: num(node.max), stepLimit: num(node.stepLimit),
|
|
53
|
+
writable: true, lineId: node.lineId ?? '', enabled: node.enabled !== false,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (binding.platformKind === 'recipe_param') {
|
|
57
|
+
const recipe = getDcwController().listRecipes().find(r => r.id === binding.platformId)
|
|
58
|
+
if (!recipe) return null
|
|
59
|
+
const key = binding.targetSub || ''
|
|
60
|
+
const param = recipe.params.find(p => p.nodeId === key)
|
|
61
|
+
if (!param) return null
|
|
62
|
+
const node = getDcwController().byId(param.nodeId)
|
|
63
|
+
return {
|
|
64
|
+
platformKind: 'recipe_param', platformId: recipe.id, targetSub: param.nodeId,
|
|
65
|
+
name: `${recipe.name}/${param.nodeId}`, unit: node?.unit ?? '',
|
|
66
|
+
min: num(param.min ?? node?.min), max: num(param.max ?? node?.max), stepLimit: num(param.stepLimit ?? node?.stepLimit),
|
|
67
|
+
writable: true, lineId: recipe.lineId ?? '', enabled: true,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// device / spec_variable / scene_variable / condition:无活体目录面,只做形状校验
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 单条绑定的形状校验(纯函数:目标已解析;可单测,不查库) */
|
|
75
|
+
export function assertBindingShape(
|
|
76
|
+
entity: OntologyEntity,
|
|
77
|
+
binding: BindingDraft,
|
|
78
|
+
target: ResolvedTarget,
|
|
79
|
+
opts: { lineId?: string } = {},
|
|
80
|
+
): ResolvedTarget {
|
|
81
|
+
if (!target.enabled) {
|
|
82
|
+
throw new AppError(422, 'TARGET_DISABLED', `绑定 ${binding.id} 的目标「${target.name}」已停用`)
|
|
83
|
+
}
|
|
84
|
+
// 产线归属:文档有授权锚时绑定不得跨线
|
|
85
|
+
if (opts.lineId && target.lineId && opts.lineId !== target.lineId) {
|
|
86
|
+
throw new AppError(422, 'ONTOLOGY_SCOPE_MISMATCH',
|
|
87
|
+
`绑定 ${binding.id} 的目标「${target.name}」属于产线 ${target.lineId},与本体授权锚 ${opts.lineId} 不一致`)
|
|
88
|
+
}
|
|
89
|
+
// 读写能力
|
|
90
|
+
if ((binding.direction === 'write' || binding.direction === 'read_write') && !target.writable) {
|
|
91
|
+
throw new AppError(422, 'ACCESS_MODE_CONFLICT',
|
|
92
|
+
`绑定 ${binding.id} 声明 ${binding.direction},但目标 ${binding.platformKind}:${binding.platformId} 是只读通道;写入应绑定 dcw_node 或 recipe_param`)
|
|
93
|
+
}
|
|
94
|
+
// 数据类型(实体 attrs.dataType 非数值时不得绑定数值通道)
|
|
95
|
+
const declaredType = entity.attrs.dataType
|
|
96
|
+
if (typeof declaredType === 'string' && declaredType !== 'number') {
|
|
97
|
+
throw new AppError(422, 'TYPE_MISMATCH',
|
|
98
|
+
`实体「${entity.key}」声明 dataType=${declaredType},而目标「${target.name}」是数值通道;请改正实体类型`)
|
|
99
|
+
}
|
|
100
|
+
// 单位:声明单位 vs 目标单位
|
|
101
|
+
const declared = binding.unitDeclared || entity.unit || ''
|
|
102
|
+
if (declared && target.unit) {
|
|
103
|
+
const relation = unitRelation(declared, target.unit)
|
|
104
|
+
if (relation.kind === 'incompatible') {
|
|
105
|
+
throw new AppError(422, 'UNIT_MISMATCH',
|
|
106
|
+
`绑定 ${binding.id} 的声明单位「${declared}」(${relation.from})与目标单位「${target.unit}」(${relation.to})量纲不相容`)
|
|
107
|
+
}
|
|
108
|
+
if (relation.kind === 'convertible') {
|
|
109
|
+
const explicit = Number.isFinite(binding.scale) && binding.scale !== 0
|
|
110
|
+
&& (binding.scale !== 1 || binding.offset !== 0)
|
|
111
|
+
if (!explicit) {
|
|
112
|
+
throw new AppError(422, 'UNIT_TRANSFORM_REQUIRED',
|
|
113
|
+
`绑定 ${binding.id} 的单位「${declared}」与目标单位「${target.unit}」同量纲但字面不同(${normalizeUnit(declared)}≠${normalizeUnit(target.unit)});必须显式给出 scale/offset,禁止按 1/0 兜底`)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!Number.isFinite(binding.scale) || binding.scale === 0) {
|
|
118
|
+
throw new AppError(422, 'UNIT_CONTRACT_INVALID', `绑定 ${binding.id} 的 scale 必须为非零有限数`)
|
|
119
|
+
}
|
|
120
|
+
return target
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 单条绑定的活体校验(throw AppError:422 形状/单位/能力) */
|
|
124
|
+
export function assertBindingLive(entity: OntologyEntity, binding: BindingDraft, opts: { lineId?: string } = {}): ResolvedTarget {
|
|
125
|
+
const target = resolveBindingTarget(binding)
|
|
126
|
+
if (!target) {
|
|
127
|
+
throw new AppError(422, 'TARGET_NOT_FOUND',
|
|
128
|
+
`绑定 ${binding.id} 的目标 ${binding.platformKind}:${binding.platformId}${binding.targetSub ? `#${binding.targetSub}` : ''} 不存在;请先创建该节点/配方参数`)
|
|
129
|
+
}
|
|
130
|
+
return assertBindingShape(entity, binding, target, opts)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface BindingValidationReport {
|
|
134
|
+
ok: boolean
|
|
135
|
+
failures: Array<{ bindingId: string, code: string, message: string }>
|
|
136
|
+
resolved: ResolvedTarget[]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 批量活体校验(草稿期每次编辑 + 发布前复核):
|
|
141
|
+
* - 逐条存在性/单位/能力;
|
|
142
|
+
* - 同一物理目标被两个实体声明为**写** → 409 BINDING_TARGET_OWNED(一个量只有一个写语义);
|
|
143
|
+
* - 同一实体同目标的读写方向冲突 → 422。
|
|
144
|
+
*/
|
|
145
|
+
export function validateDraftBindings(
|
|
146
|
+
doc: OntologyDraftDoc,
|
|
147
|
+
opts: { lineId?: string, resolver?: (binding: BindingDraft) => ResolvedTarget | null } = {},
|
|
148
|
+
): BindingValidationReport {
|
|
149
|
+
const entities = new Map(doc.entities.map(e => [e.key, e]))
|
|
150
|
+
const failures: BindingValidationReport['failures'] = []
|
|
151
|
+
const resolved: ResolvedTarget[] = []
|
|
152
|
+
const writeOwners = new Map<string, string>()
|
|
153
|
+
const resolver = opts.resolver ?? resolveBindingTarget
|
|
154
|
+
|
|
155
|
+
for (const binding of doc.bindings) {
|
|
156
|
+
const entity = entities.get(binding.entityKey)
|
|
157
|
+
if (!entity) {
|
|
158
|
+
failures.push({ bindingId: binding.id, code: 'ONTOLOGY_UNKNOWN_ENTITY', message: `绑定 ${binding.id} 的实体「${binding.entityKey}」不存在` })
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const target = resolver(binding)
|
|
163
|
+
if (!target) {
|
|
164
|
+
throw new AppError(422, 'TARGET_NOT_FOUND',
|
|
165
|
+
`绑定 ${binding.id} 的目标 ${binding.platformKind}:${binding.platformId}${binding.targetSub ? `#${binding.targetSub}` : ''} 不存在;请先创建该节点/配方参数`)
|
|
166
|
+
}
|
|
167
|
+
assertBindingShape(entity, binding, target, opts)
|
|
168
|
+
resolved.push(target)
|
|
169
|
+
if (binding.direction === 'write' || binding.direction === 'read_write') {
|
|
170
|
+
const key = `${binding.platformKind}:${binding.platformId}${binding.targetSub ? `#${binding.targetSub}` : ''}`
|
|
171
|
+
const owner = writeOwners.get(key)
|
|
172
|
+
if (owner && owner !== binding.entityKey) {
|
|
173
|
+
failures.push({
|
|
174
|
+
bindingId: binding.id, code: 'BINDING_TARGET_OWNED',
|
|
175
|
+
message: `目标 ${key} 已被实体「${owner}」声明为写语义,不能再由「${binding.entityKey}」写入(一个物理量只能有一个写语义)`,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
else { writeOwners.set(key, binding.entityKey) }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const e = err as { code?: string, message?: string }
|
|
183
|
+
failures.push({ bindingId: binding.id, code: e.code ?? 'ONTOLOGY_INVALID', message: e.message ?? String(err) })
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { ok: failures.length === 0, failures, resolved }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 发布前/运行时的漂移检测:目标被删/停用/单位变更 → drift */
|
|
190
|
+
export interface BindingDriftReport {
|
|
191
|
+
ok: boolean
|
|
192
|
+
drifted: Array<{ bindingId: string, reason: string }>
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function detectBindingDrift(bindings: BindingRow[]): BindingDriftReport {
|
|
196
|
+
const drifted: BindingDriftReport['drifted'] = []
|
|
197
|
+
for (const row of bindings) {
|
|
198
|
+
const target = resolveBindingTarget({ platformKind: row.platformKind as BindingDraft['platformKind'], platformId: row.platformId, targetSub: row.targetSub })
|
|
199
|
+
if (!target) {
|
|
200
|
+
drifted.push({ bindingId: row.id, reason: '目标不存在(节点/配方参数已删除)' })
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
if (!target.enabled) {
|
|
204
|
+
drifted.push({ bindingId: row.id, reason: '目标已停用' })
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
if (row.unitTarget && target.unit && normalizeUnit(row.unitTarget) !== normalizeUnit(target.unit)) {
|
|
208
|
+
drifted.push({ bindingId: row.id, reason: `目标单位已变更(${row.unitTarget} → ${target.unit})` })
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
if (row.unitDeclared && target.unit && !unitsCompatible(row.unitDeclared, target.unit)) {
|
|
212
|
+
drifted.push({ bindingId: row.id, reason: `声明单位「${row.unitDeclared}」与目标单位「${target.unit}」量纲不相容` })
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { ok: drifted.length === 0, drifted }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 供 UI/工具展示:目标量纲注记 */
|
|
219
|
+
export function describeTarget(target: ResolvedTarget) {
|
|
220
|
+
return {
|
|
221
|
+
...target,
|
|
222
|
+
dimension: dimensionOf(target.unit),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { dimensionOf }
|