mdb-engine 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mdb_engine/__init__.py +38 -6
- mdb_engine/auth/README.md +534 -11
- mdb_engine/auth/__init__.py +129 -28
- mdb_engine/auth/audit.py +592 -0
- mdb_engine/auth/casbin_factory.py +10 -14
- mdb_engine/auth/config_helpers.py +7 -6
- mdb_engine/auth/cookie_utils.py +3 -7
- mdb_engine/auth/csrf.py +373 -0
- mdb_engine/auth/decorators.py +3 -10
- mdb_engine/auth/dependencies.py +37 -45
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +30 -73
- mdb_engine/auth/jwt.py +2 -6
- mdb_engine/auth/middleware.py +77 -34
- mdb_engine/auth/oso_factory.py +16 -36
- mdb_engine/auth/provider.py +17 -38
- mdb_engine/auth/rate_limiter.py +504 -0
- mdb_engine/auth/restrictions.py +8 -24
- mdb_engine/auth/session_manager.py +14 -29
- mdb_engine/auth/shared_middleware.py +600 -0
- mdb_engine/auth/shared_users.py +759 -0
- mdb_engine/auth/token_store.py +14 -28
- mdb_engine/auth/users.py +54 -113
- mdb_engine/auth/utils.py +213 -15
- mdb_engine/cli/commands/generate.py +545 -9
- mdb_engine/cli/commands/validate.py +3 -7
- mdb_engine/cli/utils.py +3 -3
- mdb_engine/config.py +7 -21
- mdb_engine/constants.py +65 -0
- mdb_engine/core/README.md +117 -6
- mdb_engine/core/__init__.py +39 -7
- mdb_engine/core/app_registration.py +22 -41
- mdb_engine/core/app_secrets.py +290 -0
- mdb_engine/core/connection.py +18 -9
- mdb_engine/core/encryption.py +223 -0
- mdb_engine/core/engine.py +758 -95
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +424 -135
- mdb_engine/core/ray_integration.py +435 -0
- mdb_engine/core/seeding.py +10 -18
- mdb_engine/core/service_initialization.py +12 -23
- mdb_engine/core/types.py +2 -5
- mdb_engine/database/README.md +112 -16
- mdb_engine/database/__init__.py +17 -6
- mdb_engine/database/abstraction.py +25 -37
- mdb_engine/database/connection.py +11 -18
- mdb_engine/database/query_validator.py +367 -0
- mdb_engine/database/resource_limiter.py +204 -0
- mdb_engine/database/scoped_wrapper.py +713 -196
- mdb_engine/embeddings/__init__.py +17 -9
- mdb_engine/embeddings/dependencies.py +1 -3
- mdb_engine/embeddings/service.py +11 -25
- mdb_engine/exceptions.py +92 -0
- mdb_engine/indexes/README.md +30 -13
- mdb_engine/indexes/__init__.py +1 -0
- mdb_engine/indexes/helpers.py +1 -1
- mdb_engine/indexes/manager.py +50 -114
- mdb_engine/memory/README.md +2 -2
- mdb_engine/memory/__init__.py +1 -2
- mdb_engine/memory/service.py +30 -87
- mdb_engine/observability/README.md +4 -2
- mdb_engine/observability/__init__.py +26 -9
- mdb_engine/observability/health.py +8 -9
- mdb_engine/observability/metrics.py +32 -12
- mdb_engine/routing/README.md +1 -1
- mdb_engine/routing/__init__.py +1 -3
- mdb_engine/routing/websockets.py +25 -60
- mdb_engine-0.1.7.dist-info/METADATA +285 -0
- mdb_engine-0.1.7.dist-info/RECORD +85 -0
- mdb_engine-0.1.6.dist-info/METADATA +0 -213
- mdb_engine-0.1.6.dist-info/RECORD +0 -75
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/top_level.txt +0 -0
mdb_engine/core/manifest.py
CHANGED
|
@@ -36,9 +36,14 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
|
|
|
36
36
|
|
|
37
37
|
from jsonschema import SchemaError, ValidationError, validate
|
|
38
38
|
|
|
39
|
-
from ..constants import (
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
from ..constants import (
|
|
40
|
+
CURRENT_SCHEMA_VERSION,
|
|
41
|
+
DEFAULT_SCHEMA_VERSION,
|
|
42
|
+
MAX_TTL_SECONDS,
|
|
43
|
+
MAX_VECTOR_DIMENSIONS,
|
|
44
|
+
MIN_TTL_SECONDS,
|
|
45
|
+
MIN_VECTOR_DIMENSIONS,
|
|
46
|
+
)
|
|
42
47
|
|
|
43
48
|
logger = logging.getLogger(__name__)
|
|
44
49
|
|
|
@@ -87,9 +92,7 @@ def _get_manifest_hash(manifest_data: Dict[str, Any]) -> str:
|
|
|
87
92
|
|
|
88
93
|
# Normalize manifest by removing metadata fields that don't affect validation
|
|
89
94
|
normalized = {
|
|
90
|
-
k: v
|
|
91
|
-
for k, v in manifest_data.items()
|
|
92
|
-
if k not in ["_id", "_updated", "_created", "url"]
|
|
95
|
+
k: v for k, v in manifest_data.items() if k not in ["_id", "_updated", "_created", "url"]
|
|
93
96
|
}
|
|
94
97
|
normalized_str = json.dumps(normalized, sort_keys=True)
|
|
95
98
|
return hashlib.sha256(normalized_str.encode()).hexdigest()[:16]
|
|
@@ -137,6 +140,49 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
137
140
|
"auth": {
|
|
138
141
|
"type": "object",
|
|
139
142
|
"properties": {
|
|
143
|
+
"mode": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"enum": ["app", "shared"],
|
|
146
|
+
"default": "app",
|
|
147
|
+
"description": (
|
|
148
|
+
"Authentication mode: 'app' for per-app tokens "
|
|
149
|
+
"(isolated auth per app, default), 'shared' for "
|
|
150
|
+
"shared user pool with SSO across all apps. "
|
|
151
|
+
"Modes are mutually exclusive."
|
|
152
|
+
),
|
|
153
|
+
},
|
|
154
|
+
"roles": {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"items": {"type": "string"},
|
|
157
|
+
"description": (
|
|
158
|
+
"Available roles for this app (shared mode only). "
|
|
159
|
+
"Example: ['viewer', 'editor', 'admin']"
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
"default_role": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"description": (
|
|
165
|
+
"Default role assigned to new users for this app "
|
|
166
|
+
"(shared mode only). Must be one of the defined roles."
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
"require_role": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"description": (
|
|
172
|
+
"Minimum role required to access this app "
|
|
173
|
+
"(shared mode only). Users without this role "
|
|
174
|
+
"will be denied access."
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
"public_routes": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {"type": "string"},
|
|
180
|
+
"description": (
|
|
181
|
+
"Routes that don't require authentication. "
|
|
182
|
+
"Supports wildcards, e.g., ['/health', '/api/public/*']. "
|
|
183
|
+
"Works in both auth modes."
|
|
184
|
+
),
|
|
185
|
+
},
|
|
140
186
|
"policy": {
|
|
141
187
|
"type": "object",
|
|
142
188
|
"properties": {
|
|
@@ -484,8 +530,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
484
530
|
"type": "string",
|
|
485
531
|
"default": "user",
|
|
486
532
|
"description": (
|
|
487
|
-
"Role for demo user in app "
|
|
488
|
-
"(default: 'user')"
|
|
533
|
+
"Role for demo user in app " "(default: 'user')"
|
|
489
534
|
),
|
|
490
535
|
},
|
|
491
536
|
"auto_create": {
|
|
@@ -567,6 +612,292 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
567
612
|
"independent of platform authentication."
|
|
568
613
|
),
|
|
569
614
|
},
|
|
615
|
+
"rate_limits": {
|
|
616
|
+
"type": "object",
|
|
617
|
+
"additionalProperties": {
|
|
618
|
+
"type": "object",
|
|
619
|
+
"properties": {
|
|
620
|
+
"max_attempts": {
|
|
621
|
+
"type": "integer",
|
|
622
|
+
"minimum": 1,
|
|
623
|
+
"default": 5,
|
|
624
|
+
"description": (
|
|
625
|
+
"Maximum attempts allowed in the time window " "(default: 5)."
|
|
626
|
+
),
|
|
627
|
+
},
|
|
628
|
+
"window_seconds": {
|
|
629
|
+
"type": "integer",
|
|
630
|
+
"minimum": 1,
|
|
631
|
+
"default": 300,
|
|
632
|
+
"description": (
|
|
633
|
+
"Time window in seconds for rate limiting "
|
|
634
|
+
"(default: 300 = 5 minutes)."
|
|
635
|
+
),
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
"additionalProperties": False,
|
|
639
|
+
},
|
|
640
|
+
"description": (
|
|
641
|
+
"Rate limiting configuration for auth endpoints. "
|
|
642
|
+
"Keys are endpoint paths (e.g., '/login', '/register'). "
|
|
643
|
+
"Example: {'/login': {'max_attempts': 5, 'window_seconds': 300}}"
|
|
644
|
+
),
|
|
645
|
+
},
|
|
646
|
+
"audit": {
|
|
647
|
+
"type": "object",
|
|
648
|
+
"properties": {
|
|
649
|
+
"enabled": {
|
|
650
|
+
"type": "boolean",
|
|
651
|
+
"default": True,
|
|
652
|
+
"description": (
|
|
653
|
+
"Enable audit logging for authentication events "
|
|
654
|
+
"(default: true for shared auth mode)."
|
|
655
|
+
),
|
|
656
|
+
},
|
|
657
|
+
"retention_days": {
|
|
658
|
+
"type": "integer",
|
|
659
|
+
"minimum": 1,
|
|
660
|
+
"default": 90,
|
|
661
|
+
"description": (
|
|
662
|
+
"Number of days to retain audit logs " "(default: 90 days)."
|
|
663
|
+
),
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
"additionalProperties": False,
|
|
667
|
+
"description": (
|
|
668
|
+
"Audit logging configuration for authentication events. "
|
|
669
|
+
"Logs are stored in MongoDB with automatic TTL cleanup."
|
|
670
|
+
),
|
|
671
|
+
},
|
|
672
|
+
"csrf_protection": {
|
|
673
|
+
"oneOf": [
|
|
674
|
+
{"type": "boolean"},
|
|
675
|
+
{
|
|
676
|
+
"type": "object",
|
|
677
|
+
"properties": {
|
|
678
|
+
"enabled": {
|
|
679
|
+
"type": "boolean",
|
|
680
|
+
"default": True,
|
|
681
|
+
"description": (
|
|
682
|
+
"Enable CSRF protection "
|
|
683
|
+
"(default: true for shared auth mode)."
|
|
684
|
+
),
|
|
685
|
+
},
|
|
686
|
+
"exempt_routes": {
|
|
687
|
+
"type": "array",
|
|
688
|
+
"items": {"type": "string"},
|
|
689
|
+
"description": (
|
|
690
|
+
"Routes exempt from CSRF validation. "
|
|
691
|
+
"Supports wildcards (e.g., '/api/*'). "
|
|
692
|
+
"Defaults to public_routes if not specified."
|
|
693
|
+
),
|
|
694
|
+
},
|
|
695
|
+
"rotate_tokens": {
|
|
696
|
+
"type": "boolean",
|
|
697
|
+
"default": False,
|
|
698
|
+
"description": (
|
|
699
|
+
"Rotate CSRF token on each request "
|
|
700
|
+
"(more secure, less convenient). Default: false."
|
|
701
|
+
),
|
|
702
|
+
},
|
|
703
|
+
"token_ttl": {
|
|
704
|
+
"type": "integer",
|
|
705
|
+
"minimum": 60,
|
|
706
|
+
"default": 3600,
|
|
707
|
+
"description": (
|
|
708
|
+
"CSRF token TTL in seconds " "(default: 3600 = 1 hour)."
|
|
709
|
+
),
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
"additionalProperties": False,
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
"default": True,
|
|
716
|
+
"description": (
|
|
717
|
+
"CSRF protection configuration. Auto-enabled for shared "
|
|
718
|
+
"auth mode. Set to false to disable, or provide object "
|
|
719
|
+
"for detailed configuration. Uses double-submit cookie "
|
|
720
|
+
"pattern with SameSite=Lax cookies."
|
|
721
|
+
),
|
|
722
|
+
},
|
|
723
|
+
"security": {
|
|
724
|
+
"type": "object",
|
|
725
|
+
"properties": {
|
|
726
|
+
"hsts": {
|
|
727
|
+
"type": "object",
|
|
728
|
+
"properties": {
|
|
729
|
+
"enabled": {
|
|
730
|
+
"type": "boolean",
|
|
731
|
+
"default": True,
|
|
732
|
+
"description": (
|
|
733
|
+
"Enable HSTS header in production " "(default: true)."
|
|
734
|
+
),
|
|
735
|
+
},
|
|
736
|
+
"max_age": {
|
|
737
|
+
"type": "integer",
|
|
738
|
+
"minimum": 0,
|
|
739
|
+
"default": 31536000,
|
|
740
|
+
"description": (
|
|
741
|
+
"HSTS max-age in seconds " "(default: 31536000 = 1 year)."
|
|
742
|
+
),
|
|
743
|
+
},
|
|
744
|
+
"include_subdomains": {
|
|
745
|
+
"type": "boolean",
|
|
746
|
+
"default": True,
|
|
747
|
+
"description": (
|
|
748
|
+
"Include subdomains in HSTS policy " "(default: true)."
|
|
749
|
+
),
|
|
750
|
+
},
|
|
751
|
+
"preload": {
|
|
752
|
+
"type": "boolean",
|
|
753
|
+
"default": False,
|
|
754
|
+
"description": (
|
|
755
|
+
"Add preload directive for HSTS preload "
|
|
756
|
+
"list submission (default: false). Only "
|
|
757
|
+
"enable if you're ready for permanent HTTPS."
|
|
758
|
+
),
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
"additionalProperties": False,
|
|
762
|
+
"description": (
|
|
763
|
+
"HTTP Strict Transport Security configuration. "
|
|
764
|
+
"Forces HTTPS connections in production."
|
|
765
|
+
),
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
"additionalProperties": False,
|
|
769
|
+
"description": (
|
|
770
|
+
"Security settings including HSTS, headers, and other " "security controls."
|
|
771
|
+
),
|
|
772
|
+
},
|
|
773
|
+
"jwt": {
|
|
774
|
+
"type": "object",
|
|
775
|
+
"properties": {
|
|
776
|
+
"algorithm": {
|
|
777
|
+
"type": "string",
|
|
778
|
+
"enum": ["HS256", "RS256", "ES256"],
|
|
779
|
+
"default": "HS256",
|
|
780
|
+
"description": (
|
|
781
|
+
"JWT signing algorithm. HS256 (HMAC, default) "
|
|
782
|
+
"uses symmetric secret. RS256 (RSA) and ES256 "
|
|
783
|
+
"(ECDSA) use asymmetric key pairs for better "
|
|
784
|
+
"security in distributed systems."
|
|
785
|
+
),
|
|
786
|
+
},
|
|
787
|
+
"token_expiry_hours": {
|
|
788
|
+
"type": "integer",
|
|
789
|
+
"minimum": 1,
|
|
790
|
+
"default": 24,
|
|
791
|
+
"description": ("JWT token expiry in hours (default: 24)."),
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
"additionalProperties": False,
|
|
795
|
+
"description": (
|
|
796
|
+
"JWT configuration for shared auth mode. "
|
|
797
|
+
"Controls algorithm and token lifetime."
|
|
798
|
+
),
|
|
799
|
+
},
|
|
800
|
+
"password_policy": {
|
|
801
|
+
"type": "object",
|
|
802
|
+
"properties": {
|
|
803
|
+
"min_length": {
|
|
804
|
+
"type": "integer",
|
|
805
|
+
"minimum": 6,
|
|
806
|
+
"default": 12,
|
|
807
|
+
"description": ("Minimum password length (default: 12)."),
|
|
808
|
+
},
|
|
809
|
+
"min_entropy_bits": {
|
|
810
|
+
"type": "integer",
|
|
811
|
+
"minimum": 0,
|
|
812
|
+
"default": 50,
|
|
813
|
+
"description": (
|
|
814
|
+
"Minimum password entropy in bits "
|
|
815
|
+
"(default: 50). Set to 0 to disable."
|
|
816
|
+
),
|
|
817
|
+
},
|
|
818
|
+
"require_uppercase": {
|
|
819
|
+
"type": "boolean",
|
|
820
|
+
"default": True,
|
|
821
|
+
"description": (
|
|
822
|
+
"Require at least one uppercase letter " "(default: true)."
|
|
823
|
+
),
|
|
824
|
+
},
|
|
825
|
+
"require_lowercase": {
|
|
826
|
+
"type": "boolean",
|
|
827
|
+
"default": True,
|
|
828
|
+
"description": (
|
|
829
|
+
"Require at least one lowercase letter " "(default: true)."
|
|
830
|
+
),
|
|
831
|
+
},
|
|
832
|
+
"require_numbers": {
|
|
833
|
+
"type": "boolean",
|
|
834
|
+
"default": True,
|
|
835
|
+
"description": ("Require at least one number " "(default: true)."),
|
|
836
|
+
},
|
|
837
|
+
"require_special": {
|
|
838
|
+
"type": "boolean",
|
|
839
|
+
"default": False,
|
|
840
|
+
"description": (
|
|
841
|
+
"Require at least one special character " "(default: false)."
|
|
842
|
+
),
|
|
843
|
+
},
|
|
844
|
+
"check_common_passwords": {
|
|
845
|
+
"type": "boolean",
|
|
846
|
+
"default": True,
|
|
847
|
+
"description": (
|
|
848
|
+
"Check against common password list " "(default: true)."
|
|
849
|
+
),
|
|
850
|
+
},
|
|
851
|
+
"check_breaches": {
|
|
852
|
+
"type": "boolean",
|
|
853
|
+
"default": False,
|
|
854
|
+
"description": (
|
|
855
|
+
"Check against HaveIBeenPwned breach database "
|
|
856
|
+
"(default: false). Requires network access."
|
|
857
|
+
),
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
"additionalProperties": False,
|
|
861
|
+
"description": (
|
|
862
|
+
"Password policy configuration for shared auth mode. "
|
|
863
|
+
"Enforces password strength requirements."
|
|
864
|
+
),
|
|
865
|
+
},
|
|
866
|
+
"session_binding": {
|
|
867
|
+
"type": "object",
|
|
868
|
+
"properties": {
|
|
869
|
+
"bind_ip": {
|
|
870
|
+
"type": "boolean",
|
|
871
|
+
"default": False,
|
|
872
|
+
"description": (
|
|
873
|
+
"Bind sessions to IP address. Strict mode: "
|
|
874
|
+
"reject if IP changes (default: false)."
|
|
875
|
+
),
|
|
876
|
+
},
|
|
877
|
+
"bind_fingerprint": {
|
|
878
|
+
"type": "boolean",
|
|
879
|
+
"default": True,
|
|
880
|
+
"description": (
|
|
881
|
+
"Bind sessions to device fingerprint. Soft mode: "
|
|
882
|
+
"log warning if fingerprint changes (default: true)."
|
|
883
|
+
),
|
|
884
|
+
},
|
|
885
|
+
"allow_ip_change_with_reauth": {
|
|
886
|
+
"type": "boolean",
|
|
887
|
+
"default": True,
|
|
888
|
+
"description": (
|
|
889
|
+
"Allow IP change if user re-authenticates "
|
|
890
|
+
"(default: true). Only applies when bind_ip=true."
|
|
891
|
+
),
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
"additionalProperties": False,
|
|
895
|
+
"description": (
|
|
896
|
+
"Session binding configuration. Ties sessions to client "
|
|
897
|
+
"characteristics for additional security against session "
|
|
898
|
+
"hijacking."
|
|
899
|
+
),
|
|
900
|
+
},
|
|
570
901
|
},
|
|
571
902
|
"additionalProperties": False,
|
|
572
903
|
"description": (
|
|
@@ -590,17 +921,13 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
590
921
|
"type": "integer",
|
|
591
922
|
"minimum": 60,
|
|
592
923
|
"default": 900,
|
|
593
|
-
"description": (
|
|
594
|
-
"Access token TTL in seconds " "(default: 900 = 15 minutes)."
|
|
595
|
-
),
|
|
924
|
+
"description": ("Access token TTL in seconds " "(default: 900 = 15 minutes)."),
|
|
596
925
|
},
|
|
597
926
|
"refresh_token_ttl": {
|
|
598
927
|
"type": "integer",
|
|
599
928
|
"minimum": 3600,
|
|
600
929
|
"default": 604800,
|
|
601
|
-
"description": (
|
|
602
|
-
"Refresh token TTL in seconds " "(default: 604800 = 7 days)."
|
|
603
|
-
),
|
|
930
|
+
"description": ("Refresh token TTL in seconds " "(default: 604800 = 7 days)."),
|
|
604
931
|
},
|
|
605
932
|
"token_rotation": {
|
|
606
933
|
"type": "boolean",
|
|
@@ -615,8 +942,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
615
942
|
"minimum": 1,
|
|
616
943
|
"default": 10,
|
|
617
944
|
"description": (
|
|
618
|
-
"Maximum number of concurrent sessions per user "
|
|
619
|
-
"(default: 10)."
|
|
945
|
+
"Maximum number of concurrent sessions per user " "(default: 10)."
|
|
620
946
|
),
|
|
621
947
|
},
|
|
622
948
|
"session_inactivity_timeout": {
|
|
@@ -635,8 +961,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
635
961
|
"type": "boolean",
|
|
636
962
|
"default": False,
|
|
637
963
|
"description": (
|
|
638
|
-
"Require HTTPS in production "
|
|
639
|
-
"(default: false, auto-detected)."
|
|
964
|
+
"Require HTTPS in production " "(default: false, auto-detected)."
|
|
640
965
|
),
|
|
641
966
|
},
|
|
642
967
|
"cookie_secure": {
|
|
@@ -718,9 +1043,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
718
1043
|
},
|
|
719
1044
|
},
|
|
720
1045
|
"additionalProperties": False,
|
|
721
|
-
"description": (
|
|
722
|
-
"Rate limiting configuration per endpoint type."
|
|
723
|
-
),
|
|
1046
|
+
"description": ("Rate limiting configuration per endpoint type."),
|
|
724
1047
|
},
|
|
725
1048
|
"password_policy": {
|
|
726
1049
|
"type": "object",
|
|
@@ -747,9 +1070,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
747
1070
|
"require_lowercase": {
|
|
748
1071
|
"type": "boolean",
|
|
749
1072
|
"default": True,
|
|
750
|
-
"description": (
|
|
751
|
-
"Require lowercase letters " "(default: true)"
|
|
752
|
-
),
|
|
1073
|
+
"description": ("Require lowercase letters " "(default: true)"),
|
|
753
1074
|
},
|
|
754
1075
|
"require_numbers": {
|
|
755
1076
|
"type": "boolean",
|
|
@@ -774,24 +1095,21 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
774
1095
|
"type": "boolean",
|
|
775
1096
|
"default": True,
|
|
776
1097
|
"description": (
|
|
777
|
-
"Enable session fingerprinting "
|
|
778
|
-
"(default: true)"
|
|
1098
|
+
"Enable session fingerprinting " "(default: true)"
|
|
779
1099
|
),
|
|
780
1100
|
},
|
|
781
1101
|
"validate_on_login": {
|
|
782
1102
|
"type": "boolean",
|
|
783
1103
|
"default": True,
|
|
784
1104
|
"description": (
|
|
785
|
-
"Validate fingerprint on login "
|
|
786
|
-
"(default: true)"
|
|
1105
|
+
"Validate fingerprint on login " "(default: true)"
|
|
787
1106
|
),
|
|
788
1107
|
},
|
|
789
1108
|
"validate_on_refresh": {
|
|
790
1109
|
"type": "boolean",
|
|
791
1110
|
"default": True,
|
|
792
1111
|
"description": (
|
|
793
|
-
"Validate fingerprint on token refresh "
|
|
794
|
-
"(default: true)"
|
|
1112
|
+
"Validate fingerprint on token refresh " "(default: true)"
|
|
795
1113
|
),
|
|
796
1114
|
},
|
|
797
1115
|
"validate_on_request": {
|
|
@@ -837,8 +1155,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
837
1155
|
"minimum": 1,
|
|
838
1156
|
"default": 900,
|
|
839
1157
|
"description": (
|
|
840
|
-
"Lockout duration in seconds "
|
|
841
|
-
"(default: 900 = 15 minutes)"
|
|
1158
|
+
"Lockout duration in seconds " "(default: 900 = 15 minutes)"
|
|
842
1159
|
),
|
|
843
1160
|
},
|
|
844
1161
|
"reset_on_success": {
|
|
@@ -860,8 +1177,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
860
1177
|
"type": "boolean",
|
|
861
1178
|
"default": False,
|
|
862
1179
|
"description": (
|
|
863
|
-
"Enable IP address validation "
|
|
864
|
-
"(default: false)"
|
|
1180
|
+
"Enable IP address validation " "(default: false)"
|
|
865
1181
|
),
|
|
866
1182
|
},
|
|
867
1183
|
"strict": {
|
|
@@ -876,8 +1192,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
876
1192
|
"type": "boolean",
|
|
877
1193
|
"default": True,
|
|
878
1194
|
"description": (
|
|
879
|
-
"Allow IP address changes during session "
|
|
880
|
-
"(default: true)"
|
|
1195
|
+
"Allow IP address changes during session " "(default: true)"
|
|
881
1196
|
),
|
|
882
1197
|
},
|
|
883
1198
|
},
|
|
@@ -897,9 +1212,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
897
1212
|
"bind_to_device": {
|
|
898
1213
|
"type": "boolean",
|
|
899
1214
|
"default": True,
|
|
900
|
-
"description": (
|
|
901
|
-
"Bind tokens to device ID " "(default: true)"
|
|
902
|
-
),
|
|
1215
|
+
"description": ("Bind tokens to device ID " "(default: true)"),
|
|
903
1216
|
},
|
|
904
1217
|
},
|
|
905
1218
|
"additionalProperties": False,
|
|
@@ -913,8 +1226,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
913
1226
|
"type": "boolean",
|
|
914
1227
|
"default": True,
|
|
915
1228
|
"description": (
|
|
916
|
-
"Automatically set up token management on app startup "
|
|
917
|
-
"(default: true)."
|
|
1229
|
+
"Automatically set up token management on app startup " "(default: true)."
|
|
918
1230
|
),
|
|
919
1231
|
},
|
|
920
1232
|
},
|
|
@@ -946,9 +1258,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
946
1258
|
},
|
|
947
1259
|
"collection_settings": {
|
|
948
1260
|
"type": "object",
|
|
949
|
-
"patternProperties": {
|
|
950
|
-
"^[a-zA-Z0-9_]+$": {"$ref": "#/definitions/collectionSettings"}
|
|
951
|
-
},
|
|
1261
|
+
"patternProperties": {"^[a-zA-Z0-9_]+$": {"$ref": "#/definitions/collectionSettings"}},
|
|
952
1262
|
"description": "Collection name -> collection settings",
|
|
953
1263
|
},
|
|
954
1264
|
"websockets": {
|
|
@@ -996,8 +1306,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
996
1306
|
"description": {
|
|
997
1307
|
"type": "string",
|
|
998
1308
|
"description": (
|
|
999
|
-
"Description of what this WebSocket endpoint "
|
|
1000
|
-
"is used for"
|
|
1309
|
+
"Description of what this WebSocket endpoint " "is used for"
|
|
1001
1310
|
),
|
|
1002
1311
|
},
|
|
1003
1312
|
"ping_interval": {
|
|
@@ -1210,8 +1519,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1210
1519
|
"type": "boolean",
|
|
1211
1520
|
"default": False,
|
|
1212
1521
|
"description": (
|
|
1213
|
-
"Allow credentials (cookies, authorization headers) "
|
|
1214
|
-
"in CORS requests"
|
|
1522
|
+
"Allow credentials (cookies, authorization headers) " "in CORS requests"
|
|
1215
1523
|
),
|
|
1216
1524
|
},
|
|
1217
1525
|
"allow_methods": {
|
|
@@ -1256,6 +1564,40 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1256
1564
|
"additionalProperties": False,
|
|
1257
1565
|
"description": "CORS (Cross-Origin Resource Sharing) configuration for web apps",
|
|
1258
1566
|
},
|
|
1567
|
+
"data_access": {
|
|
1568
|
+
"type": "object",
|
|
1569
|
+
"properties": {
|
|
1570
|
+
"read_scopes": {
|
|
1571
|
+
"type": "array",
|
|
1572
|
+
"items": {"type": "string"},
|
|
1573
|
+
"description": (
|
|
1574
|
+
"List of app slugs this app can read from. "
|
|
1575
|
+
"Defaults to [app_slug] if not specified."
|
|
1576
|
+
),
|
|
1577
|
+
},
|
|
1578
|
+
"write_scope": {
|
|
1579
|
+
"type": "string",
|
|
1580
|
+
"description": (
|
|
1581
|
+
"App slug this app writes to. " "Defaults to app_slug if not specified."
|
|
1582
|
+
),
|
|
1583
|
+
},
|
|
1584
|
+
"cross_app_policy": {
|
|
1585
|
+
"type": "string",
|
|
1586
|
+
"enum": ["explicit", "deny_all"],
|
|
1587
|
+
"default": "explicit",
|
|
1588
|
+
"description": (
|
|
1589
|
+
"Policy for cross-app access. 'explicit' allows access "
|
|
1590
|
+
"to apps listed in read_scopes. 'deny_all' blocks all "
|
|
1591
|
+
"cross-app access regardless of read_scopes."
|
|
1592
|
+
),
|
|
1593
|
+
},
|
|
1594
|
+
},
|
|
1595
|
+
"additionalProperties": False,
|
|
1596
|
+
"description": (
|
|
1597
|
+
"Data access configuration defining which apps this app can "
|
|
1598
|
+
"read from and write to. Used for cross-app data access control."
|
|
1599
|
+
),
|
|
1600
|
+
},
|
|
1259
1601
|
"observability": {
|
|
1260
1602
|
"type": "object",
|
|
1261
1603
|
"properties": {
|
|
@@ -1295,8 +1637,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1295
1637
|
"type": "boolean",
|
|
1296
1638
|
"default": True,
|
|
1297
1639
|
"description": (
|
|
1298
|
-
"Collect operation-level metrics "
|
|
1299
|
-
"(duration, errors, etc.)"
|
|
1640
|
+
"Collect operation-level metrics " "(duration, errors, etc.)"
|
|
1300
1641
|
),
|
|
1301
1642
|
},
|
|
1302
1643
|
"collect_performance_metrics": {
|
|
@@ -1456,8 +1797,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1456
1797
|
"definition": {
|
|
1457
1798
|
"type": "object",
|
|
1458
1799
|
"description": (
|
|
1459
|
-
"Index definition (required for vectorSearch and "
|
|
1460
|
-
"search indexes)"
|
|
1800
|
+
"Index definition (required for vectorSearch and " "search indexes)"
|
|
1461
1801
|
),
|
|
1462
1802
|
},
|
|
1463
1803
|
"hybrid": {
|
|
@@ -1470,8 +1810,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1470
1810
|
"type": "string",
|
|
1471
1811
|
"pattern": "^[a-zA-Z0-9_]+$",
|
|
1472
1812
|
"description": (
|
|
1473
|
-
"Name for the vector index "
|
|
1474
|
-
"(defaults to '{name}_vector')"
|
|
1813
|
+
"Name for the vector index " "(defaults to '{name}_vector')"
|
|
1475
1814
|
),
|
|
1476
1815
|
},
|
|
1477
1816
|
"definition": {
|
|
@@ -1493,8 +1832,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1493
1832
|
"type": "string",
|
|
1494
1833
|
"pattern": "^[a-zA-Z0-9_]+$",
|
|
1495
1834
|
"description": (
|
|
1496
|
-
"Name for the text index "
|
|
1497
|
-
"(defaults to '{name}_text')"
|
|
1835
|
+
"Name for the text index " "(defaults to '{name}_text')"
|
|
1498
1836
|
),
|
|
1499
1837
|
},
|
|
1500
1838
|
"definition": {
|
|
@@ -1575,9 +1913,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1575
1913
|
"then": {"required": ["keys", "options"]},
|
|
1576
1914
|
"else": {
|
|
1577
1915
|
"properties": {
|
|
1578
|
-
"options": {
|
|
1579
|
-
"not": {"required": ["partialFilterExpression"]}
|
|
1580
|
-
}
|
|
1916
|
+
"options": {"not": {"required": ["partialFilterExpression"]}}
|
|
1581
1917
|
}
|
|
1582
1918
|
},
|
|
1583
1919
|
},
|
|
@@ -1803,9 +2139,7 @@ def migrate_manifest(
|
|
|
1803
2139
|
|
|
1804
2140
|
# No data transformation needed - V2.0 is backward compatible
|
|
1805
2141
|
# New fields (auth, etc.) are optional
|
|
1806
|
-
logger.debug(
|
|
1807
|
-
f"Migrated manifest from 1.0 to 2.0: {migrated.get('slug', 'unknown')}"
|
|
1808
|
-
)
|
|
2142
|
+
logger.debug(f"Migrated manifest from 1.0 to 2.0: {migrated.get('slug', 'unknown')}")
|
|
1809
2143
|
|
|
1810
2144
|
# Future: Add more migration paths as needed
|
|
1811
2145
|
# Example: 2.0 -> 3.0, etc.
|
|
@@ -1841,8 +2175,7 @@ def get_schema_for_version(version: str) -> Dict[str, Any]:
|
|
|
1841
2175
|
|
|
1842
2176
|
# Fallback to current
|
|
1843
2177
|
logger.warning(
|
|
1844
|
-
f"Schema version {version} not found, using current version "
|
|
1845
|
-
f"{CURRENT_SCHEMA_VERSION}"
|
|
2178
|
+
f"Schema version {version} not found, using current version " f"{CURRENT_SCHEMA_VERSION}"
|
|
1846
2179
|
)
|
|
1847
2180
|
return SCHEMA_REGISTRY[CURRENT_SCHEMA_VERSION]
|
|
1848
2181
|
|
|
@@ -1873,12 +2206,11 @@ async def _validate_manifest_async(
|
|
|
1873
2206
|
Use validate_manifest_with_db() for database validation.
|
|
1874
2207
|
"""
|
|
1875
2208
|
# Check cache first
|
|
2209
|
+
cache_key = None
|
|
1876
2210
|
if use_cache:
|
|
1877
|
-
cache_key = (
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
if cache_key in _validation_cache:
|
|
1881
|
-
return _validation_cache[cache_key]
|
|
2211
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
2212
|
+
if cache_key in _validation_cache:
|
|
2213
|
+
return _validation_cache[cache_key]
|
|
1882
2214
|
|
|
1883
2215
|
try:
|
|
1884
2216
|
# Get schema version
|
|
@@ -1935,11 +2267,7 @@ async def _validate_manifest_async(
|
|
|
1935
2267
|
error_message = f"Invalid schema definition: {e.message}"
|
|
1936
2268
|
result = (False, error_message, ["schema"])
|
|
1937
2269
|
if use_cache:
|
|
1938
|
-
cache_key = (
|
|
1939
|
-
_get_manifest_hash(manifest_data)
|
|
1940
|
-
+ "_"
|
|
1941
|
-
+ get_schema_version(manifest_data)
|
|
1942
|
-
)
|
|
2270
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1943
2271
|
_validation_cache[cache_key] = result
|
|
1944
2272
|
|
|
1945
2273
|
return result
|
|
@@ -1949,9 +2277,7 @@ async def _validate_manifest_async(
|
|
|
1949
2277
|
error_paths = []
|
|
1950
2278
|
error_messages = []
|
|
1951
2279
|
if isinstance(e, ValidationError):
|
|
1952
|
-
error_paths = [
|
|
1953
|
-
f".{'.'.join(str(p) for p in error.path)}" for error in e.context or [e]
|
|
1954
|
-
]
|
|
2280
|
+
error_paths = [f".{'.'.join(str(p) for p in error.path)}" for error in e.context or [e]]
|
|
1955
2281
|
error_messages = [error.message for error in e.context or [e]]
|
|
1956
2282
|
else:
|
|
1957
2283
|
error_messages = [str(e)]
|
|
@@ -1959,11 +2285,7 @@ async def _validate_manifest_async(
|
|
|
1959
2285
|
error_message = "; ".join(error_messages) if error_messages else str(e)
|
|
1960
2286
|
result = (False, error_message, error_paths if error_paths else None)
|
|
1961
2287
|
if use_cache:
|
|
1962
|
-
cache_key = (
|
|
1963
|
-
_get_manifest_hash(manifest_data)
|
|
1964
|
-
+ "_"
|
|
1965
|
-
+ get_schema_version(manifest_data)
|
|
1966
|
-
)
|
|
2288
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1967
2289
|
_validation_cache[cache_key] = result
|
|
1968
2290
|
|
|
1969
2291
|
return result
|
|
@@ -1973,11 +2295,7 @@ async def _validate_manifest_async(
|
|
|
1973
2295
|
logger.exception("Unexpected error during manifest validation")
|
|
1974
2296
|
result = (False, error_message, None)
|
|
1975
2297
|
if use_cache:
|
|
1976
|
-
cache_key = (
|
|
1977
|
-
_get_manifest_hash(manifest_data)
|
|
1978
|
-
+ "_"
|
|
1979
|
-
+ get_schema_version(manifest_data)
|
|
1980
|
-
)
|
|
2298
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1981
2299
|
_validation_cache[cache_key] = result
|
|
1982
2300
|
|
|
1983
2301
|
return result
|
|
@@ -2006,27 +2324,21 @@ async def validate_manifests_parallel(
|
|
|
2006
2324
|
"""
|
|
2007
2325
|
|
|
2008
2326
|
async def validate_one(
|
|
2009
|
-
manifest: Dict[str, Any]
|
|
2327
|
+
manifest: Dict[str, Any],
|
|
2010
2328
|
) -> Tuple[bool, Optional[str], Optional[List[str]], Optional[str]]:
|
|
2011
2329
|
slug = manifest.get("slug", "unknown")
|
|
2012
|
-
is_valid, error, paths = await _validate_manifest_async(
|
|
2013
|
-
manifest, use_cache=use_cache
|
|
2014
|
-
)
|
|
2330
|
+
is_valid, error, paths = await _validate_manifest_async(manifest, use_cache=use_cache)
|
|
2015
2331
|
return (is_valid, error, paths, slug)
|
|
2016
2332
|
|
|
2017
2333
|
# Run validations in parallel
|
|
2018
|
-
results = await asyncio.gather(
|
|
2019
|
-
*[validate_one(m) for m in manifests], return_exceptions=True
|
|
2020
|
-
)
|
|
2334
|
+
results = await asyncio.gather(*[validate_one(m) for m in manifests], return_exceptions=True)
|
|
2021
2335
|
|
|
2022
2336
|
# Handle exceptions
|
|
2023
2337
|
validated_results = []
|
|
2024
2338
|
for i, result in enumerate(results):
|
|
2025
2339
|
if isinstance(result, Exception):
|
|
2026
2340
|
slug = manifests[i].get("slug", "unknown")
|
|
2027
|
-
validated_results.append(
|
|
2028
|
-
(False, f"Validation error: {str(result)}", None, slug)
|
|
2029
|
-
)
|
|
2341
|
+
validated_results.append((False, f"Validation error: {str(result)}", None, slug))
|
|
2030
2342
|
else:
|
|
2031
2343
|
validated_results.append(result)
|
|
2032
2344
|
|
|
@@ -2072,9 +2384,7 @@ async def validate_developer_id(
|
|
|
2072
2384
|
f"developer_id '{developer_id}' does not exist or does not have developer role",
|
|
2073
2385
|
)
|
|
2074
2386
|
except (ValueError, TypeError, AttributeError) as e:
|
|
2075
|
-
logger.exception(
|
|
2076
|
-
f"Validation error validating developer_id '{developer_id}'"
|
|
2077
|
-
)
|
|
2387
|
+
logger.exception(f"Validation error validating developer_id '{developer_id}'")
|
|
2078
2388
|
return False, f"Error validating developer_id: {e}"
|
|
2079
2389
|
|
|
2080
2390
|
return True, None
|
|
@@ -2154,15 +2464,11 @@ def validate_manifest(
|
|
|
2154
2464
|
|
|
2155
2465
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
2156
2466
|
future = executor.submit(
|
|
2157
|
-
lambda: asyncio.run(
|
|
2158
|
-
_validate_manifest_async(manifest_data, use_cache)
|
|
2159
|
-
)
|
|
2467
|
+
lambda: asyncio.run(_validate_manifest_async(manifest_data, use_cache))
|
|
2160
2468
|
)
|
|
2161
2469
|
return future.result()
|
|
2162
2470
|
else:
|
|
2163
|
-
return loop.run_until_complete(
|
|
2164
|
-
_validate_manifest_async(manifest_data, use_cache)
|
|
2165
|
-
)
|
|
2471
|
+
return loop.run_until_complete(_validate_manifest_async(manifest_data, use_cache))
|
|
2166
2472
|
except RuntimeError:
|
|
2167
2473
|
# No event loop, create one
|
|
2168
2474
|
return asyncio.run(_validate_manifest_async(manifest_data, use_cache))
|
|
@@ -2186,8 +2492,7 @@ def _validate_regular_index(
|
|
|
2186
2492
|
):
|
|
2187
2493
|
return (
|
|
2188
2494
|
False,
|
|
2189
|
-
f"Regular index '{index_name}' in collection "
|
|
2190
|
-
f"'{collection_name}' has empty 'keys'",
|
|
2495
|
+
f"Regular index '{index_name}' in collection " f"'{collection_name}' has empty 'keys'",
|
|
2191
2496
|
)
|
|
2192
2497
|
|
|
2193
2498
|
# Check for _id index
|
|
@@ -2214,8 +2519,7 @@ def _validate_ttl_index(
|
|
|
2214
2519
|
if "keys" not in index_def:
|
|
2215
2520
|
return (
|
|
2216
2521
|
False,
|
|
2217
|
-
f"TTL index '{index_name}' in collection '{collection_name}' "
|
|
2218
|
-
f"requires 'keys' field",
|
|
2522
|
+
f"TTL index '{index_name}' in collection '{collection_name}' " f"requires 'keys' field",
|
|
2219
2523
|
)
|
|
2220
2524
|
options = index_def.get("options", {})
|
|
2221
2525
|
if "expireAfterSeconds" not in options:
|
|
@@ -2305,9 +2609,7 @@ def _validate_geospatial_index(
|
|
|
2305
2609
|
if isinstance(keys, dict):
|
|
2306
2610
|
has_geo = any(v in ["2dsphere", "2d", "geoHaystack"] for v in keys.values())
|
|
2307
2611
|
elif isinstance(keys, list):
|
|
2308
|
-
has_geo = any(
|
|
2309
|
-
len(k) >= 2 and k[1] in ["2dsphere", "2d", "geoHaystack"] for k in keys
|
|
2310
|
-
)
|
|
2612
|
+
has_geo = any(len(k) >= 2 and k[1] in ["2dsphere", "2d", "geoHaystack"] for k in keys)
|
|
2311
2613
|
if not has_geo:
|
|
2312
2614
|
return (
|
|
2313
2615
|
False,
|
|
@@ -2448,8 +2750,7 @@ def validate_index_definition(
|
|
|
2448
2750
|
if not index_type:
|
|
2449
2751
|
return (
|
|
2450
2752
|
False,
|
|
2451
|
-
f"Index '{index_name}' in collection '{collection_name}' "
|
|
2452
|
-
f"is missing 'type' field",
|
|
2753
|
+
f"Index '{index_name}' in collection '{collection_name}' " f"is missing 'type' field",
|
|
2453
2754
|
)
|
|
2454
2755
|
|
|
2455
2756
|
# Type-specific validation
|
|
@@ -2464,9 +2765,7 @@ def validate_index_definition(
|
|
|
2464
2765
|
elif index_type == "geospatial":
|
|
2465
2766
|
return _validate_geospatial_index(index_def, collection_name, index_name)
|
|
2466
2767
|
elif index_type in ("vectorSearch", "search"):
|
|
2467
|
-
return _validate_vector_search_index(
|
|
2468
|
-
index_def, collection_name, index_name, index_type
|
|
2469
|
-
)
|
|
2768
|
+
return _validate_vector_search_index(index_def, collection_name, index_name, index_type)
|
|
2470
2769
|
elif index_type == "hybrid":
|
|
2471
2770
|
return _validate_hybrid_index(index_def, collection_name, index_name)
|
|
2472
2771
|
else:
|
|
@@ -2478,7 +2777,7 @@ def validate_index_definition(
|
|
|
2478
2777
|
|
|
2479
2778
|
|
|
2480
2779
|
def validate_managed_indexes(
|
|
2481
|
-
managed_indexes: Dict[str, List[Dict[str, Any]]]
|
|
2780
|
+
managed_indexes: Dict[str, List[Dict[str, Any]]],
|
|
2482
2781
|
) -> Tuple[bool, Optional[str]]:
|
|
2483
2782
|
"""
|
|
2484
2783
|
Validate all managed indexes with collection and index context.
|
|
@@ -2516,9 +2815,7 @@ def validate_managed_indexes(
|
|
|
2516
2815
|
)
|
|
2517
2816
|
|
|
2518
2817
|
index_name = index_def.get("name", f"index_{idx}")
|
|
2519
|
-
is_valid, error_msg = validate_index_definition(
|
|
2520
|
-
index_def, collection_name, index_name
|
|
2521
|
-
)
|
|
2818
|
+
is_valid, error_msg = validate_index_definition(index_def, collection_name, index_name)
|
|
2522
2819
|
if not is_valid:
|
|
2523
2820
|
return False, error_msg
|
|
2524
2821
|
|
|
@@ -2600,13 +2897,11 @@ class ManifestValidator:
|
|
|
2600
2897
|
Returns:
|
|
2601
2898
|
Tuple of (is_valid, error_message, error_paths)
|
|
2602
2899
|
"""
|
|
2603
|
-
return await validate_manifest_with_db(
|
|
2604
|
-
manifest, db_validator, use_cache=use_cache
|
|
2605
|
-
)
|
|
2900
|
+
return await validate_manifest_with_db(manifest, db_validator, use_cache=use_cache)
|
|
2606
2901
|
|
|
2607
2902
|
@staticmethod
|
|
2608
2903
|
def validate_managed_indexes(
|
|
2609
|
-
managed_indexes: Dict[str, List[Dict[str, Any]]]
|
|
2904
|
+
managed_indexes: Dict[str, List[Dict[str, Any]]],
|
|
2610
2905
|
) -> Tuple[bool, Optional[str]]:
|
|
2611
2906
|
"""
|
|
2612
2907
|
Validate managed indexes configuration.
|
|
@@ -2720,17 +3015,13 @@ class ManifestParser:
|
|
|
2720
3015
|
if validate:
|
|
2721
3016
|
is_valid, error, paths = ManifestValidator.validate(manifest_data)
|
|
2722
3017
|
if not is_valid:
|
|
2723
|
-
error_path_str = (
|
|
2724
|
-
f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2725
|
-
)
|
|
3018
|
+
error_path_str = f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2726
3019
|
raise ValueError(f"Manifest validation failed: {error}{error_path_str}")
|
|
2727
3020
|
|
|
2728
3021
|
return manifest_data
|
|
2729
3022
|
|
|
2730
3023
|
@staticmethod
|
|
2731
|
-
async def load_from_dict(
|
|
2732
|
-
data: Dict[str, Any], validate: bool = True
|
|
2733
|
-
) -> Dict[str, Any]:
|
|
3024
|
+
async def load_from_dict(data: Dict[str, Any], validate: bool = True) -> Dict[str, Any]:
|
|
2734
3025
|
"""
|
|
2735
3026
|
Load and validate manifest from dictionary.
|
|
2736
3027
|
|
|
@@ -2748,9 +3039,7 @@ class ManifestParser:
|
|
|
2748
3039
|
if validate:
|
|
2749
3040
|
is_valid, error, paths = ManifestValidator.validate(data)
|
|
2750
3041
|
if not is_valid:
|
|
2751
|
-
error_path_str = (
|
|
2752
|
-
f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2753
|
-
)
|
|
3042
|
+
error_path_str = f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2754
3043
|
raise ValueError(f"Manifest validation failed: {error}{error_path_str}")
|
|
2755
3044
|
|
|
2756
3045
|
return data.copy()
|