chainlesschain 0.162.161 → 0.162.163
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DMn528q-.js → AIOps-BQ0kgfA1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-D9ZcDCJL.js → ActionButton-Dr1MDANe.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-A_K-8PX0.js → Analytics-CZ5vsaXW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D4pFW3X6.js → AppLayout-BIBga881.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dz46COAd.js → Artifacts-vpXHRU3u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-cbMjbyg0.js → Audit-B8exuoub.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dtf88GAj.js → BackgroundAgents-923evRRN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C5KRxn52.js → Backup-sr_sswlQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dd2UK3BN.js → BaseInput-CnW-4XKT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-lptKTFdG.js → Chat-FZCsTZIL.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_yspG6rG.js → Checkbox-DFrxB59H.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BGq_GAha.js → Codegen-_b6WEel8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CePor71g.js → Col-CkbnnZ-K.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOWhIT7.js → Community-BxM_82zW.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CivJt2vT.js → Compact-DYinP45R.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Btmo1GGE.js → Compliance-DyXp3sSP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Ct35OlG4.js → Cowork-BEr2DhW7.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CTc2GmhB.js → Cron-B-Vwo1hN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-ZtiBETk1.js → Crosschain-DbLxKddd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BNqMQQXw.js → DID-BsQ_LFw9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmzZd-9V.js → Dashboard-lVlXQHu5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CMVgvP2M.js → Dropdown-Beo_foO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcqQFAfT.js → EmailListRenderer-DszHIXED.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CTqB1_4V.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CiKOOzlb.js → Federation-CuJLa2eb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Wk-OT0gr.js → FormItemContext-Cxd1lxhc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DbEQT6ue.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-0ZvCPSD9.js → Git-WqPKetx6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CmVuKkPb.js → Governance-CWYtY3ke.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CTWE3Jwg.js → Inference-BnX1iiNV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBEIF00L.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLhxDwVG.js → Logs-DqLMNntm.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BgsCBrnc.js → Marketplace-_vGfmhNl.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-ClBUGDS2.js → McpTools-BeJDOkDj.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CnG_ZnYw.js → Memory-Da0EEZDO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BUOAbR1Z.js → MobileBridge-DSuzcCCy.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dnt_9-WB.js → MobileProjects-D0RAk4e8.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CLjt4kCD.js → Mtc-BE9YgdA0.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-f24U9Aes.js → MtcAudit-fC2BpO5S.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-GrmGQbSq.js → Multisig-D24Xxqw3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DADaWn-n.js → NLProgramming-Bs3oGvS8.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-xtf5RX6C.js → Notes-BFjmAA-3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cd27Rgtx.js → NotificationSettings-BX_9Bhv0.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-fXRxlDjl.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-joCFlNB0.js → Organization-DBcdsVEP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B1HoURq0.js → Overflow-DDyaF9vC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DiwsKbT7.js → P2P-BB78L4he.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Xe-BsW6S.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C23qi8kl.js → Permissions-INbnWv4j.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BS1syX2f.js → PersonalDataHub-BST-VmTq.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DRYwwDyh.js → Pipeline-CE7_tS2k.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BQKaDHPH.js → Privacy-hK5L8dOB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BC6F9xiG.js → ProjectInit-BvkkUoDn.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ClFPc1Nd.js → ProjectSettings-u7_EUtkm.js} +2 -2
- package/src/assets/web-panel/assets/Projects-5Fmt_8Nw.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CLWuOUNt.js → Providers-B9eanarR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DiqULTxb.js → QrScannerModal-C0dX5N2f.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-9A03P-E_.js → QuickAsk-CKTpG-PZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJZAnbq_.js → Recommend-Px_w_XQM.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C9A1R77h.js → RemoteSession-Di0fnYkM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BIZc6Rky.js → Reputation-vesnWjXt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DfIPNtuG.js → Row-exbY6LkT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C5znFcQ1.js → RssFeed-B3O3x70t.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DSs2z3OX.js → Search-uRA0DhNz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CV6LcrLY.js → Security-CViqdH6K.js} +4 -4
- package/src/assets/web-panel/assets/{Services-cmhoX0Xj.js → Services-CkXS97WN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-tCsg2po7.js → Skeleton-DxW5WYnE.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BK1KUDiy.js → Skills-CLxmGFse.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yRgyeA82.js → Sla-Cr5fziXq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-c1mo1mlp.js → SpeechSettings-H0JSb3cp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-koIAfuT3.js → SyncSettings-DMYWMae7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DkkreAaB.js → Tasks-CbYyDdF4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-fy5Ctf9M.js → Templates-Cpc8JjiK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Bw70Jf6S.js → Tenant-CRh77Jda.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BKh6Rx-4.js → Terminal-4D8tNBO5.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Dg54WP0c.js → TimelineRenderer-D41jzzbG.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvvD5SxJ.js → Tokens-C_jWEy6b.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B7Ug9x4e.js → Trigger-Dk1ZUhsy.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BjvuMORY.js → Trust-CLVy4l7m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZqhkx8p.js → UkeySign-C-FeqFDj.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClR50lhX.js → VideoEditing-CqVZzyMm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DlfppYjx.js → Wallet-DX9BW9pu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-BirgVr8B.js → WebAuthn-7hQDpWw_.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-B8DP_bax.js → WorkflowEditor-6XqUAO0j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DA8AUZzL.js → chat-5R_vkaJG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C38pLGp1.js → colors-huUPZdC-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-81rtr98o.js → compact-item-CfJlgisE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D34ixq8t.js → createContext-jO8heofr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D4bzqTrZ.js → hasIn-DY8Vxqx7.js} +1 -1
- package/src/assets/web-panel/assets/{index-E1OJRXs4.js → index-A48PtXw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3zCCZRS.js → index-B300RPnT.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cd3KuNK5.js → index-B74OXFxC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmo3dFHb.js → index-BAON7TzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddk0e_Pi.js → index-BCqxyvqp.js} +1 -1
- package/src/assets/web-panel/assets/{index-9qoVpPt6.js → index-BHE8OIls.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBFSo1NF.js → index-BQS-uPOK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUQLXLQM.js → index-BRXOP09X.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6GfvEQG.js → index-BYLBPpt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFyWKOBD.js → index-BfCx4pFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP-zkXK8.js → index-BliaT02i.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMgnRwrK.js → index-BmqGerpC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIP62a6r.js → index-BsBtRWbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkHqMnn3.js → index-BtEXSoPL.js} +1 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
- package/src/assets/web-panel/assets/{index-B8mkLM4x.js → index-Bxax9vQ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZRJRItm0.js → index-C2iVSFpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP5alL4t.js → index-CA6sO5VD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS3M5H9V.js → index-CA90TZxG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvfCEJG-.js → index-CLhBh-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-tKiC8T-4.js → index-CRElRhyS.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0QArPQt.js → index-Cbe9G0V4.js} +1 -1
- package/src/assets/web-panel/assets/{index-COe_cOLJ.js → index-ChvHubX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvL_3J_n.js → index-Cis_Ip7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-F76w1NiL.js → index-Cjxbu68P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlxEphl7.js → index-CzKmteNS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuS-UPoj.js → index-D0pet5Uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbhE3j5j.js → index-D1VLuww0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_2VYPr1.js → index-DXBcvUnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DakfFZ-k.js → index-Db1cLWir.js} +1 -1
- package/src/assets/web-panel/assets/{index-fI5sXMLg.js → index-DoI2sZm7.js} +1 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
- package/src/assets/web-panel/assets/{index-CoVgZw_b.js → index-KRHroryI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvD7b2IK.js → index-d1xkbtNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtGxuQrO.js → index-dKx57io7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqIhLAbA.js → index-j3O2EEJe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXd2_LmH.js → index-p85YaaKw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFHa1HIg.js → index-v3pTaEQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Ih7Hwn.js → index-vXs3B8GJ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ByyvflIR.js → initDefaultProps-MCB7-raZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B3FQm2By.js → motion-UWDF9swV.js} +1 -1
- package/src/assets/web-panel/assets/{move-BSfns6Ef.js → move-CsTmgXZU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CTG1Ieri.js → mtc-parser-fQy6KGLg.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DObM_2j6.js → omit-woPnEjd1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DOfelQ1t.js → pickAttrs-Bs2dZ1gf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CHZILds_.js → placementArrow-C7EVgPkg.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-REbucKSx.js → responsiveObserve-BH2kYCTN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C6EZzD7o.js → slide-BFBP2V_e.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BxkWS-g3.js → statusUtils-xdDxDYse.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Chklyoq6.js → styleChecker-aAWgPPQf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CRVwKrrI.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DV24ik_J.js → useFs-BrOih40N.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C7vIo4Pg.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-By-v5GlF.js → vnode-B8lutNSE.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B1w9Am3r.js → zoom-CffNre7V.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +254 -23
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +239 -1
- package/src/commands/review.js +352 -2
- package/src/data/changelog.json +14 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +5 -2
- package/src/lib/agent-schedule-store.js +289 -10
- package/src/lib/agents.js +19 -0
- package/src/lib/background-agent-supervisor.js +34 -7
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/docs-drift.js +166 -0
- package/src/lib/doctor-checkup.js +128 -0
- package/src/lib/hook-event-bus.cjs +0 -0
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +75 -12
- package/src/lib/json-schema-validate.js +831 -0
- package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
- package/src/lib/lsp/diagnostics-scheduler.js +127 -0
- package/src/lib/operation-fingerprint.js +95 -0
- package/src/lib/plugin-runtime/capabilities.js +371 -0
- package/src/lib/plugin-runtime/capability-consent.js +175 -0
- package/src/lib/plugin-runtime/manifest.js +30 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +16 -0
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +52 -0
- package/src/lib/review-pipeline.js +227 -0
- package/src/lib/runtime-checkup.js +318 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/session-lifecycle.js +305 -0
- package/src/lib/settings-hook-events.cjs +52 -13
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +296 -0
- package/src/lib/sub-agent-context.js +74 -5
- package/src/lib/subagent-contract.js +383 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry-ids.js +190 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/turn-binding.js +397 -0
- package/src/repl/agent-repl.js +136 -0
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +387 -14
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/headless-runner.js +267 -0
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C2KLtwHz.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +0 -1
- package/src/assets/web-panel/assets/index-CY1NURWZ.js +0 -1
- package/src/assets/web-panel/assets/index-DokUYqpq.js +0 -1
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema (Draft 2020-12 subset) validator with STABLE error codes + RFC
|
|
3
|
+
* 6901 JSON Pointers, schema meta-validation, a schema digest, and a
|
|
4
|
+
* `structured_result` stream event (P2 "JSON Schema 结构化输出").
|
|
5
|
+
*
|
|
6
|
+
* The existing [[json-schema-output.js]] validator covers only
|
|
7
|
+
* type/properties/required/items/enum/const/additionalProperties:false and
|
|
8
|
+
* reports `$.path`-style strings. This module is the richer, protocol-facing
|
|
9
|
+
* core:
|
|
10
|
+
*
|
|
11
|
+
* validate(value, schema) → { valid, errors:[{code, keyword, instancePath,
|
|
12
|
+
* schemaPath, message}] } — JSON Pointers + codes
|
|
13
|
+
* validateSchema(schema) → meta-validate the schema itself (startup check)
|
|
14
|
+
* computeSchemaDigest(s) → "sha256:…" over the canonical schema
|
|
15
|
+
* buildStructuredResultEvent({schema, value})
|
|
16
|
+
* → { type:"structured_result", schema_digest,
|
|
17
|
+
* valid, value, errors? }
|
|
18
|
+
*
|
|
19
|
+
* On failure the caller must surface the codes + pointers, never silently fall
|
|
20
|
+
* back to free text. Pure (crypto only hashes the digest); zero deps.
|
|
21
|
+
*
|
|
22
|
+
* Supported keywords: type (+ integer), enum, const, properties, required,
|
|
23
|
+
* additionalProperties (bool|schema), patternProperties-free, items,
|
|
24
|
+
* prefixItems, minItems/maxItems, uniqueItems, minLength/maxLength, pattern,
|
|
25
|
+
* format (date-time/date/time/email/uri/uuid/ipv4/ipv6/hostname — asserted;
|
|
26
|
+
* unknown formats pass through as annotations), minimum/maximum/
|
|
27
|
+
* exclusiveMinimum/exclusiveMaximum, multipleOf, minProperties/maxProperties,
|
|
28
|
+
* allOf/anyOf/oneOf/not, if/then/else, and local `$ref` (`#/$defs/…`,
|
|
29
|
+
* `#/definitions/…`, or any in-document JSON Pointer).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import crypto from "crypto";
|
|
33
|
+
|
|
34
|
+
const KNOWN_TYPES = new Set([
|
|
35
|
+
"object",
|
|
36
|
+
"array",
|
|
37
|
+
"string",
|
|
38
|
+
"number",
|
|
39
|
+
"integer",
|
|
40
|
+
"boolean",
|
|
41
|
+
"null",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const MAX_REF_DEPTH = 128; // guards recursive $ref against cyclic data
|
|
45
|
+
|
|
46
|
+
// ─── format assertions ───────────────────────────────────────────────────────
|
|
47
|
+
// Draft 2020-12 `format` is asserted here (the protocol-facing structured-output
|
|
48
|
+
// contract wants it enforced, not merely annotated). A format not in this map is
|
|
49
|
+
// treated as an annotation and always passes. All checkers are pure strings→bool.
|
|
50
|
+
|
|
51
|
+
function isCalendarDate(y, m, d) {
|
|
52
|
+
if (m < 1 || m > 12 || d < 1) return false;
|
|
53
|
+
const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
54
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
55
|
+
return d <= days[m - 1];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkDate(v) {
|
|
59
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
60
|
+
return !!m && isCalendarDate(+m[1], +m[2], +m[3]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkTime(v) {
|
|
64
|
+
const m = /^(\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-]\d{2}:\d{2})?$/.exec(v);
|
|
65
|
+
if (!m) return false;
|
|
66
|
+
const hh = +m[1];
|
|
67
|
+
const mm = +m[2];
|
|
68
|
+
const ss = +m[3];
|
|
69
|
+
return hh <= 23 && mm <= 59 && ss <= 60; // 60 allows a leap second
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function checkDateTime(v) {
|
|
73
|
+
const m = /^(.+)[Tt](.+)$/.exec(v);
|
|
74
|
+
if (!m) return false;
|
|
75
|
+
// RFC 3339 date-time requires an offset/Z on the time part.
|
|
76
|
+
if (!/([Zz]|[+-]\d{2}:\d{2})$/.test(m[2])) return false;
|
|
77
|
+
return checkDate(m[1]) && checkTime(m[2]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkOctet(s) {
|
|
81
|
+
if (!/^\d{1,3}$/.test(s)) return false;
|
|
82
|
+
if (s.length > 1 && s[0] === "0") return false; // no leading zeros
|
|
83
|
+
return +s <= 255;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function checkIpv4(v) {
|
|
87
|
+
const parts = v.split(".");
|
|
88
|
+
return parts.length === 4 && parts.every(checkOctet);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function checkIpv6(v) {
|
|
92
|
+
if (typeof v !== "string" || v.length === 0) return false;
|
|
93
|
+
// At most one "::" compression; split on it.
|
|
94
|
+
const halves = v.split("::");
|
|
95
|
+
if (halves.length > 2) return false;
|
|
96
|
+
const hextet = /^[0-9a-fA-F]{1,4}$/;
|
|
97
|
+
if (halves.length === 2) {
|
|
98
|
+
const left = halves[0] === "" ? [] : halves[0].split(":");
|
|
99
|
+
const right = halves[1] === "" ? [] : halves[1].split(":");
|
|
100
|
+
if (
|
|
101
|
+
!left.every((g) => hextet.test(g)) ||
|
|
102
|
+
!right.every((g) => hextet.test(g))
|
|
103
|
+
)
|
|
104
|
+
return false;
|
|
105
|
+
return left.length + right.length <= 7; // "::" stands for ≥1 zero group
|
|
106
|
+
}
|
|
107
|
+
const groups = v.split(":");
|
|
108
|
+
return groups.length === 8 && groups.every((g) => hextet.test(g));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function checkHostname(v) {
|
|
112
|
+
if (typeof v !== "string" || v.length === 0 || v.length > 253) return false;
|
|
113
|
+
const labels = v.replace(/\.$/, "").split(".");
|
|
114
|
+
return labels.every((l) =>
|
|
115
|
+
/^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(l),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const FORMAT_CHECKERS = {
|
|
120
|
+
"date-time": checkDateTime,
|
|
121
|
+
date: checkDate,
|
|
122
|
+
time: checkTime,
|
|
123
|
+
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
|
|
124
|
+
uri: (v) => /^[A-Za-z][A-Za-z0-9+.-]*:\S*$/.test(v),
|
|
125
|
+
uuid: (v) =>
|
|
126
|
+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
|
127
|
+
v,
|
|
128
|
+
),
|
|
129
|
+
ipv4: checkIpv4,
|
|
130
|
+
ipv6: checkIpv6,
|
|
131
|
+
hostname: checkHostname,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** Known format names (exported for meta-validation / callers). */
|
|
135
|
+
export const KNOWN_FORMATS = new Set(Object.keys(FORMAT_CHECKERS));
|
|
136
|
+
|
|
137
|
+
/** RFC 6901 JSON Pointer from a segment array ("" = document root). */
|
|
138
|
+
export function jsonPointer(segments) {
|
|
139
|
+
if (!segments || segments.length === 0) return "";
|
|
140
|
+
return (
|
|
141
|
+
"/" +
|
|
142
|
+
segments
|
|
143
|
+
.map((s) => String(s).replace(/~/g, "~0").replace(/\//g, "~1"))
|
|
144
|
+
.join("/")
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** JSON value type per Draft 2020-12 (integer is a refinement of number). */
|
|
149
|
+
function typeOf(v) {
|
|
150
|
+
if (v === null) return "null";
|
|
151
|
+
if (Array.isArray(v)) return "array";
|
|
152
|
+
if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number";
|
|
153
|
+
return typeof v;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function deepEqual(a, b) {
|
|
157
|
+
return canonical(a) === canonical(b);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Canonical JSON (sorted keys) — stable across key order for digest + equality. */
|
|
161
|
+
export function canonical(value) {
|
|
162
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
163
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
164
|
+
const keys = Object.keys(value).sort();
|
|
165
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(",")}}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** "sha256:<hex>" over the canonical form of the schema. */
|
|
169
|
+
export function computeSchemaDigest(schema) {
|
|
170
|
+
const h = crypto.createHash("sha256");
|
|
171
|
+
h.update(canonical(schema));
|
|
172
|
+
return `sha256:${h.digest("hex")}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Resolve a local `#/...` JSON Pointer $ref within `root`; null if unresolved. */
|
|
176
|
+
function resolveRef(root, ref) {
|
|
177
|
+
if (typeof ref !== "string" || !ref.startsWith("#")) return null;
|
|
178
|
+
const frag = ref.slice(1);
|
|
179
|
+
if (frag === "" || frag === "/") return root;
|
|
180
|
+
const parts = frag
|
|
181
|
+
.replace(/^\//, "")
|
|
182
|
+
.split("/")
|
|
183
|
+
.map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
184
|
+
let cur = root;
|
|
185
|
+
for (const p of parts) {
|
|
186
|
+
if (cur == null || typeof cur !== "object") return null;
|
|
187
|
+
cur = cur[p];
|
|
188
|
+
}
|
|
189
|
+
return cur === undefined ? null : cur;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── instance validation ─────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validate `value` against `schema`.
|
|
196
|
+
* @returns {{valid:boolean, errors:Array<{code,keyword,instancePath,schemaPath,message}>}}
|
|
197
|
+
*/
|
|
198
|
+
export function validate(value, schema) {
|
|
199
|
+
const errors = [];
|
|
200
|
+
_validate(value, schema, [], [], schema, errors, 0);
|
|
201
|
+
return { valid: errors.length === 0, errors };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function _err(errors, keyword, instSegs, schemaSegs, message) {
|
|
205
|
+
errors.push({
|
|
206
|
+
code: keyword,
|
|
207
|
+
keyword,
|
|
208
|
+
instancePath: jsonPointer(instSegs),
|
|
209
|
+
schemaPath: jsonPointer(schemaSegs),
|
|
210
|
+
message,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function _validate(value, schema, inst, sch, root, errors, depth) {
|
|
215
|
+
if (schema === true || schema === undefined) return;
|
|
216
|
+
if (schema === false) {
|
|
217
|
+
_err(errors, "false", inst, sch, "schema is `false` — no value is valid");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!schema || typeof schema !== "object") return;
|
|
221
|
+
if (depth > MAX_REF_DEPTH) return; // cyclic $ref guard
|
|
222
|
+
|
|
223
|
+
// $ref — validate against the referenced schema (local only).
|
|
224
|
+
if (typeof schema.$ref === "string") {
|
|
225
|
+
const target = resolveRef(root, schema.$ref);
|
|
226
|
+
if (target == null) {
|
|
227
|
+
_err(
|
|
228
|
+
errors,
|
|
229
|
+
"$ref",
|
|
230
|
+
inst,
|
|
231
|
+
[...sch, "$ref"],
|
|
232
|
+
`cannot resolve $ref "${schema.$ref}"`,
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
_validate(value, target, inst, [...sch, "$ref"], root, errors, depth + 1);
|
|
236
|
+
}
|
|
237
|
+
// sibling keywords still apply in 2020-12; fall through.
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const t = typeOf(value);
|
|
241
|
+
|
|
242
|
+
if (schema.type !== undefined) {
|
|
243
|
+
const want = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
244
|
+
const ok = want.some((x) => x === t || (x === "number" && t === "integer"));
|
|
245
|
+
if (!ok) {
|
|
246
|
+
_err(
|
|
247
|
+
errors,
|
|
248
|
+
"type",
|
|
249
|
+
inst,
|
|
250
|
+
[...sch, "type"],
|
|
251
|
+
`expected type ${want.join("|")}, got ${t}`,
|
|
252
|
+
);
|
|
253
|
+
return; // type mismatch — deeper checks would be noise
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (schema.enum !== undefined) {
|
|
258
|
+
if (
|
|
259
|
+
!Array.isArray(schema.enum) ||
|
|
260
|
+
!schema.enum.some((e) => deepEqual(e, value))
|
|
261
|
+
) {
|
|
262
|
+
_err(
|
|
263
|
+
errors,
|
|
264
|
+
"enum",
|
|
265
|
+
inst,
|
|
266
|
+
[...sch, "enum"],
|
|
267
|
+
"value is not one of the allowed enum values",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (schema.const !== undefined && !deepEqual(schema.const, value)) {
|
|
272
|
+
_err(
|
|
273
|
+
errors,
|
|
274
|
+
"const",
|
|
275
|
+
inst,
|
|
276
|
+
[...sch, "const"],
|
|
277
|
+
`value must equal ${JSON.stringify(schema.const)}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (t === "string") _validateString(value, schema, inst, sch, errors);
|
|
282
|
+
if (t === "number" || t === "integer")
|
|
283
|
+
_validateNumber(value, schema, inst, sch, errors);
|
|
284
|
+
if (t === "array")
|
|
285
|
+
_validateArray(value, schema, inst, sch, root, errors, depth);
|
|
286
|
+
if (t === "object")
|
|
287
|
+
_validateObject(value, schema, inst, sch, root, errors, depth);
|
|
288
|
+
|
|
289
|
+
_validateCombinators(value, schema, inst, sch, root, errors, depth);
|
|
290
|
+
_validateConditional(value, schema, inst, sch, root, errors, depth);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* if/then/else — Draft 2020-12 conditional application. `if` is a *test* whose
|
|
295
|
+
* own errors are never reported; if the value matches `if`, `then` applies,
|
|
296
|
+
* otherwise `else` applies. No `if` keyword ⇒ then/else are inert.
|
|
297
|
+
*/
|
|
298
|
+
function _validateConditional(value, schema, inst, sch, root, errors, depth) {
|
|
299
|
+
if (schema.if === undefined) return;
|
|
300
|
+
const matched = _branchValid(value, schema.if, root, depth);
|
|
301
|
+
if (matched) {
|
|
302
|
+
if (schema.then !== undefined) {
|
|
303
|
+
_validate(
|
|
304
|
+
value,
|
|
305
|
+
schema.then,
|
|
306
|
+
inst,
|
|
307
|
+
[...sch, "then"],
|
|
308
|
+
root,
|
|
309
|
+
errors,
|
|
310
|
+
depth,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
} else if (schema.else !== undefined) {
|
|
314
|
+
_validate(value, schema.else, inst, [...sch, "else"], root, errors, depth);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function _validateString(value, schema, inst, sch, errors) {
|
|
319
|
+
if (Number.isFinite(schema.minLength) && value.length < schema.minLength) {
|
|
320
|
+
_err(
|
|
321
|
+
errors,
|
|
322
|
+
"minLength",
|
|
323
|
+
inst,
|
|
324
|
+
[...sch, "minLength"],
|
|
325
|
+
`string is shorter than minLength ${schema.minLength}`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (Number.isFinite(schema.maxLength) && value.length > schema.maxLength) {
|
|
329
|
+
_err(
|
|
330
|
+
errors,
|
|
331
|
+
"maxLength",
|
|
332
|
+
inst,
|
|
333
|
+
[...sch, "maxLength"],
|
|
334
|
+
`string is longer than maxLength ${schema.maxLength}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if (typeof schema.pattern === "string") {
|
|
338
|
+
let re;
|
|
339
|
+
try {
|
|
340
|
+
re = new RegExp(schema.pattern);
|
|
341
|
+
} catch {
|
|
342
|
+
re = null;
|
|
343
|
+
}
|
|
344
|
+
if (re && !re.test(value)) {
|
|
345
|
+
_err(
|
|
346
|
+
errors,
|
|
347
|
+
"pattern",
|
|
348
|
+
inst,
|
|
349
|
+
[...sch, "pattern"],
|
|
350
|
+
`string does not match pattern ${schema.pattern}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (typeof schema.format === "string") {
|
|
355
|
+
const check = FORMAT_CHECKERS[schema.format];
|
|
356
|
+
// Unknown format = annotation only (always passes), per Draft 2020-12.
|
|
357
|
+
if (check && !check(value)) {
|
|
358
|
+
_err(
|
|
359
|
+
errors,
|
|
360
|
+
"format",
|
|
361
|
+
inst,
|
|
362
|
+
[...sch, "format"],
|
|
363
|
+
`string is not a valid ${schema.format}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function _validateNumber(value, schema, inst, sch, errors) {
|
|
370
|
+
if (Number.isFinite(schema.minimum) && value < schema.minimum) {
|
|
371
|
+
_err(
|
|
372
|
+
errors,
|
|
373
|
+
"minimum",
|
|
374
|
+
inst,
|
|
375
|
+
[...sch, "minimum"],
|
|
376
|
+
`value is less than minimum ${schema.minimum}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (Number.isFinite(schema.maximum) && value > schema.maximum) {
|
|
380
|
+
_err(
|
|
381
|
+
errors,
|
|
382
|
+
"maximum",
|
|
383
|
+
inst,
|
|
384
|
+
[...sch, "maximum"],
|
|
385
|
+
`value is greater than maximum ${schema.maximum}`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (
|
|
389
|
+
Number.isFinite(schema.exclusiveMinimum) &&
|
|
390
|
+
value <= schema.exclusiveMinimum
|
|
391
|
+
) {
|
|
392
|
+
_err(
|
|
393
|
+
errors,
|
|
394
|
+
"exclusiveMinimum",
|
|
395
|
+
inst,
|
|
396
|
+
[...sch, "exclusiveMinimum"],
|
|
397
|
+
`value must be > ${schema.exclusiveMinimum}`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (
|
|
401
|
+
Number.isFinite(schema.exclusiveMaximum) &&
|
|
402
|
+
value >= schema.exclusiveMaximum
|
|
403
|
+
) {
|
|
404
|
+
_err(
|
|
405
|
+
errors,
|
|
406
|
+
"exclusiveMaximum",
|
|
407
|
+
inst,
|
|
408
|
+
[...sch, "exclusiveMaximum"],
|
|
409
|
+
`value must be < ${schema.exclusiveMaximum}`,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (Number.isFinite(schema.multipleOf) && schema.multipleOf > 0) {
|
|
413
|
+
const q = value / schema.multipleOf;
|
|
414
|
+
if (Math.abs(q - Math.round(q)) > 1e-9) {
|
|
415
|
+
_err(
|
|
416
|
+
errors,
|
|
417
|
+
"multipleOf",
|
|
418
|
+
inst,
|
|
419
|
+
[...sch, "multipleOf"],
|
|
420
|
+
`value is not a multiple of ${schema.multipleOf}`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function _validateArray(value, schema, inst, sch, root, errors, depth) {
|
|
427
|
+
if (Number.isFinite(schema.minItems) && value.length < schema.minItems) {
|
|
428
|
+
_err(
|
|
429
|
+
errors,
|
|
430
|
+
"minItems",
|
|
431
|
+
inst,
|
|
432
|
+
[...sch, "minItems"],
|
|
433
|
+
`array has fewer than minItems ${schema.minItems}`,
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (Number.isFinite(schema.maxItems) && value.length > schema.maxItems) {
|
|
437
|
+
_err(
|
|
438
|
+
errors,
|
|
439
|
+
"maxItems",
|
|
440
|
+
inst,
|
|
441
|
+
[...sch, "maxItems"],
|
|
442
|
+
`array has more than maxItems ${schema.maxItems}`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (schema.uniqueItems === true) {
|
|
446
|
+
const seen = new Set();
|
|
447
|
+
for (const item of value) {
|
|
448
|
+
const key = canonical(item);
|
|
449
|
+
if (seen.has(key)) {
|
|
450
|
+
_err(
|
|
451
|
+
errors,
|
|
452
|
+
"uniqueItems",
|
|
453
|
+
inst,
|
|
454
|
+
[...sch, "uniqueItems"],
|
|
455
|
+
"array items are not unique",
|
|
456
|
+
);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
seen.add(key);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const prefix = Array.isArray(schema.prefixItems) ? schema.prefixItems : [];
|
|
463
|
+
value.forEach((item, i) => {
|
|
464
|
+
if (i < prefix.length) {
|
|
465
|
+
_validate(
|
|
466
|
+
item,
|
|
467
|
+
prefix[i],
|
|
468
|
+
[...inst, i],
|
|
469
|
+
[...sch, "prefixItems", i],
|
|
470
|
+
root,
|
|
471
|
+
errors,
|
|
472
|
+
depth,
|
|
473
|
+
);
|
|
474
|
+
} else if (schema.items !== undefined) {
|
|
475
|
+
_validate(
|
|
476
|
+
item,
|
|
477
|
+
schema.items,
|
|
478
|
+
[...inst, i],
|
|
479
|
+
[...sch, "items"],
|
|
480
|
+
root,
|
|
481
|
+
errors,
|
|
482
|
+
depth,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function _validateObject(value, schema, inst, sch, root, errors, depth) {
|
|
489
|
+
const keys = Object.keys(value);
|
|
490
|
+
if (
|
|
491
|
+
Number.isFinite(schema.minProperties) &&
|
|
492
|
+
keys.length < schema.minProperties
|
|
493
|
+
) {
|
|
494
|
+
_err(
|
|
495
|
+
errors,
|
|
496
|
+
"minProperties",
|
|
497
|
+
inst,
|
|
498
|
+
[...sch, "minProperties"],
|
|
499
|
+
`object has fewer than minProperties ${schema.minProperties}`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (
|
|
503
|
+
Number.isFinite(schema.maxProperties) &&
|
|
504
|
+
keys.length > schema.maxProperties
|
|
505
|
+
) {
|
|
506
|
+
_err(
|
|
507
|
+
errors,
|
|
508
|
+
"maxProperties",
|
|
509
|
+
inst,
|
|
510
|
+
[...sch, "maxProperties"],
|
|
511
|
+
`object has more than maxProperties ${schema.maxProperties}`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
for (const req of schema.required || []) {
|
|
515
|
+
if (!Object.prototype.hasOwnProperty.call(value, req)) {
|
|
516
|
+
_err(
|
|
517
|
+
errors,
|
|
518
|
+
"required",
|
|
519
|
+
inst,
|
|
520
|
+
[...sch, "required"],
|
|
521
|
+
`missing required property "${req}"`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const props = schema.properties || {};
|
|
526
|
+
for (const [k, v] of Object.entries(value)) {
|
|
527
|
+
if (Object.prototype.hasOwnProperty.call(props, k)) {
|
|
528
|
+
_validate(
|
|
529
|
+
v,
|
|
530
|
+
props[k],
|
|
531
|
+
[...inst, k],
|
|
532
|
+
[...sch, "properties", k],
|
|
533
|
+
root,
|
|
534
|
+
errors,
|
|
535
|
+
depth,
|
|
536
|
+
);
|
|
537
|
+
} else if (schema.additionalProperties === false) {
|
|
538
|
+
_err(
|
|
539
|
+
errors,
|
|
540
|
+
"additionalProperties",
|
|
541
|
+
[...inst, k],
|
|
542
|
+
[...sch, "additionalProperties"],
|
|
543
|
+
`unexpected property "${k}"`,
|
|
544
|
+
);
|
|
545
|
+
} else if (
|
|
546
|
+
schema.additionalProperties &&
|
|
547
|
+
typeof schema.additionalProperties === "object"
|
|
548
|
+
) {
|
|
549
|
+
_validate(
|
|
550
|
+
v,
|
|
551
|
+
schema.additionalProperties,
|
|
552
|
+
[...inst, k],
|
|
553
|
+
[...sch, "additionalProperties"],
|
|
554
|
+
root,
|
|
555
|
+
errors,
|
|
556
|
+
depth,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function _branchValid(value, subschema, root, depth) {
|
|
563
|
+
const errs = [];
|
|
564
|
+
_validate(value, subschema, [], [], root, errs, depth + 1);
|
|
565
|
+
return errs.length === 0;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function _validateCombinators(value, schema, inst, sch, root, errors, depth) {
|
|
569
|
+
if (Array.isArray(schema.allOf)) {
|
|
570
|
+
schema.allOf.forEach((s, i) => {
|
|
571
|
+
_validate(value, s, inst, [...sch, "allOf", i], root, errors, depth);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (Array.isArray(schema.anyOf)) {
|
|
575
|
+
const ok = schema.anyOf.some((s) => _branchValid(value, s, root, depth));
|
|
576
|
+
if (!ok)
|
|
577
|
+
_err(
|
|
578
|
+
errors,
|
|
579
|
+
"anyOf",
|
|
580
|
+
inst,
|
|
581
|
+
[...sch, "anyOf"],
|
|
582
|
+
"value does not match any of the anyOf schemas",
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
if (Array.isArray(schema.oneOf)) {
|
|
586
|
+
const matches = schema.oneOf.filter((s) =>
|
|
587
|
+
_branchValid(value, s, root, depth),
|
|
588
|
+
).length;
|
|
589
|
+
if (matches !== 1) {
|
|
590
|
+
_err(
|
|
591
|
+
errors,
|
|
592
|
+
"oneOf",
|
|
593
|
+
inst,
|
|
594
|
+
[...sch, "oneOf"],
|
|
595
|
+
`value matched ${matches} oneOf schemas (must match exactly 1)`,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (
|
|
600
|
+
schema.not !== undefined &&
|
|
601
|
+
_branchValid(value, schema.not, root, depth)
|
|
602
|
+
) {
|
|
603
|
+
_err(
|
|
604
|
+
errors,
|
|
605
|
+
"not",
|
|
606
|
+
inst,
|
|
607
|
+
[...sch, "not"],
|
|
608
|
+
"value must NOT match the `not` schema",
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ─── schema meta-validation (startup) ────────────────────────────────────────
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Meta-validate a schema itself — the "启动时校验 schema" check. Rejects a
|
|
617
|
+
* malformed schema (bad type value, non-array required, non-number numeric
|
|
618
|
+
* keyword, invalid regex, non-object properties, …) so a broken contract fails
|
|
619
|
+
* fast instead of silently mis-validating every value.
|
|
620
|
+
*
|
|
621
|
+
* @returns {{valid:boolean, errors:Array<{code, schemaPath, message}>}}
|
|
622
|
+
*/
|
|
623
|
+
export function validateSchema(schema) {
|
|
624
|
+
const errors = [];
|
|
625
|
+
_metaValidate(schema, [], errors, 0);
|
|
626
|
+
return { valid: errors.length === 0, errors };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function _metaErr(errors, code, segs, message) {
|
|
630
|
+
errors.push({ code, schemaPath: jsonPointer(segs), message });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function _metaValidate(schema, segs, errors, depth) {
|
|
634
|
+
if (schema === true || schema === false) return; // boolean schemas are valid
|
|
635
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
636
|
+
_metaErr(errors, "schema", segs, "schema must be an object or a boolean");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (depth > MAX_REF_DEPTH) return;
|
|
640
|
+
|
|
641
|
+
if (schema.type !== undefined) {
|
|
642
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
643
|
+
for (const x of types) {
|
|
644
|
+
if (!KNOWN_TYPES.has(x)) {
|
|
645
|
+
_metaErr(errors, "type", [...segs, "type"], `unknown type "${x}"`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (schema.required !== undefined) {
|
|
650
|
+
if (
|
|
651
|
+
!Array.isArray(schema.required) ||
|
|
652
|
+
schema.required.some((r) => typeof r !== "string")
|
|
653
|
+
) {
|
|
654
|
+
_metaErr(
|
|
655
|
+
errors,
|
|
656
|
+
"required",
|
|
657
|
+
[...segs, "required"],
|
|
658
|
+
"required must be an array of strings",
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (schema.enum !== undefined && !Array.isArray(schema.enum)) {
|
|
663
|
+
_metaErr(errors, "enum", [...segs, "enum"], "enum must be an array");
|
|
664
|
+
}
|
|
665
|
+
for (const k of [
|
|
666
|
+
"minimum",
|
|
667
|
+
"maximum",
|
|
668
|
+
"exclusiveMinimum",
|
|
669
|
+
"exclusiveMaximum",
|
|
670
|
+
"multipleOf",
|
|
671
|
+
]) {
|
|
672
|
+
if (schema[k] !== undefined && typeof schema[k] !== "number") {
|
|
673
|
+
_metaErr(errors, k, [...segs, k], `${k} must be a number`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (
|
|
677
|
+
schema.multipleOf !== undefined &&
|
|
678
|
+
typeof schema.multipleOf === "number" &&
|
|
679
|
+
schema.multipleOf <= 0
|
|
680
|
+
) {
|
|
681
|
+
_metaErr(
|
|
682
|
+
errors,
|
|
683
|
+
"multipleOf",
|
|
684
|
+
[...segs, "multipleOf"],
|
|
685
|
+
"multipleOf must be > 0",
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
for (const k of [
|
|
689
|
+
"minLength",
|
|
690
|
+
"maxLength",
|
|
691
|
+
"minItems",
|
|
692
|
+
"maxItems",
|
|
693
|
+
"minProperties",
|
|
694
|
+
"maxProperties",
|
|
695
|
+
]) {
|
|
696
|
+
if (
|
|
697
|
+
schema[k] !== undefined &&
|
|
698
|
+
(!Number.isInteger(schema[k]) || schema[k] < 0)
|
|
699
|
+
) {
|
|
700
|
+
_metaErr(errors, k, [...segs, k], `${k} must be a non-negative integer`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (schema.pattern !== undefined) {
|
|
704
|
+
if (typeof schema.pattern !== "string") {
|
|
705
|
+
_metaErr(
|
|
706
|
+
errors,
|
|
707
|
+
"pattern",
|
|
708
|
+
[...segs, "pattern"],
|
|
709
|
+
"pattern must be a string",
|
|
710
|
+
);
|
|
711
|
+
} else {
|
|
712
|
+
try {
|
|
713
|
+
new RegExp(schema.pattern);
|
|
714
|
+
} catch (e) {
|
|
715
|
+
_metaErr(
|
|
716
|
+
errors,
|
|
717
|
+
"pattern",
|
|
718
|
+
[...segs, "pattern"],
|
|
719
|
+
`pattern is not a valid regex: ${e.message}`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (schema.$ref !== undefined && typeof schema.$ref !== "string") {
|
|
725
|
+
_metaErr(errors, "$ref", [...segs, "$ref"], "$ref must be a string");
|
|
726
|
+
}
|
|
727
|
+
if (schema.properties !== undefined) {
|
|
728
|
+
if (
|
|
729
|
+
typeof schema.properties !== "object" ||
|
|
730
|
+
Array.isArray(schema.properties)
|
|
731
|
+
) {
|
|
732
|
+
_metaErr(
|
|
733
|
+
errors,
|
|
734
|
+
"properties",
|
|
735
|
+
[...segs, "properties"],
|
|
736
|
+
"properties must be an object",
|
|
737
|
+
);
|
|
738
|
+
} else {
|
|
739
|
+
for (const [k, sub] of Object.entries(schema.properties)) {
|
|
740
|
+
_metaValidate(sub, [...segs, "properties", k], errors, depth + 1);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (
|
|
745
|
+
schema.additionalProperties !== undefined &&
|
|
746
|
+
typeof schema.additionalProperties === "object"
|
|
747
|
+
) {
|
|
748
|
+
_metaValidate(
|
|
749
|
+
schema.additionalProperties,
|
|
750
|
+
[...segs, "additionalProperties"],
|
|
751
|
+
errors,
|
|
752
|
+
depth + 1,
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
if (schema.items !== undefined) {
|
|
756
|
+
_metaValidate(schema.items, [...segs, "items"], errors, depth + 1);
|
|
757
|
+
}
|
|
758
|
+
if (schema.prefixItems !== undefined) {
|
|
759
|
+
if (!Array.isArray(schema.prefixItems)) {
|
|
760
|
+
_metaErr(
|
|
761
|
+
errors,
|
|
762
|
+
"prefixItems",
|
|
763
|
+
[...segs, "prefixItems"],
|
|
764
|
+
"prefixItems must be an array of schemas",
|
|
765
|
+
);
|
|
766
|
+
} else {
|
|
767
|
+
schema.prefixItems.forEach((sub, i) =>
|
|
768
|
+
_metaValidate(sub, [...segs, "prefixItems", i], errors, depth + 1),
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const k of ["allOf", "anyOf", "oneOf"]) {
|
|
773
|
+
if (schema[k] !== undefined) {
|
|
774
|
+
if (!Array.isArray(schema[k])) {
|
|
775
|
+
_metaErr(errors, k, [...segs, k], `${k} must be an array of schemas`);
|
|
776
|
+
} else {
|
|
777
|
+
schema[k].forEach((sub, i) =>
|
|
778
|
+
_metaValidate(sub, [...segs, k, i], errors, depth + 1),
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (schema.not !== undefined)
|
|
784
|
+
_metaValidate(schema.not, [...segs, "not"], errors, depth + 1);
|
|
785
|
+
if (schema.format !== undefined && typeof schema.format !== "string") {
|
|
786
|
+
_metaErr(errors, "format", [...segs, "format"], "format must be a string");
|
|
787
|
+
}
|
|
788
|
+
for (const k of ["if", "then", "else"]) {
|
|
789
|
+
if (schema[k] !== undefined) {
|
|
790
|
+
_metaValidate(schema[k], [...segs, k], errors, depth + 1);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const defsKey of ["$defs", "definitions"]) {
|
|
794
|
+
if (schema[defsKey] !== undefined) {
|
|
795
|
+
if (
|
|
796
|
+
typeof schema[defsKey] !== "object" ||
|
|
797
|
+
Array.isArray(schema[defsKey])
|
|
798
|
+
) {
|
|
799
|
+
_metaErr(
|
|
800
|
+
errors,
|
|
801
|
+
defsKey,
|
|
802
|
+
[...segs, defsKey],
|
|
803
|
+
`${defsKey} must be an object`,
|
|
804
|
+
);
|
|
805
|
+
} else {
|
|
806
|
+
for (const [k, sub] of Object.entries(schema[defsKey])) {
|
|
807
|
+
_metaValidate(sub, [...segs, defsKey, k], errors, depth + 1);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// ─── structured_result stream event ──────────────────────────────────────────
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Build the terminal `structured_result` stream event. Carries the schema
|
|
818
|
+
* digest (so a consumer can pin the contract), the validity, the value, and —
|
|
819
|
+
* when invalid — the coded/pointered errors. Never returns free text.
|
|
820
|
+
*/
|
|
821
|
+
export function buildStructuredResultEvent({ schema, value }) {
|
|
822
|
+
const { valid, errors } = validate(value, schema);
|
|
823
|
+
const event = {
|
|
824
|
+
type: "structured_result",
|
|
825
|
+
schema_digest: computeSchemaDigest(schema),
|
|
826
|
+
valid,
|
|
827
|
+
value,
|
|
828
|
+
};
|
|
829
|
+
if (!valid) event.errors = errors;
|
|
830
|
+
return event;
|
|
831
|
+
}
|