mdb-engine 0.1.6__py3-none-any.whl → 0.2.0__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 +104 -11
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +648 -11
- mdb_engine/auth/__init__.py +136 -29
- mdb_engine/auth/audit.py +592 -0
- mdb_engine/auth/base.py +252 -0
- mdb_engine/auth/casbin_factory.py +264 -69
- 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 +47 -50
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +53 -80
- mdb_engine/auth/jwt.py +2 -6
- mdb_engine/auth/middleware.py +77 -34
- mdb_engine/auth/oso_factory.py +18 -38
- mdb_engine/auth/provider.py +270 -171
- 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 +1057 -93
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +459 -150
- 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 +140 -17
- 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/dependencies.py +426 -0
- mdb_engine/di/__init__.py +34 -0
- mdb_engine/di/container.py +248 -0
- mdb_engine/di/providers.py +205 -0
- mdb_engine/di/scopes.py +139 -0
- mdb_engine/embeddings/README.md +54 -24
- mdb_engine/embeddings/__init__.py +31 -24
- mdb_engine/embeddings/dependencies.py +37 -154
- 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/repositories/__init__.py +34 -0
- mdb_engine/repositories/base.py +325 -0
- mdb_engine/repositories/mongo.py +233 -0
- mdb_engine/repositories/unit_of_work.py +166 -0
- mdb_engine/routing/README.md +1 -1
- mdb_engine/routing/__init__.py +1 -3
- mdb_engine/routing/websockets.py +25 -60
- mdb_engine-0.2.0.dist-info/METADATA +313 -0
- mdb_engine-0.2.0.dist-info/RECORD +96 -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.2.0.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.2.0.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": {
|
|
@@ -254,24 +300,44 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
254
300
|
"initial_policies": {
|
|
255
301
|
"type": "array",
|
|
256
302
|
"items": {
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
"
|
|
262
|
-
"
|
|
303
|
+
"oneOf": [
|
|
304
|
+
{
|
|
305
|
+
"type": "array",
|
|
306
|
+
"items": {"type": "string"},
|
|
307
|
+
"minItems": 3,
|
|
308
|
+
"maxItems": 3,
|
|
309
|
+
"description": (
|
|
310
|
+
"Casbin policy as array: "
|
|
311
|
+
'["role", "resource", "action"]'
|
|
312
|
+
),
|
|
263
313
|
},
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
314
|
+
{
|
|
315
|
+
"type": "object",
|
|
316
|
+
"properties": {
|
|
317
|
+
"role": {"type": "string"},
|
|
318
|
+
"resource": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"default": "documents",
|
|
321
|
+
},
|
|
322
|
+
"action": {"type": "string"},
|
|
323
|
+
},
|
|
324
|
+
"required": ["role", "action"],
|
|
325
|
+
"additionalProperties": False,
|
|
326
|
+
"description": (
|
|
327
|
+
"OSO policy as object: "
|
|
328
|
+
'{"role": "admin", "resource": "documents", '
|
|
329
|
+
'"action": "read"}'
|
|
330
|
+
),
|
|
331
|
+
},
|
|
332
|
+
],
|
|
268
333
|
},
|
|
269
334
|
"description": (
|
|
270
|
-
"Initial permission policies to set up "
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
|
|
274
|
-
'"
|
|
335
|
+
"Initial permission policies to set up on startup. "
|
|
336
|
+
"For Casbin provider: use arrays like "
|
|
337
|
+
'["admin", "clicks", "read"]. '
|
|
338
|
+
"For OSO Cloud provider: use objects like "
|
|
339
|
+
'{"role": "admin", "resource": "documents", '
|
|
340
|
+
'"action": "read"}.'
|
|
275
341
|
),
|
|
276
342
|
},
|
|
277
343
|
},
|
|
@@ -484,8 +550,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
484
550
|
"type": "string",
|
|
485
551
|
"default": "user",
|
|
486
552
|
"description": (
|
|
487
|
-
"Role for demo user in app "
|
|
488
|
-
"(default: 'user')"
|
|
553
|
+
"Role for demo user in app " "(default: 'user')"
|
|
489
554
|
),
|
|
490
555
|
},
|
|
491
556
|
"auto_create": {
|
|
@@ -567,6 +632,292 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
567
632
|
"independent of platform authentication."
|
|
568
633
|
),
|
|
569
634
|
},
|
|
635
|
+
"rate_limits": {
|
|
636
|
+
"type": "object",
|
|
637
|
+
"additionalProperties": {
|
|
638
|
+
"type": "object",
|
|
639
|
+
"properties": {
|
|
640
|
+
"max_attempts": {
|
|
641
|
+
"type": "integer",
|
|
642
|
+
"minimum": 1,
|
|
643
|
+
"default": 5,
|
|
644
|
+
"description": (
|
|
645
|
+
"Maximum attempts allowed in the time window " "(default: 5)."
|
|
646
|
+
),
|
|
647
|
+
},
|
|
648
|
+
"window_seconds": {
|
|
649
|
+
"type": "integer",
|
|
650
|
+
"minimum": 1,
|
|
651
|
+
"default": 300,
|
|
652
|
+
"description": (
|
|
653
|
+
"Time window in seconds for rate limiting "
|
|
654
|
+
"(default: 300 = 5 minutes)."
|
|
655
|
+
),
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
"additionalProperties": False,
|
|
659
|
+
},
|
|
660
|
+
"description": (
|
|
661
|
+
"Rate limiting configuration for auth endpoints. "
|
|
662
|
+
"Keys are endpoint paths (e.g., '/login', '/register'). "
|
|
663
|
+
"Example: {'/login': {'max_attempts': 5, 'window_seconds': 300}}"
|
|
664
|
+
),
|
|
665
|
+
},
|
|
666
|
+
"audit": {
|
|
667
|
+
"type": "object",
|
|
668
|
+
"properties": {
|
|
669
|
+
"enabled": {
|
|
670
|
+
"type": "boolean",
|
|
671
|
+
"default": True,
|
|
672
|
+
"description": (
|
|
673
|
+
"Enable audit logging for authentication events "
|
|
674
|
+
"(default: true for shared auth mode)."
|
|
675
|
+
),
|
|
676
|
+
},
|
|
677
|
+
"retention_days": {
|
|
678
|
+
"type": "integer",
|
|
679
|
+
"minimum": 1,
|
|
680
|
+
"default": 90,
|
|
681
|
+
"description": (
|
|
682
|
+
"Number of days to retain audit logs " "(default: 90 days)."
|
|
683
|
+
),
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
"additionalProperties": False,
|
|
687
|
+
"description": (
|
|
688
|
+
"Audit logging configuration for authentication events. "
|
|
689
|
+
"Logs are stored in MongoDB with automatic TTL cleanup."
|
|
690
|
+
),
|
|
691
|
+
},
|
|
692
|
+
"csrf_protection": {
|
|
693
|
+
"oneOf": [
|
|
694
|
+
{"type": "boolean"},
|
|
695
|
+
{
|
|
696
|
+
"type": "object",
|
|
697
|
+
"properties": {
|
|
698
|
+
"enabled": {
|
|
699
|
+
"type": "boolean",
|
|
700
|
+
"default": True,
|
|
701
|
+
"description": (
|
|
702
|
+
"Enable CSRF protection "
|
|
703
|
+
"(default: true for shared auth mode)."
|
|
704
|
+
),
|
|
705
|
+
},
|
|
706
|
+
"exempt_routes": {
|
|
707
|
+
"type": "array",
|
|
708
|
+
"items": {"type": "string"},
|
|
709
|
+
"description": (
|
|
710
|
+
"Routes exempt from CSRF validation. "
|
|
711
|
+
"Supports wildcards (e.g., '/api/*'). "
|
|
712
|
+
"Defaults to public_routes if not specified."
|
|
713
|
+
),
|
|
714
|
+
},
|
|
715
|
+
"rotate_tokens": {
|
|
716
|
+
"type": "boolean",
|
|
717
|
+
"default": False,
|
|
718
|
+
"description": (
|
|
719
|
+
"Rotate CSRF token on each request "
|
|
720
|
+
"(more secure, less convenient). Default: false."
|
|
721
|
+
),
|
|
722
|
+
},
|
|
723
|
+
"token_ttl": {
|
|
724
|
+
"type": "integer",
|
|
725
|
+
"minimum": 60,
|
|
726
|
+
"default": 3600,
|
|
727
|
+
"description": (
|
|
728
|
+
"CSRF token TTL in seconds " "(default: 3600 = 1 hour)."
|
|
729
|
+
),
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
"additionalProperties": False,
|
|
733
|
+
},
|
|
734
|
+
],
|
|
735
|
+
"default": True,
|
|
736
|
+
"description": (
|
|
737
|
+
"CSRF protection configuration. Auto-enabled for shared "
|
|
738
|
+
"auth mode. Set to false to disable, or provide object "
|
|
739
|
+
"for detailed configuration. Uses double-submit cookie "
|
|
740
|
+
"pattern with SameSite=Lax cookies."
|
|
741
|
+
),
|
|
742
|
+
},
|
|
743
|
+
"security": {
|
|
744
|
+
"type": "object",
|
|
745
|
+
"properties": {
|
|
746
|
+
"hsts": {
|
|
747
|
+
"type": "object",
|
|
748
|
+
"properties": {
|
|
749
|
+
"enabled": {
|
|
750
|
+
"type": "boolean",
|
|
751
|
+
"default": True,
|
|
752
|
+
"description": (
|
|
753
|
+
"Enable HSTS header in production " "(default: true)."
|
|
754
|
+
),
|
|
755
|
+
},
|
|
756
|
+
"max_age": {
|
|
757
|
+
"type": "integer",
|
|
758
|
+
"minimum": 0,
|
|
759
|
+
"default": 31536000,
|
|
760
|
+
"description": (
|
|
761
|
+
"HSTS max-age in seconds " "(default: 31536000 = 1 year)."
|
|
762
|
+
),
|
|
763
|
+
},
|
|
764
|
+
"include_subdomains": {
|
|
765
|
+
"type": "boolean",
|
|
766
|
+
"default": True,
|
|
767
|
+
"description": (
|
|
768
|
+
"Include subdomains in HSTS policy " "(default: true)."
|
|
769
|
+
),
|
|
770
|
+
},
|
|
771
|
+
"preload": {
|
|
772
|
+
"type": "boolean",
|
|
773
|
+
"default": False,
|
|
774
|
+
"description": (
|
|
775
|
+
"Add preload directive for HSTS preload "
|
|
776
|
+
"list submission (default: false). Only "
|
|
777
|
+
"enable if you're ready for permanent HTTPS."
|
|
778
|
+
),
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
"additionalProperties": False,
|
|
782
|
+
"description": (
|
|
783
|
+
"HTTP Strict Transport Security configuration. "
|
|
784
|
+
"Forces HTTPS connections in production."
|
|
785
|
+
),
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
"additionalProperties": False,
|
|
789
|
+
"description": (
|
|
790
|
+
"Security settings including HSTS, headers, and other " "security controls."
|
|
791
|
+
),
|
|
792
|
+
},
|
|
793
|
+
"jwt": {
|
|
794
|
+
"type": "object",
|
|
795
|
+
"properties": {
|
|
796
|
+
"algorithm": {
|
|
797
|
+
"type": "string",
|
|
798
|
+
"enum": ["HS256", "RS256", "ES256"],
|
|
799
|
+
"default": "HS256",
|
|
800
|
+
"description": (
|
|
801
|
+
"JWT signing algorithm. HS256 (HMAC, default) "
|
|
802
|
+
"uses symmetric secret. RS256 (RSA) and ES256 "
|
|
803
|
+
"(ECDSA) use asymmetric key pairs for better "
|
|
804
|
+
"security in distributed systems."
|
|
805
|
+
),
|
|
806
|
+
},
|
|
807
|
+
"token_expiry_hours": {
|
|
808
|
+
"type": "integer",
|
|
809
|
+
"minimum": 1,
|
|
810
|
+
"default": 24,
|
|
811
|
+
"description": ("JWT token expiry in hours (default: 24)."),
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
"additionalProperties": False,
|
|
815
|
+
"description": (
|
|
816
|
+
"JWT configuration for shared auth mode. "
|
|
817
|
+
"Controls algorithm and token lifetime."
|
|
818
|
+
),
|
|
819
|
+
},
|
|
820
|
+
"password_policy": {
|
|
821
|
+
"type": "object",
|
|
822
|
+
"properties": {
|
|
823
|
+
"min_length": {
|
|
824
|
+
"type": "integer",
|
|
825
|
+
"minimum": 6,
|
|
826
|
+
"default": 12,
|
|
827
|
+
"description": ("Minimum password length (default: 12)."),
|
|
828
|
+
},
|
|
829
|
+
"min_entropy_bits": {
|
|
830
|
+
"type": "integer",
|
|
831
|
+
"minimum": 0,
|
|
832
|
+
"default": 50,
|
|
833
|
+
"description": (
|
|
834
|
+
"Minimum password entropy in bits "
|
|
835
|
+
"(default: 50). Set to 0 to disable."
|
|
836
|
+
),
|
|
837
|
+
},
|
|
838
|
+
"require_uppercase": {
|
|
839
|
+
"type": "boolean",
|
|
840
|
+
"default": True,
|
|
841
|
+
"description": (
|
|
842
|
+
"Require at least one uppercase letter " "(default: true)."
|
|
843
|
+
),
|
|
844
|
+
},
|
|
845
|
+
"require_lowercase": {
|
|
846
|
+
"type": "boolean",
|
|
847
|
+
"default": True,
|
|
848
|
+
"description": (
|
|
849
|
+
"Require at least one lowercase letter " "(default: true)."
|
|
850
|
+
),
|
|
851
|
+
},
|
|
852
|
+
"require_numbers": {
|
|
853
|
+
"type": "boolean",
|
|
854
|
+
"default": True,
|
|
855
|
+
"description": ("Require at least one number " "(default: true)."),
|
|
856
|
+
},
|
|
857
|
+
"require_special": {
|
|
858
|
+
"type": "boolean",
|
|
859
|
+
"default": False,
|
|
860
|
+
"description": (
|
|
861
|
+
"Require at least one special character " "(default: false)."
|
|
862
|
+
),
|
|
863
|
+
},
|
|
864
|
+
"check_common_passwords": {
|
|
865
|
+
"type": "boolean",
|
|
866
|
+
"default": True,
|
|
867
|
+
"description": (
|
|
868
|
+
"Check against common password list " "(default: true)."
|
|
869
|
+
),
|
|
870
|
+
},
|
|
871
|
+
"check_breaches": {
|
|
872
|
+
"type": "boolean",
|
|
873
|
+
"default": False,
|
|
874
|
+
"description": (
|
|
875
|
+
"Check against HaveIBeenPwned breach database "
|
|
876
|
+
"(default: false). Requires network access."
|
|
877
|
+
),
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
"additionalProperties": False,
|
|
881
|
+
"description": (
|
|
882
|
+
"Password policy configuration for shared auth mode. "
|
|
883
|
+
"Enforces password strength requirements."
|
|
884
|
+
),
|
|
885
|
+
},
|
|
886
|
+
"session_binding": {
|
|
887
|
+
"type": "object",
|
|
888
|
+
"properties": {
|
|
889
|
+
"bind_ip": {
|
|
890
|
+
"type": "boolean",
|
|
891
|
+
"default": False,
|
|
892
|
+
"description": (
|
|
893
|
+
"Bind sessions to IP address. Strict mode: "
|
|
894
|
+
"reject if IP changes (default: false)."
|
|
895
|
+
),
|
|
896
|
+
},
|
|
897
|
+
"bind_fingerprint": {
|
|
898
|
+
"type": "boolean",
|
|
899
|
+
"default": True,
|
|
900
|
+
"description": (
|
|
901
|
+
"Bind sessions to device fingerprint. Soft mode: "
|
|
902
|
+
"log warning if fingerprint changes (default: true)."
|
|
903
|
+
),
|
|
904
|
+
},
|
|
905
|
+
"allow_ip_change_with_reauth": {
|
|
906
|
+
"type": "boolean",
|
|
907
|
+
"default": True,
|
|
908
|
+
"description": (
|
|
909
|
+
"Allow IP change if user re-authenticates "
|
|
910
|
+
"(default: true). Only applies when bind_ip=true."
|
|
911
|
+
),
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
"additionalProperties": False,
|
|
915
|
+
"description": (
|
|
916
|
+
"Session binding configuration. Ties sessions to client "
|
|
917
|
+
"characteristics for additional security against session "
|
|
918
|
+
"hijacking."
|
|
919
|
+
),
|
|
920
|
+
},
|
|
570
921
|
},
|
|
571
922
|
"additionalProperties": False,
|
|
572
923
|
"description": (
|
|
@@ -590,17 +941,13 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
590
941
|
"type": "integer",
|
|
591
942
|
"minimum": 60,
|
|
592
943
|
"default": 900,
|
|
593
|
-
"description": (
|
|
594
|
-
"Access token TTL in seconds " "(default: 900 = 15 minutes)."
|
|
595
|
-
),
|
|
944
|
+
"description": ("Access token TTL in seconds " "(default: 900 = 15 minutes)."),
|
|
596
945
|
},
|
|
597
946
|
"refresh_token_ttl": {
|
|
598
947
|
"type": "integer",
|
|
599
948
|
"minimum": 3600,
|
|
600
949
|
"default": 604800,
|
|
601
|
-
"description": (
|
|
602
|
-
"Refresh token TTL in seconds " "(default: 604800 = 7 days)."
|
|
603
|
-
),
|
|
950
|
+
"description": ("Refresh token TTL in seconds " "(default: 604800 = 7 days)."),
|
|
604
951
|
},
|
|
605
952
|
"token_rotation": {
|
|
606
953
|
"type": "boolean",
|
|
@@ -615,8 +962,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
615
962
|
"minimum": 1,
|
|
616
963
|
"default": 10,
|
|
617
964
|
"description": (
|
|
618
|
-
"Maximum number of concurrent sessions per user "
|
|
619
|
-
"(default: 10)."
|
|
965
|
+
"Maximum number of concurrent sessions per user " "(default: 10)."
|
|
620
966
|
),
|
|
621
967
|
},
|
|
622
968
|
"session_inactivity_timeout": {
|
|
@@ -635,8 +981,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
635
981
|
"type": "boolean",
|
|
636
982
|
"default": False,
|
|
637
983
|
"description": (
|
|
638
|
-
"Require HTTPS in production "
|
|
639
|
-
"(default: false, auto-detected)."
|
|
984
|
+
"Require HTTPS in production " "(default: false, auto-detected)."
|
|
640
985
|
),
|
|
641
986
|
},
|
|
642
987
|
"cookie_secure": {
|
|
@@ -718,9 +1063,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
718
1063
|
},
|
|
719
1064
|
},
|
|
720
1065
|
"additionalProperties": False,
|
|
721
|
-
"description": (
|
|
722
|
-
"Rate limiting configuration per endpoint type."
|
|
723
|
-
),
|
|
1066
|
+
"description": ("Rate limiting configuration per endpoint type."),
|
|
724
1067
|
},
|
|
725
1068
|
"password_policy": {
|
|
726
1069
|
"type": "object",
|
|
@@ -747,9 +1090,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
747
1090
|
"require_lowercase": {
|
|
748
1091
|
"type": "boolean",
|
|
749
1092
|
"default": True,
|
|
750
|
-
"description": (
|
|
751
|
-
"Require lowercase letters " "(default: true)"
|
|
752
|
-
),
|
|
1093
|
+
"description": ("Require lowercase letters " "(default: true)"),
|
|
753
1094
|
},
|
|
754
1095
|
"require_numbers": {
|
|
755
1096
|
"type": "boolean",
|
|
@@ -774,24 +1115,21 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
774
1115
|
"type": "boolean",
|
|
775
1116
|
"default": True,
|
|
776
1117
|
"description": (
|
|
777
|
-
"Enable session fingerprinting "
|
|
778
|
-
"(default: true)"
|
|
1118
|
+
"Enable session fingerprinting " "(default: true)"
|
|
779
1119
|
),
|
|
780
1120
|
},
|
|
781
1121
|
"validate_on_login": {
|
|
782
1122
|
"type": "boolean",
|
|
783
1123
|
"default": True,
|
|
784
1124
|
"description": (
|
|
785
|
-
"Validate fingerprint on login "
|
|
786
|
-
"(default: true)"
|
|
1125
|
+
"Validate fingerprint on login " "(default: true)"
|
|
787
1126
|
),
|
|
788
1127
|
},
|
|
789
1128
|
"validate_on_refresh": {
|
|
790
1129
|
"type": "boolean",
|
|
791
1130
|
"default": True,
|
|
792
1131
|
"description": (
|
|
793
|
-
"Validate fingerprint on token refresh "
|
|
794
|
-
"(default: true)"
|
|
1132
|
+
"Validate fingerprint on token refresh " "(default: true)"
|
|
795
1133
|
),
|
|
796
1134
|
},
|
|
797
1135
|
"validate_on_request": {
|
|
@@ -837,8 +1175,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
837
1175
|
"minimum": 1,
|
|
838
1176
|
"default": 900,
|
|
839
1177
|
"description": (
|
|
840
|
-
"Lockout duration in seconds "
|
|
841
|
-
"(default: 900 = 15 minutes)"
|
|
1178
|
+
"Lockout duration in seconds " "(default: 900 = 15 minutes)"
|
|
842
1179
|
),
|
|
843
1180
|
},
|
|
844
1181
|
"reset_on_success": {
|
|
@@ -860,8 +1197,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
860
1197
|
"type": "boolean",
|
|
861
1198
|
"default": False,
|
|
862
1199
|
"description": (
|
|
863
|
-
"Enable IP address validation "
|
|
864
|
-
"(default: false)"
|
|
1200
|
+
"Enable IP address validation " "(default: false)"
|
|
865
1201
|
),
|
|
866
1202
|
},
|
|
867
1203
|
"strict": {
|
|
@@ -876,8 +1212,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
876
1212
|
"type": "boolean",
|
|
877
1213
|
"default": True,
|
|
878
1214
|
"description": (
|
|
879
|
-
"Allow IP address changes during session "
|
|
880
|
-
"(default: true)"
|
|
1215
|
+
"Allow IP address changes during session " "(default: true)"
|
|
881
1216
|
),
|
|
882
1217
|
},
|
|
883
1218
|
},
|
|
@@ -897,9 +1232,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
897
1232
|
"bind_to_device": {
|
|
898
1233
|
"type": "boolean",
|
|
899
1234
|
"default": True,
|
|
900
|
-
"description": (
|
|
901
|
-
"Bind tokens to device ID " "(default: true)"
|
|
902
|
-
),
|
|
1235
|
+
"description": ("Bind tokens to device ID " "(default: true)"),
|
|
903
1236
|
},
|
|
904
1237
|
},
|
|
905
1238
|
"additionalProperties": False,
|
|
@@ -913,8 +1246,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
913
1246
|
"type": "boolean",
|
|
914
1247
|
"default": True,
|
|
915
1248
|
"description": (
|
|
916
|
-
"Automatically set up token management on app startup "
|
|
917
|
-
"(default: true)."
|
|
1249
|
+
"Automatically set up token management on app startup " "(default: true)."
|
|
918
1250
|
),
|
|
919
1251
|
},
|
|
920
1252
|
},
|
|
@@ -946,9 +1278,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
946
1278
|
},
|
|
947
1279
|
"collection_settings": {
|
|
948
1280
|
"type": "object",
|
|
949
|
-
"patternProperties": {
|
|
950
|
-
"^[a-zA-Z0-9_]+$": {"$ref": "#/definitions/collectionSettings"}
|
|
951
|
-
},
|
|
1281
|
+
"patternProperties": {"^[a-zA-Z0-9_]+$": {"$ref": "#/definitions/collectionSettings"}},
|
|
952
1282
|
"description": "Collection name -> collection settings",
|
|
953
1283
|
},
|
|
954
1284
|
"websockets": {
|
|
@@ -996,8 +1326,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
996
1326
|
"description": {
|
|
997
1327
|
"type": "string",
|
|
998
1328
|
"description": (
|
|
999
|
-
"Description of what this WebSocket endpoint "
|
|
1000
|
-
"is used for"
|
|
1329
|
+
"Description of what this WebSocket endpoint " "is used for"
|
|
1001
1330
|
),
|
|
1002
1331
|
},
|
|
1003
1332
|
"ping_interval": {
|
|
@@ -1210,8 +1539,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1210
1539
|
"type": "boolean",
|
|
1211
1540
|
"default": False,
|
|
1212
1541
|
"description": (
|
|
1213
|
-
"Allow credentials (cookies, authorization headers) "
|
|
1214
|
-
"in CORS requests"
|
|
1542
|
+
"Allow credentials (cookies, authorization headers) " "in CORS requests"
|
|
1215
1543
|
),
|
|
1216
1544
|
},
|
|
1217
1545
|
"allow_methods": {
|
|
@@ -1256,6 +1584,40 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1256
1584
|
"additionalProperties": False,
|
|
1257
1585
|
"description": "CORS (Cross-Origin Resource Sharing) configuration for web apps",
|
|
1258
1586
|
},
|
|
1587
|
+
"data_access": {
|
|
1588
|
+
"type": "object",
|
|
1589
|
+
"properties": {
|
|
1590
|
+
"read_scopes": {
|
|
1591
|
+
"type": "array",
|
|
1592
|
+
"items": {"type": "string"},
|
|
1593
|
+
"description": (
|
|
1594
|
+
"List of app slugs this app can read from. "
|
|
1595
|
+
"Defaults to [app_slug] if not specified."
|
|
1596
|
+
),
|
|
1597
|
+
},
|
|
1598
|
+
"write_scope": {
|
|
1599
|
+
"type": "string",
|
|
1600
|
+
"description": (
|
|
1601
|
+
"App slug this app writes to. " "Defaults to app_slug if not specified."
|
|
1602
|
+
),
|
|
1603
|
+
},
|
|
1604
|
+
"cross_app_policy": {
|
|
1605
|
+
"type": "string",
|
|
1606
|
+
"enum": ["explicit", "deny_all"],
|
|
1607
|
+
"default": "explicit",
|
|
1608
|
+
"description": (
|
|
1609
|
+
"Policy for cross-app access. 'explicit' allows access "
|
|
1610
|
+
"to apps listed in read_scopes. 'deny_all' blocks all "
|
|
1611
|
+
"cross-app access regardless of read_scopes."
|
|
1612
|
+
),
|
|
1613
|
+
},
|
|
1614
|
+
},
|
|
1615
|
+
"additionalProperties": False,
|
|
1616
|
+
"description": (
|
|
1617
|
+
"Data access configuration defining which apps this app can "
|
|
1618
|
+
"read from and write to. Used for cross-app data access control."
|
|
1619
|
+
),
|
|
1620
|
+
},
|
|
1259
1621
|
"observability": {
|
|
1260
1622
|
"type": "object",
|
|
1261
1623
|
"properties": {
|
|
@@ -1295,8 +1657,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1295
1657
|
"type": "boolean",
|
|
1296
1658
|
"default": True,
|
|
1297
1659
|
"description": (
|
|
1298
|
-
"Collect operation-level metrics "
|
|
1299
|
-
"(duration, errors, etc.)"
|
|
1660
|
+
"Collect operation-level metrics " "(duration, errors, etc.)"
|
|
1300
1661
|
),
|
|
1301
1662
|
},
|
|
1302
1663
|
"collect_performance_metrics": {
|
|
@@ -1456,8 +1817,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1456
1817
|
"definition": {
|
|
1457
1818
|
"type": "object",
|
|
1458
1819
|
"description": (
|
|
1459
|
-
"Index definition (required for vectorSearch and "
|
|
1460
|
-
"search indexes)"
|
|
1820
|
+
"Index definition (required for vectorSearch and " "search indexes)"
|
|
1461
1821
|
),
|
|
1462
1822
|
},
|
|
1463
1823
|
"hybrid": {
|
|
@@ -1470,8 +1830,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1470
1830
|
"type": "string",
|
|
1471
1831
|
"pattern": "^[a-zA-Z0-9_]+$",
|
|
1472
1832
|
"description": (
|
|
1473
|
-
"Name for the vector index "
|
|
1474
|
-
"(defaults to '{name}_vector')"
|
|
1833
|
+
"Name for the vector index " "(defaults to '{name}_vector')"
|
|
1475
1834
|
),
|
|
1476
1835
|
},
|
|
1477
1836
|
"definition": {
|
|
@@ -1493,8 +1852,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1493
1852
|
"type": "string",
|
|
1494
1853
|
"pattern": "^[a-zA-Z0-9_]+$",
|
|
1495
1854
|
"description": (
|
|
1496
|
-
"Name for the text index "
|
|
1497
|
-
"(defaults to '{name}_text')"
|
|
1855
|
+
"Name for the text index " "(defaults to '{name}_text')"
|
|
1498
1856
|
),
|
|
1499
1857
|
},
|
|
1500
1858
|
"definition": {
|
|
@@ -1575,9 +1933,7 @@ MANIFEST_SCHEMA_V2 = {
|
|
|
1575
1933
|
"then": {"required": ["keys", "options"]},
|
|
1576
1934
|
"else": {
|
|
1577
1935
|
"properties": {
|
|
1578
|
-
"options": {
|
|
1579
|
-
"not": {"required": ["partialFilterExpression"]}
|
|
1580
|
-
}
|
|
1936
|
+
"options": {"not": {"required": ["partialFilterExpression"]}}
|
|
1581
1937
|
}
|
|
1582
1938
|
},
|
|
1583
1939
|
},
|
|
@@ -1803,9 +2159,7 @@ def migrate_manifest(
|
|
|
1803
2159
|
|
|
1804
2160
|
# No data transformation needed - V2.0 is backward compatible
|
|
1805
2161
|
# New fields (auth, etc.) are optional
|
|
1806
|
-
logger.debug(
|
|
1807
|
-
f"Migrated manifest from 1.0 to 2.0: {migrated.get('slug', 'unknown')}"
|
|
1808
|
-
)
|
|
2162
|
+
logger.debug(f"Migrated manifest from 1.0 to 2.0: {migrated.get('slug', 'unknown')}")
|
|
1809
2163
|
|
|
1810
2164
|
# Future: Add more migration paths as needed
|
|
1811
2165
|
# Example: 2.0 -> 3.0, etc.
|
|
@@ -1841,8 +2195,7 @@ def get_schema_for_version(version: str) -> Dict[str, Any]:
|
|
|
1841
2195
|
|
|
1842
2196
|
# Fallback to current
|
|
1843
2197
|
logger.warning(
|
|
1844
|
-
f"Schema version {version} not found, using current version "
|
|
1845
|
-
f"{CURRENT_SCHEMA_VERSION}"
|
|
2198
|
+
f"Schema version {version} not found, using current version " f"{CURRENT_SCHEMA_VERSION}"
|
|
1846
2199
|
)
|
|
1847
2200
|
return SCHEMA_REGISTRY[CURRENT_SCHEMA_VERSION]
|
|
1848
2201
|
|
|
@@ -1873,12 +2226,11 @@ async def _validate_manifest_async(
|
|
|
1873
2226
|
Use validate_manifest_with_db() for database validation.
|
|
1874
2227
|
"""
|
|
1875
2228
|
# Check cache first
|
|
2229
|
+
cache_key = None
|
|
1876
2230
|
if use_cache:
|
|
1877
|
-
cache_key = (
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
if cache_key in _validation_cache:
|
|
1881
|
-
return _validation_cache[cache_key]
|
|
2231
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
2232
|
+
if cache_key in _validation_cache:
|
|
2233
|
+
return _validation_cache[cache_key]
|
|
1882
2234
|
|
|
1883
2235
|
try:
|
|
1884
2236
|
# Get schema version
|
|
@@ -1935,11 +2287,7 @@ async def _validate_manifest_async(
|
|
|
1935
2287
|
error_message = f"Invalid schema definition: {e.message}"
|
|
1936
2288
|
result = (False, error_message, ["schema"])
|
|
1937
2289
|
if use_cache:
|
|
1938
|
-
cache_key = (
|
|
1939
|
-
_get_manifest_hash(manifest_data)
|
|
1940
|
-
+ "_"
|
|
1941
|
-
+ get_schema_version(manifest_data)
|
|
1942
|
-
)
|
|
2290
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1943
2291
|
_validation_cache[cache_key] = result
|
|
1944
2292
|
|
|
1945
2293
|
return result
|
|
@@ -1949,9 +2297,7 @@ async def _validate_manifest_async(
|
|
|
1949
2297
|
error_paths = []
|
|
1950
2298
|
error_messages = []
|
|
1951
2299
|
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
|
-
]
|
|
2300
|
+
error_paths = [f".{'.'.join(str(p) for p in error.path)}" for error in e.context or [e]]
|
|
1955
2301
|
error_messages = [error.message for error in e.context or [e]]
|
|
1956
2302
|
else:
|
|
1957
2303
|
error_messages = [str(e)]
|
|
@@ -1959,11 +2305,7 @@ async def _validate_manifest_async(
|
|
|
1959
2305
|
error_message = "; ".join(error_messages) if error_messages else str(e)
|
|
1960
2306
|
result = (False, error_message, error_paths if error_paths else None)
|
|
1961
2307
|
if use_cache:
|
|
1962
|
-
cache_key = (
|
|
1963
|
-
_get_manifest_hash(manifest_data)
|
|
1964
|
-
+ "_"
|
|
1965
|
-
+ get_schema_version(manifest_data)
|
|
1966
|
-
)
|
|
2308
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1967
2309
|
_validation_cache[cache_key] = result
|
|
1968
2310
|
|
|
1969
2311
|
return result
|
|
@@ -1973,11 +2315,7 @@ async def _validate_manifest_async(
|
|
|
1973
2315
|
logger.exception("Unexpected error during manifest validation")
|
|
1974
2316
|
result = (False, error_message, None)
|
|
1975
2317
|
if use_cache:
|
|
1976
|
-
cache_key = (
|
|
1977
|
-
_get_manifest_hash(manifest_data)
|
|
1978
|
-
+ "_"
|
|
1979
|
-
+ get_schema_version(manifest_data)
|
|
1980
|
-
)
|
|
2318
|
+
cache_key = _get_manifest_hash(manifest_data) + "_" + get_schema_version(manifest_data)
|
|
1981
2319
|
_validation_cache[cache_key] = result
|
|
1982
2320
|
|
|
1983
2321
|
return result
|
|
@@ -2006,27 +2344,21 @@ async def validate_manifests_parallel(
|
|
|
2006
2344
|
"""
|
|
2007
2345
|
|
|
2008
2346
|
async def validate_one(
|
|
2009
|
-
manifest: Dict[str, Any]
|
|
2347
|
+
manifest: Dict[str, Any],
|
|
2010
2348
|
) -> Tuple[bool, Optional[str], Optional[List[str]], Optional[str]]:
|
|
2011
2349
|
slug = manifest.get("slug", "unknown")
|
|
2012
|
-
is_valid, error, paths = await _validate_manifest_async(
|
|
2013
|
-
manifest, use_cache=use_cache
|
|
2014
|
-
)
|
|
2350
|
+
is_valid, error, paths = await _validate_manifest_async(manifest, use_cache=use_cache)
|
|
2015
2351
|
return (is_valid, error, paths, slug)
|
|
2016
2352
|
|
|
2017
2353
|
# Run validations in parallel
|
|
2018
|
-
results = await asyncio.gather(
|
|
2019
|
-
*[validate_one(m) for m in manifests], return_exceptions=True
|
|
2020
|
-
)
|
|
2354
|
+
results = await asyncio.gather(*[validate_one(m) for m in manifests], return_exceptions=True)
|
|
2021
2355
|
|
|
2022
2356
|
# Handle exceptions
|
|
2023
2357
|
validated_results = []
|
|
2024
2358
|
for i, result in enumerate(results):
|
|
2025
2359
|
if isinstance(result, Exception):
|
|
2026
2360
|
slug = manifests[i].get("slug", "unknown")
|
|
2027
|
-
validated_results.append(
|
|
2028
|
-
(False, f"Validation error: {str(result)}", None, slug)
|
|
2029
|
-
)
|
|
2361
|
+
validated_results.append((False, f"Validation error: {str(result)}", None, slug))
|
|
2030
2362
|
else:
|
|
2031
2363
|
validated_results.append(result)
|
|
2032
2364
|
|
|
@@ -2072,9 +2404,7 @@ async def validate_developer_id(
|
|
|
2072
2404
|
f"developer_id '{developer_id}' does not exist or does not have developer role",
|
|
2073
2405
|
)
|
|
2074
2406
|
except (ValueError, TypeError, AttributeError) as e:
|
|
2075
|
-
logger.exception(
|
|
2076
|
-
f"Validation error validating developer_id '{developer_id}'"
|
|
2077
|
-
)
|
|
2407
|
+
logger.exception(f"Validation error validating developer_id '{developer_id}'")
|
|
2078
2408
|
return False, f"Error validating developer_id: {e}"
|
|
2079
2409
|
|
|
2080
2410
|
return True, None
|
|
@@ -2154,15 +2484,11 @@ def validate_manifest(
|
|
|
2154
2484
|
|
|
2155
2485
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
2156
2486
|
future = executor.submit(
|
|
2157
|
-
lambda: asyncio.run(
|
|
2158
|
-
_validate_manifest_async(manifest_data, use_cache)
|
|
2159
|
-
)
|
|
2487
|
+
lambda: asyncio.run(_validate_manifest_async(manifest_data, use_cache))
|
|
2160
2488
|
)
|
|
2161
2489
|
return future.result()
|
|
2162
2490
|
else:
|
|
2163
|
-
return loop.run_until_complete(
|
|
2164
|
-
_validate_manifest_async(manifest_data, use_cache)
|
|
2165
|
-
)
|
|
2491
|
+
return loop.run_until_complete(_validate_manifest_async(manifest_data, use_cache))
|
|
2166
2492
|
except RuntimeError:
|
|
2167
2493
|
# No event loop, create one
|
|
2168
2494
|
return asyncio.run(_validate_manifest_async(manifest_data, use_cache))
|
|
@@ -2186,8 +2512,7 @@ def _validate_regular_index(
|
|
|
2186
2512
|
):
|
|
2187
2513
|
return (
|
|
2188
2514
|
False,
|
|
2189
|
-
f"Regular index '{index_name}' in collection "
|
|
2190
|
-
f"'{collection_name}' has empty 'keys'",
|
|
2515
|
+
f"Regular index '{index_name}' in collection " f"'{collection_name}' has empty 'keys'",
|
|
2191
2516
|
)
|
|
2192
2517
|
|
|
2193
2518
|
# Check for _id index
|
|
@@ -2214,8 +2539,7 @@ def _validate_ttl_index(
|
|
|
2214
2539
|
if "keys" not in index_def:
|
|
2215
2540
|
return (
|
|
2216
2541
|
False,
|
|
2217
|
-
f"TTL index '{index_name}' in collection '{collection_name}' "
|
|
2218
|
-
f"requires 'keys' field",
|
|
2542
|
+
f"TTL index '{index_name}' in collection '{collection_name}' " f"requires 'keys' field",
|
|
2219
2543
|
)
|
|
2220
2544
|
options = index_def.get("options", {})
|
|
2221
2545
|
if "expireAfterSeconds" not in options:
|
|
@@ -2305,9 +2629,7 @@ def _validate_geospatial_index(
|
|
|
2305
2629
|
if isinstance(keys, dict):
|
|
2306
2630
|
has_geo = any(v in ["2dsphere", "2d", "geoHaystack"] for v in keys.values())
|
|
2307
2631
|
elif isinstance(keys, list):
|
|
2308
|
-
has_geo = any(
|
|
2309
|
-
len(k) >= 2 and k[1] in ["2dsphere", "2d", "geoHaystack"] for k in keys
|
|
2310
|
-
)
|
|
2632
|
+
has_geo = any(len(k) >= 2 and k[1] in ["2dsphere", "2d", "geoHaystack"] for k in keys)
|
|
2311
2633
|
if not has_geo:
|
|
2312
2634
|
return (
|
|
2313
2635
|
False,
|
|
@@ -2448,8 +2770,7 @@ def validate_index_definition(
|
|
|
2448
2770
|
if not index_type:
|
|
2449
2771
|
return (
|
|
2450
2772
|
False,
|
|
2451
|
-
f"Index '{index_name}' in collection '{collection_name}' "
|
|
2452
|
-
f"is missing 'type' field",
|
|
2773
|
+
f"Index '{index_name}' in collection '{collection_name}' " f"is missing 'type' field",
|
|
2453
2774
|
)
|
|
2454
2775
|
|
|
2455
2776
|
# Type-specific validation
|
|
@@ -2464,9 +2785,7 @@ def validate_index_definition(
|
|
|
2464
2785
|
elif index_type == "geospatial":
|
|
2465
2786
|
return _validate_geospatial_index(index_def, collection_name, index_name)
|
|
2466
2787
|
elif index_type in ("vectorSearch", "search"):
|
|
2467
|
-
return _validate_vector_search_index(
|
|
2468
|
-
index_def, collection_name, index_name, index_type
|
|
2469
|
-
)
|
|
2788
|
+
return _validate_vector_search_index(index_def, collection_name, index_name, index_type)
|
|
2470
2789
|
elif index_type == "hybrid":
|
|
2471
2790
|
return _validate_hybrid_index(index_def, collection_name, index_name)
|
|
2472
2791
|
else:
|
|
@@ -2478,7 +2797,7 @@ def validate_index_definition(
|
|
|
2478
2797
|
|
|
2479
2798
|
|
|
2480
2799
|
def validate_managed_indexes(
|
|
2481
|
-
managed_indexes: Dict[str, List[Dict[str, Any]]]
|
|
2800
|
+
managed_indexes: Dict[str, List[Dict[str, Any]]],
|
|
2482
2801
|
) -> Tuple[bool, Optional[str]]:
|
|
2483
2802
|
"""
|
|
2484
2803
|
Validate all managed indexes with collection and index context.
|
|
@@ -2516,9 +2835,7 @@ def validate_managed_indexes(
|
|
|
2516
2835
|
)
|
|
2517
2836
|
|
|
2518
2837
|
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
|
-
)
|
|
2838
|
+
is_valid, error_msg = validate_index_definition(index_def, collection_name, index_name)
|
|
2522
2839
|
if not is_valid:
|
|
2523
2840
|
return False, error_msg
|
|
2524
2841
|
|
|
@@ -2600,13 +2917,11 @@ class ManifestValidator:
|
|
|
2600
2917
|
Returns:
|
|
2601
2918
|
Tuple of (is_valid, error_message, error_paths)
|
|
2602
2919
|
"""
|
|
2603
|
-
return await validate_manifest_with_db(
|
|
2604
|
-
manifest, db_validator, use_cache=use_cache
|
|
2605
|
-
)
|
|
2920
|
+
return await validate_manifest_with_db(manifest, db_validator, use_cache=use_cache)
|
|
2606
2921
|
|
|
2607
2922
|
@staticmethod
|
|
2608
2923
|
def validate_managed_indexes(
|
|
2609
|
-
managed_indexes: Dict[str, List[Dict[str, Any]]]
|
|
2924
|
+
managed_indexes: Dict[str, List[Dict[str, Any]]],
|
|
2610
2925
|
) -> Tuple[bool, Optional[str]]:
|
|
2611
2926
|
"""
|
|
2612
2927
|
Validate managed indexes configuration.
|
|
@@ -2720,17 +3035,13 @@ class ManifestParser:
|
|
|
2720
3035
|
if validate:
|
|
2721
3036
|
is_valid, error, paths = ManifestValidator.validate(manifest_data)
|
|
2722
3037
|
if not is_valid:
|
|
2723
|
-
error_path_str = (
|
|
2724
|
-
f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2725
|
-
)
|
|
3038
|
+
error_path_str = f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2726
3039
|
raise ValueError(f"Manifest validation failed: {error}{error_path_str}")
|
|
2727
3040
|
|
|
2728
3041
|
return manifest_data
|
|
2729
3042
|
|
|
2730
3043
|
@staticmethod
|
|
2731
|
-
async def load_from_dict(
|
|
2732
|
-
data: Dict[str, Any], validate: bool = True
|
|
2733
|
-
) -> Dict[str, Any]:
|
|
3044
|
+
async def load_from_dict(data: Dict[str, Any], validate: bool = True) -> Dict[str, Any]:
|
|
2734
3045
|
"""
|
|
2735
3046
|
Load and validate manifest from dictionary.
|
|
2736
3047
|
|
|
@@ -2748,9 +3059,7 @@ class ManifestParser:
|
|
|
2748
3059
|
if validate:
|
|
2749
3060
|
is_valid, error, paths = ManifestValidator.validate(data)
|
|
2750
3061
|
if not is_valid:
|
|
2751
|
-
error_path_str = (
|
|
2752
|
-
f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2753
|
-
)
|
|
3062
|
+
error_path_str = f" (errors in: {', '.join(paths[:3])})" if paths else ""
|
|
2754
3063
|
raise ValueError(f"Manifest validation failed: {error}{error_path_str}")
|
|
2755
3064
|
|
|
2756
3065
|
return data.copy()
|