cloud-dog-storage 0.1.4__tar.gz
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.
- cloud_dog_storage-0.1.4/.gitignore +12 -0
- cloud_dog_storage-0.1.4/AGENT-INSTRUCTION-BUILD-STORAGE.md +452 -0
- cloud_dog_storage-0.1.4/AGENT-INSTRUCTION-FIX-STORAGE.md +313 -0
- cloud_dog_storage-0.1.4/ARCHITECTURE.md +496 -0
- cloud_dog_storage-0.1.4/LICENCE +190 -0
- cloud_dog_storage-0.1.4/LICENSE +176 -0
- cloud_dog_storage-0.1.4/NOTICE +7 -0
- cloud_dog_storage-0.1.4/PKG-INFO +129 -0
- cloud_dog_storage-0.1.4/QUALITY-GATE.md +127 -0
- cloud_dog_storage-0.1.4/README.md +88 -0
- cloud_dog_storage-0.1.4/REQUIREMENTS.md +270 -0
- cloud_dog_storage-0.1.4/TESTS.md +428 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/__init__.py +68 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/api/__init__.py +8 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/api/fastapi/__init__.py +12 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/api/fastapi/deps.py +42 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/async_wrapper.py +133 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/__init__.py +45 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/base.py +89 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/ftp.py +252 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/google_drive.py +386 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/local.py +209 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/s3.py +287 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/backends/webdav.py +280 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/config/__init__.py +28 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/config/models.py +89 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/errors.py +53 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/factory.py +49 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/models.py +54 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/observability/__init__.py +12 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/observability/logging.py +65 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/path_utils.py +224 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/security/__init__.py +13 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/security/path_sanitiser.py +62 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/security/tls.py +37 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/testing/__init__.py +13 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/testing/conformance.py +88 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/testing/fixtures.py +111 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/testing/mock_backend.py +164 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/traceability_ids.py +36 -0
- cloud_dog_storage-0.1.4/cloud_dog_storage/transfer.py +157 -0
- cloud_dog_storage-0.1.4/defaults.yaml +68 -0
- cloud_dog_storage-0.1.4/pyproject.toml +67 -0
- cloud_dog_storage-0.1.4/scaffold/cloud_dog_storage/__init__.py +48 -0
- cloud_dog_storage-0.1.4/scaffold/pyproject.toml +64 -0
- cloud_dog_storage-0.1.4/scaffold/tests/conftest.py +165 -0
- cloud_dog_storage-0.1.4/tests/application/AT1.1_FastAPIFileUploadDownload/test_fastapi_file_ops.py +64 -0
- cloud_dog_storage-0.1.4/tests/application/AT1.2_FastAPIStorageDependency/test_fastapi_storage_dep.py +44 -0
- cloud_dog_storage-0.1.4/tests/application/AT1.3_AsyncStorageInFastAPI/test_async_storage_fastapi.py +48 -0
- cloud_dog_storage-0.1.4/tests/application/AT1.4_BackendSwitchViaConfig/test_backend_switch.py +36 -0
- cloud_dog_storage-0.1.4/tests/application/AT1.5_ConformanceSuiteRunner/test_conformance_runner.py +33 -0
- cloud_dog_storage-0.1.4/tests/conftest.py +388 -0
- cloud_dog_storage-0.1.4/tests/env-AT +2 -0
- cloud_dog_storage-0.1.4/tests/env-IT +20 -0
- cloud_dog_storage-0.1.4/tests/env-QT +2 -0
- cloud_dog_storage-0.1.4/tests/env-ST +2 -0
- cloud_dog_storage-0.1.4/tests/env-UT +2 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.10_FtpListDir/test_ftp_list_dir.py +46 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.11_FtpCreateDir/test_ftp_create_dir.py +41 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.12_FtpCopyMove/test_ftp_copy_move.py +48 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.13_GoogleDriveReadWriteDelete/test_gdrive_crud.py +47 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.14_GoogleDriveListDir/test_gdrive_list_dir.py +43 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.15_GoogleDriveCreateDir/test_gdrive_create_dir.py +45 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.16_GoogleDriveCopyMove/test_gdrive_copy_move.py +51 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.17_S3ConformanceSuite/test_s3_conformance.py +47 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.18_WebDavConformanceSuite/test_webdav_conformance.py +40 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.19_FtpConformanceSuite/test_ftp_conformance.py +38 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.1_S3ReadWriteDelete/test_s3_crud.py +50 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.20_GoogleDriveConformanceSuite/test_gdrive_conformance.py +41 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.2_S3ListDir/test_s3_list_dir.py +54 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.3_S3CopyMove/test_s3_copy_move.py +52 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.4_S3StatExists/test_s3_stat_exists.py +51 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.5_WebDavReadWriteDelete/test_webdav_crud.py +45 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.6_WebDavListDir/test_webdav_list_dir.py +48 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.7_WebDavCopyMove/test_webdav_copy_move.py +50 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.8_WebDavCreateDir/test_webdav_create_dir.py +43 -0
- cloud_dog_storage-0.1.4/tests/integration/IT1.9_FtpReadWriteDelete/test_ftp_crud.py +49 -0
- cloud_dog_storage-0.1.4/tests/security/QT1.1_PathTraversalAttack/test_path_traversal_attack.py +30 -0
- cloud_dog_storage-0.1.4/tests/security/QT1.2_CredentialRedactionInLogs/test_credential_redaction.py +32 -0
- cloud_dog_storage-0.1.4/tests/security/QT1.3_TlsEnforcement/test_tls_enforcement.py +35 -0
- cloud_dog_storage-0.1.4/tests/security/QT1.4_RootBoundaryEscape/test_root_boundary.py +35 -0
- cloud_dog_storage-0.1.4/tests/security/QT1.5_NullByteInjection/test_null_byte_injection.py +30 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.10_StoredFileSerialisation/test_stored_file_serialisation.py +42 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.1_LocalFullLifecycle/test_local_full_lifecycle.py +47 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.2_LocalOverwriteSemantics/test_local_overwrite.py +35 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.3_LocalRecursiveListing/test_local_recursive_listing.py +35 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.4_LocalIterPaths/test_local_iter_paths.py +37 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.5_FactoryConfigRoundTrip/test_factory_config_roundtrip.py +34 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.6_ConfigFromDefaults/test_config_from_defaults.py +38 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.7_VaultConfigIntegration/test_vault_config.py +54 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.8_ConformanceSuiteLocal/test_conformance_local.py +33 -0
- cloud_dog_storage-0.1.4/tests/system/ST1.9_AsyncWrapperLifecycle/test_async_wrapper_lifecycle.py +42 -0
- cloud_dog_storage-0.1.4/tests/test_traceability_ids.py +26 -0
- cloud_dog_storage-0.1.4/tests/test_transfer.py +106 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.10_S3SigningLogic/test_s3_signing.py +41 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.11_S3KeyMapping/test_s3_key_mapping.py +37 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.12_WebDavPropfindParsing/test_webdav_propfind.py +47 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.13_WebDavRetryLogic/test_webdav_retry.py +56 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.14_FtpPathMapping/test_ftp_path_mapping.py +37 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.15_GoogleDriveFolderIdExtraction/test_gdrive_folder_id.py +42 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.16_GoogleDriveTokenRefresh/test_gdrive_token_refresh.py +79 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.17_StorageConfigModels/test_config_models.py +32 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.18_StorageConfigDefaults/test_config_defaults.py +35 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.19_FactoryDispatch/test_factory_dispatch.py +42 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.1_StorageBackendInterface/test_backend_interface.py +75 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.20_FactoryLazyImports/test_factory_lazy_imports.py +42 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.21_ErrorTaxonomy/test_error_taxonomy.py +53 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.22_PathSanitiser/test_path_sanitiser.py +46 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.23_PathTraversalPrevention/test_path_traversal.py +51 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.24_TlsConfigHelpers/test_tls_config.py +43 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.25_StorageEntryModel/test_storage_entry.py +36 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.26_StorageStatModel/test_storage_stat.py +36 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.27_StoredFileModel/test_stored_file.py +37 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.28_MockBackend/test_mock_backend.py +43 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.29_AsyncWrapper/test_async_wrapper.py +43 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.2_LocalStorageBasic/test_local_basic.py +49 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.30_ObservabilityLogging/test_observability_logging.py +42 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.31_NotSupportedErrorHandling/test_not_supported.py +55 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.32_CleanPosixHelper/test_clean_posix.py +30 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.3_LocalStorageReadWrite/test_local_read_write.py +42 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.4_LocalStorageListDir/test_local_list_dir.py +51 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.5_LocalStorageStat/test_local_stat.py +49 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.6_LocalStorageCopyMove/test_local_copy_move.py +46 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.7_LocalStorageChmod/test_local_chmod.py +36 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.8_LocalStorageCreateDir/test_local_create_dir.py +40 -0
- cloud_dog_storage-0.1.4/tests/unit/UT1.9_LocalStorageDiskSpace/test_local_disk_space.py +35 -0
- cloud_dog_storage-0.1.4/working/W28A-118B-PLATFORM-STORAGE-IT-REPAIR-REPORT.md +123 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# Agent Instruction — Build `cloud_dog_storage` v0.1.0
|
|
2
|
+
|
|
3
|
+
**Package:** `cloud_dog_storage`
|
|
4
|
+
**Standard:** PS-85 (Storage Interfaces)
|
|
5
|
+
**Date:** 2026-02-18
|
|
6
|
+
**Priority:** High — needed ASAP to support new package builds and eliminate duplication
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
Build the `cloud_dog_storage` backend package from scratch, following the design in REQUIREMENTS.md, ARCHITECTURE.md, and TESTS.md in this directory. The package provides a unified file/object storage abstraction with 5 pluggable backends.
|
|
13
|
+
|
|
14
|
+
**This package is NOT part of the current migration exercise** but is needed immediately to support new project development and future migration work.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## INTEGRITY WARRANTY — ZERO TOLERANCE
|
|
19
|
+
|
|
20
|
+
All clauses from `AGENT-INSTRUCTION.md` § "INTEGRITY WARRANTY — ZERO TOLERANCE" and `AGENT-INSTRUCTION-TIER2.md` apply in full. Additionally:
|
|
21
|
+
|
|
22
|
+
1. **EVERY BACKEND MUST BE FULLY FUNCTIONAL.** A backend that only implements `read_bytes`/`write_bytes` but stubs `list_dir`/`stat`/`copy_path` is INCOMPLETE. Every method listed in ARCHITECTURE.md CC1.1 that is NOT `NotSupportedError` for that backend MUST work.
|
|
23
|
+
2. **S3 SIGNING MUST BE REAL.** The S3 backend uses AWS SigV4 with `hashlib`/`hmac` — NOT `boto3`. The signing logic must produce valid Authorization headers. Test against MinIO or real S3.
|
|
24
|
+
3. **WEBDAV XML PARSING MUST BE REAL.** PROPFIND responses must be parsed from real XML — not string matching. Use `xml.etree.ElementTree`.
|
|
25
|
+
4. **GOOGLE DRIVE MUST HANDLE TOKEN REFRESH.** The OAuth2 refresh flow must actually call the token endpoint and update the stored access token. Not a stub.
|
|
26
|
+
5. **ASYNC WRAPPER MUST USE THREAD POOL.** `AsyncStorageBackend` must use `asyncio.to_thread` or `loop.run_in_executor` — NOT `async def` that just calls sync methods directly.
|
|
27
|
+
6. **CONFORMANCE SUITE MUST BE REUSABLE.** `testing/conformance.py` must work against ANY `StorageBackend` instance, not just `LocalStorage`.
|
|
28
|
+
7. **ALL IT TESTS MUST CLEAN UP.** Use `cloud_dog_test_*` prefix for all test files/directories. Delete after test. Verify deletion.
|
|
29
|
+
8. **CONFIG DELEGATION — ZERO TOLERANCE.** This package MUST NOT read `os.environ` for credentials, import `hvac`, navigate Vault JSON, or implement secret overlay/merge logic. See § Config Delegation below.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Config Delegation — ZERO TOLERANCE (PS-80)
|
|
34
|
+
|
|
35
|
+
**`cloud_dog_config` is the ONLY package that may resolve secrets, read Vault, or parse config sources.** This package receives fully-resolved configuration — it MUST NOT navigate Vault JSON, call `hvac`, read `os.environ` for credentials, or implement its own secret resolution logic.
|
|
36
|
+
|
|
37
|
+
**Absolute prohibitions:**
|
|
38
|
+
|
|
39
|
+
1. **NO `os.environ` reads for credentials** — S3 keys, WebDAV passwords, FTP passwords, Google Drive tokens MUST come from `StorageConfig` objects populated by `cloud_dog_config`, never from direct `os.environ` access. (`os.environ` reads for _non-secret_ feature flags in test fixtures are permitted.)
|
|
40
|
+
2. **NO `hvac` imports** — only `cloud_dog_config` may import or use the `hvac` Vault client library.
|
|
41
|
+
3. **NO Vault path navigation** — no function may accept a `vault_config: dict` parameter, parse `CLOUD_DOG_*_VAULT_JSON` env vars, or navigate Vault section structures.
|
|
42
|
+
4. **NO secret overlay/merge functions** — no `overlay_secrets()`, `_resolve_runtime_provider_config()`, `_vault_from_env()`, `SecretResolver`, or equivalent.
|
|
43
|
+
5. **NO `secrets/` module** — no `secrets/` directory with overlay, resolver, or similar modules.
|
|
44
|
+
6. **NO `config/vault.py` module** — this package does NOT have a Vault integration module. Vault resolution is handled entirely by `cloud_dog_config`.
|
|
45
|
+
|
|
46
|
+
**How credentials flow:**
|
|
47
|
+
```
|
|
48
|
+
defaults.yaml → config.yaml → env-files → os.environ → cloud_dog_config compile phase
|
|
49
|
+
↓
|
|
50
|
+
${vault.storage.s3.secret_key} resolved → StorageConfig(s3=S3Config(secret_key="resolved_value"))
|
|
51
|
+
↓
|
|
52
|
+
S3Storage receives S3Config → uses self._secret_key directly
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**How consuming projects use this package:**
|
|
56
|
+
```python
|
|
57
|
+
from cloud_dog_storage import build_storage_backend
|
|
58
|
+
from cloud_dog_config import get_config, bind_model
|
|
59
|
+
from cloud_dog_storage.config.models import StorageConfig
|
|
60
|
+
|
|
61
|
+
# cloud_dog_config resolves ${vault.storage.*} variables automatically
|
|
62
|
+
config = bind_model(get_config(), "storage", StorageConfig)
|
|
63
|
+
storage = build_storage_backend(config)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**When `cloud_dog_config` is NOT installed**, constructing `StorageConfig` directly with pre-populated values is the fallback:
|
|
67
|
+
```python
|
|
68
|
+
from cloud_dog_storage import build_storage_backend
|
|
69
|
+
from cloud_dog_storage.config.models import StorageConfig, S3Config
|
|
70
|
+
|
|
71
|
+
config = StorageConfig(backend="s3", s3=S3Config(
|
|
72
|
+
endpoint="https://s3.cloud-dog.net",
|
|
73
|
+
bucket="files",
|
|
74
|
+
access_key="pre-resolved-value",
|
|
75
|
+
secret_key="pre-resolved-value",
|
|
76
|
+
))
|
|
77
|
+
storage = build_storage_backend(config)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Verification command (run after build):**
|
|
81
|
+
```bash
|
|
82
|
+
# Must return zero hits (excluding test fixtures and conftest.py vault_env fixture)
|
|
83
|
+
grep -rn "os.environ\|import hvac\|vault_config\|overlay_secrets\|_vault_from_env\|VAULT_JSON\|SecretResolver" cloud_dog_storage/ --include="*.py" | grep -v __pycache__
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Governing Documents
|
|
89
|
+
|
|
90
|
+
Read these in order before writing any code:
|
|
91
|
+
|
|
92
|
+
1. **`packages/backend/AGENT-INSTRUCTION.md`** — Master agent instruction (INTEGRITY WARRANTY, config delegation, Vault, file headers, UK English) — **READ § "Config Delegation — ZERO TOLERANCE" FIRST**
|
|
93
|
+
2. **`docs/standards/85-storage-interfaces.md`** — PS-85 v1.0 (Storage Interfaces standard) — THE AUTHORITY
|
|
94
|
+
3. **`packages/backend/platform-storage/REQUIREMENTS.md`** — 21 FRs + NFRs + CS
|
|
95
|
+
4. **`packages/backend/platform-storage/ARCHITECTURE.md`** — Module layout (22 modules), component design, integration patterns
|
|
96
|
+
5. **`packages/backend/platform-storage/TESTS.md`** — 72 tests (32 UT + 10 ST + 20 IT + 5 AT + 5 QT)
|
|
97
|
+
6. **`docs/standards/80-config-mgmt.md`** — PS-80 (config precedence, Vault)
|
|
98
|
+
7. **`docs/standards/40-logging-observability.md`** — PS-40 (logging format)
|
|
99
|
+
8. **`docs/standards/90-security.md`** — PS-90 (path sanitisation, TLS, credentials)
|
|
100
|
+
9. **`docs/standards/95-testing.md`** — PS-95 (test hierarchy, --env enforcement)
|
|
101
|
+
10. **`RULES.md`** — UK English, file headers, no hardcoded values
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Donor Codebases — STUDY THESE
|
|
106
|
+
|
|
107
|
+
The implementation draws heavily from two existing codebases. **Study them before writing code:**
|
|
108
|
+
|
|
109
|
+
### Primary donor: `file-mcp-server/src/file_tools/storage/`
|
|
110
|
+
|
|
111
|
+
| File | Lines | Use |
|
|
112
|
+
|------|------:|-----|
|
|
113
|
+
| `base.py` | 82 | `StorageBackend` class, `NotSupportedError`, `StorageStat`, `StorageEntry` — **copy pattern exactly** |
|
|
114
|
+
| `local.py` | 64 | `LocalStorage` — pathlib delegation |
|
|
115
|
+
| `s3.py` | 299 | `S3Storage` — SigV4 signing, pure requests, ListObjectsV2 XML parsing |
|
|
116
|
+
| `webdav.py` | 344 | `WebDavStorage` — PROPFIND parsing, retry, move idempotency |
|
|
117
|
+
| `ftp.py` | 277 | `FtpStorage` — MLSD/NLST, connection-per-op, FTP_TLS |
|
|
118
|
+
| `google_drive.py` | 365 | `GoogleDriveStorage` — OAuth2 refresh, path→folder-ID, multipart upload |
|
|
119
|
+
| `factory.py` | 34 | `build_storage_backend()` — lazy imports |
|
|
120
|
+
|
|
121
|
+
### Secondary donor: `notification-agent/src/core/storage/`
|
|
122
|
+
|
|
123
|
+
| File | Lines | Use |
|
|
124
|
+
|------|------:|-----|
|
|
125
|
+
| `base.py` | 173 | `StorageBackend` ABC (async), `StoredFile`, error hierarchy — **merge error classes** |
|
|
126
|
+
| `filesystem.py` | 386 | Disk space checking, subdirectory patterns, permission management |
|
|
127
|
+
| `s3.py` | 283 | Async S3 via `aioboto3` — **DO NOT copy boto3 pattern, use pure requests from file-mcp** |
|
|
128
|
+
| `factory.py` | 109 | `StorageFactory` with registry pattern — merge with file-mcp factory |
|
|
129
|
+
| `storage_manager.py` | 416 | High-level manager — **extract `StoredFile` model and MIME logic** |
|
|
130
|
+
|
|
131
|
+
### Extraction strategy
|
|
132
|
+
|
|
133
|
+
1. Start with `file-mcp-server/storage/base.py` as the backbone.
|
|
134
|
+
2. Add `exists()`, `get_url()` from notification-agent.
|
|
135
|
+
3. Merge error hierarchy from notification-agent `base.py`.
|
|
136
|
+
4. Copy backends from file-mcp-server (they are the cleanest).
|
|
137
|
+
5. Add `LocalStorage` root boundary enforcement from notification-agent `filesystem.py`.
|
|
138
|
+
6. Add disk space checking from notification-agent `filesystem.py`.
|
|
139
|
+
7. Add `StoredFile` model from notification-agent `base.py`.
|
|
140
|
+
8. Add `AsyncStorageBackend` wrapper (new code, using `asyncio.to_thread`).
|
|
141
|
+
9. Add Pydantic config models (upgrade from file-mcp's models).
|
|
142
|
+
10. Add path sanitiser (consolidate `_clean_posix` + `_sanitize_path` from both).
|
|
143
|
+
11. Add conformance test suite and mock backend (new code).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Build Order
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Phase 1 — Core (no external deps):
|
|
151
|
+
1. errors.py — Error hierarchy
|
|
152
|
+
2. models.py — StorageEntry, StorageStat, StoredFile
|
|
153
|
+
3. security/path_sanitiser.py — clean_posix, validate_within_root
|
|
154
|
+
4. security/tls.py — TLS config helpers
|
|
155
|
+
5. backends/base.py — StorageBackend ABC
|
|
156
|
+
6. backends/local.py — LocalStorage
|
|
157
|
+
7. config/models.py — Pydantic config models
|
|
158
|
+
8. factory.py — build_storage_backend (local only first)
|
|
159
|
+
9. __init__.py — Public API
|
|
160
|
+
|
|
161
|
+
Phase 2 — Remote backends (requires `requests`):
|
|
162
|
+
10. backends/s3.py — S3Storage (SigV4)
|
|
163
|
+
11. backends/webdav.py — WebDavStorage
|
|
164
|
+
12. backends/ftp.py — FtpStorage
|
|
165
|
+
13. backends/google_drive.py — GoogleDriveStorage
|
|
166
|
+
14. factory.py — Add lazy imports for all backends
|
|
167
|
+
|
|
168
|
+
Phase 3 — Advanced features:
|
|
169
|
+
15. async_wrapper.py — AsyncStorageBackend
|
|
170
|
+
16. observability/logging.py — Logging integration
|
|
171
|
+
17. api/fastapi/deps.py — FastAPI dependency
|
|
172
|
+
NOTE: There is NO config/vault.py — Vault resolution is cloud_dog_config's job.
|
|
173
|
+
|
|
174
|
+
Phase 4 — Testing infrastructure:
|
|
175
|
+
18. testing/mock_backend.py — MockStorageBackend
|
|
176
|
+
19. testing/conformance.py — Conformance test suite
|
|
177
|
+
20. testing/fixtures.py — Shared test fixtures
|
|
178
|
+
|
|
179
|
+
Phase 5 — Tests:
|
|
180
|
+
21. All UT tests (32)
|
|
181
|
+
22. All ST tests (10)
|
|
182
|
+
23. All IT tests (20) — env-gated
|
|
183
|
+
24. All AT tests (5)
|
|
184
|
+
25. All QT tests (5)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## File Header (MANDATORY)
|
|
190
|
+
|
|
191
|
+
Every `.py` file MUST start with:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
"""
|
|
195
|
+
**************************************************
|
|
196
|
+
License: Apache 2.0
|
|
197
|
+
Ownership: Cloud-Dog, Viewdeck Engineering Ltd.
|
|
198
|
+
Description: <one-line description>
|
|
199
|
+
Standard: PS-85 (Storage Interfaces)
|
|
200
|
+
**************************************************
|
|
201
|
+
"""
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Key Implementation Details
|
|
207
|
+
|
|
208
|
+
### `_clean_posix` helper (from file-mcp-server)
|
|
209
|
+
|
|
210
|
+
This function is used by ALL remote backends. Extract it to `security/path_sanitiser.py`:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
def clean_posix(path: str) -> str:
|
|
214
|
+
"""Normalise to POSIX absolute path."""
|
|
215
|
+
if not path:
|
|
216
|
+
return "/"
|
|
217
|
+
if not path.startswith("/"):
|
|
218
|
+
path = "/" + path
|
|
219
|
+
norm = posixpath.normpath(path)
|
|
220
|
+
if not norm.startswith("/"):
|
|
221
|
+
norm = "/" + norm
|
|
222
|
+
return norm
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### S3 SigV4 Signing (from file-mcp-server `s3.py`)
|
|
226
|
+
|
|
227
|
+
The existing implementation in file-mcp-server works correctly. Key functions:
|
|
228
|
+
- `_sha256_hex(data: bytes) -> str`
|
|
229
|
+
- `_hmac(key: bytes, msg: str) -> bytes`
|
|
230
|
+
- `_aws_v4_signing_key(secret_key, date, region, service) -> bytes`
|
|
231
|
+
- `_canonical_query(params) -> str`
|
|
232
|
+
- `_canonical_headers(headers) -> tuple[str, str]`
|
|
233
|
+
|
|
234
|
+
Copy these functions exactly. They produce valid SigV4 signatures.
|
|
235
|
+
|
|
236
|
+
### WebDAV PROPFIND XML Parsing (from file-mcp-server `webdav.py`)
|
|
237
|
+
|
|
238
|
+
The `_parse_propfind_xml` function parses `{DAV:}response` elements. Copy the implementation and ensure it handles:
|
|
239
|
+
- `{DAV:}href` → path extraction
|
|
240
|
+
- `{DAV:}resourcetype` → `{DAV:}collection` → is_dir
|
|
241
|
+
- `{DAV:}getcontentlength` → size
|
|
242
|
+
- Namespace handling via `{DAV:}` prefix
|
|
243
|
+
|
|
244
|
+
### Google Drive OAuth2 (from file-mcp-server `google_drive.py`)
|
|
245
|
+
|
|
246
|
+
Token refresh flow:
|
|
247
|
+
1. Check `_access_token` and `_token_expires_at`.
|
|
248
|
+
2. If expired or near-expiry (30s buffer), POST to `_token_uri` with `client_id`, `client_secret`, `refresh_token`, `grant_type=refresh_token`.
|
|
249
|
+
3. Update `_access_token` and `_token_expires_at`.
|
|
250
|
+
4. If no `refresh_token` and `access_token` exists, use it as-is (will fail when expired).
|
|
251
|
+
|
|
252
|
+
### AsyncStorageBackend (NEW CODE)
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
import asyncio
|
|
256
|
+
from cloud_dog_storage.backends.base import StorageBackend
|
|
257
|
+
|
|
258
|
+
class AsyncStorageBackend:
|
|
259
|
+
def __init__(self, backend: StorageBackend) -> None:
|
|
260
|
+
self._backend = backend
|
|
261
|
+
|
|
262
|
+
async def read_bytes(self, path: str) -> bytes:
|
|
263
|
+
return await asyncio.to_thread(self._backend.read_bytes, path)
|
|
264
|
+
|
|
265
|
+
# ... same pattern for all methods
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Also provide compatibility methods matching notification-agent's interface:
|
|
269
|
+
- `store_file(content, filename, content_type, metadata)` → write_bytes + return StoredFile
|
|
270
|
+
- `get_file_content(path)` → read_bytes
|
|
271
|
+
- `delete_file(path)` → delete_path, return bool
|
|
272
|
+
- `file_exists(path)` → exists
|
|
273
|
+
- `get_file_url(path)` → get_url
|
|
274
|
+
|
|
275
|
+
### MockStorageBackend (NEW CODE)
|
|
276
|
+
|
|
277
|
+
In-memory dict-based implementation:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
class MockStorageBackend(StorageBackend):
|
|
281
|
+
backend_name = "mock"
|
|
282
|
+
|
|
283
|
+
def __init__(self) -> None:
|
|
284
|
+
self._files: dict[str, bytes] = {}
|
|
285
|
+
self._dirs: set[str] = {"/"}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Implement all operations against `_files` and `_dirs`. Thread-safe via `threading.Lock`.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Test Service Preconditions
|
|
293
|
+
|
|
294
|
+
| Test Tier | Requires | How to Provide |
|
|
295
|
+
|-----------|----------|---------------|
|
|
296
|
+
| **UT** | Nothing external | tmp_path + MockStorageBackend |
|
|
297
|
+
| **ST** | Local filesystem | tmp_path (pytest built-in) |
|
|
298
|
+
| **IT (S3)** | S3-compatible endpoint | Set `TEST_S3_ENDPOINT`, `TEST_S3_BUCKET`, `TEST_S3_ACCESS_KEY`, `TEST_S3_SECRET_KEY` |
|
|
299
|
+
| **IT (WebDAV)** | WebDAV server | Set `TEST_WEBDAV_URL`, `TEST_WEBDAV_USERNAME`, `TEST_WEBDAV_PASSWORD` |
|
|
300
|
+
| **IT (FTP)** | FTP server | Set `TEST_FTP_HOST`, `TEST_FTP_USERNAME`, `TEST_FTP_PASSWORD` |
|
|
301
|
+
| **IT (Google Drive)** | Google Drive OAuth | Set `TEST_GDRIVE_FOLDER_ID`, `TEST_GDRIVE_CLIENT_ID`, `TEST_GDRIVE_CLIENT_SECRET`, `TEST_GDRIVE_REFRESH_TOKEN` |
|
|
302
|
+
| **AT** | FastAPI test client | `httpx` TestClient |
|
|
303
|
+
| **QT** | Nothing external | Mock-based security tests |
|
|
304
|
+
|
|
305
|
+
### Vault Config Sections
|
|
306
|
+
|
|
307
|
+
| Section | Keys | Use |
|
|
308
|
+
|---------|------|-----|
|
|
309
|
+
| `dev.storage.s3` | `endpoint`, `bucket`, `access_key`, `secret_key` | IT S3 tests |
|
|
310
|
+
| `dev.storage.webdav` | `base_url`, `username`, `password` | IT WebDAV tests |
|
|
311
|
+
| `dev.storage.ftp` | `host`, `port`, `username`, `password` | IT FTP tests |
|
|
312
|
+
| `dev.storage.google_drive` | `folder_id`, `client_id`, `client_secret`, `refresh_token` | IT Google Drive tests |
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Verification Commands
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# Setup
|
|
320
|
+
cd /path/to/platform-storage
|
|
321
|
+
python -m venv .venv
|
|
322
|
+
source .venv/bin/activate
|
|
323
|
+
pip install -e ".[all,dev]"
|
|
324
|
+
|
|
325
|
+
# Lint + format
|
|
326
|
+
ruff check cloud_dog_storage tests
|
|
327
|
+
ruff format --check cloud_dog_storage tests
|
|
328
|
+
|
|
329
|
+
# Config delegation verification (MUST return zero hits)
|
|
330
|
+
grep -rn "os.environ\|import hvac\|vault_config\|overlay_secrets\|_vault_from_env\|VAULT_JSON\|SecretResolver" cloud_dog_storage/ --include="*.py" | grep -v __pycache__
|
|
331
|
+
|
|
332
|
+
# UT + ST (no external services)
|
|
333
|
+
pytest tests/ -v --env UT --env ST
|
|
334
|
+
|
|
335
|
+
# IT (requires Vault / test services)
|
|
336
|
+
set -a; source /opt/iac/Development/cloud-dog-ai/env-vault; set +a
|
|
337
|
+
pytest tests/ -v --env IT
|
|
338
|
+
|
|
339
|
+
# Full matrix
|
|
340
|
+
set -a; source /opt/iac/Development/cloud-dog-ai/env-vault; set +a
|
|
341
|
+
pytest tests/ -v --env UT --env ST --env IT --env AT --env QT
|
|
342
|
+
|
|
343
|
+
# Build
|
|
344
|
+
python -m build --no-isolation
|
|
345
|
+
|
|
346
|
+
# Install + import check
|
|
347
|
+
pip install --force-reinstall --no-deps dist/cloud_dog_storage-0.1.0-py3-none-any.whl
|
|
348
|
+
python -c "from cloud_dog_storage import StorageBackend, build_storage_backend; print('import-ok')"
|
|
349
|
+
python -c "from cloud_dog_storage.config.models import StorageConfig; print('config-ok')"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Done Criteria
|
|
355
|
+
|
|
356
|
+
The package is DONE when ALL of the following are true:
|
|
357
|
+
|
|
358
|
+
1. **SA1 completeness**: All 22 modules from ARCHITECTURE.md SA1 exist and contain real implementation (not stubs).
|
|
359
|
+
2. **Lint**: `ruff check cloud_dog_storage tests` — zero errors.
|
|
360
|
+
3. **Format**: `ruff format --check cloud_dog_storage tests` — all files formatted.
|
|
361
|
+
4. **Config delegation verified**: `grep -rn "os.environ\|import hvac\|vault_config\|overlay_secrets" cloud_dog_storage/ --include="*.py" | grep -v __pycache__` returns **zero hits**.
|
|
362
|
+
5. **UT+ST tests**: `pytest tests/ -v --env UT --env ST` — all pass, zero failures.
|
|
363
|
+
6. **IT tests**: At minimum, S3 and WebDAV IT tests pass against real services. FTP and Google Drive IT tests pass or skip gracefully.
|
|
364
|
+
7. **AT tests**: FastAPI integration tests pass.
|
|
365
|
+
8. **QT tests**: All security tests pass (path traversal, credential redaction, TLS, boundary, null byte).
|
|
366
|
+
9. **Build**: `python -m build --no-isolation` produces `.tar.gz` + `.whl`.
|
|
367
|
+
10. **Import**: `python -c "from cloud_dog_storage import StorageBackend, build_storage_backend; print('import-ok')"` works.
|
|
368
|
+
11. **No hardcoded values**: Zero hardcoded URLs, paths, credentials, timeouts.
|
|
369
|
+
12. **File headers**: Every `.py` file has the license header.
|
|
370
|
+
13. **UK English**: All docstrings and comments use UK English spelling.
|
|
371
|
+
14. **No `config/vault.py`**: The package directory MUST NOT contain a `config/vault.py` or `secrets/` module.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Anti-Patterns to Avoid (Lessons Learned)
|
|
376
|
+
|
|
377
|
+
These issues were found in previous package builds. Do NOT repeat them:
|
|
378
|
+
|
|
379
|
+
1. **DO NOT** create stub modules that raise `NotImplementedError` for everything and claim the package is done.
|
|
380
|
+
2. **DO NOT** use `boto3` for S3 — the pure-requests SigV4 approach from file-mcp-server is intentional (minimal deps, offline-friendly).
|
|
381
|
+
3. **DO NOT** implement only `read_bytes`/`write_bytes` and skip `list_dir`/`stat`/`copy_path`/`move_path`. Every required method MUST work.
|
|
382
|
+
4. **DO NOT** skip the conformance test suite — it is what ensures all backends behave consistently.
|
|
383
|
+
5. **DO NOT** log credentials. Ever. In any backend. In any error message. In any exception.
|
|
384
|
+
6. **DO NOT** hardcode test file paths or URLs. Use `tmp_path` for local, env vars for remote.
|
|
385
|
+
7. **DO NOT** leave IT test files behind. Every IT test MUST clean up.
|
|
386
|
+
8. **DO NOT** create fake async by wrapping sync calls without a thread pool. Use `asyncio.to_thread`.
|
|
387
|
+
9. **DO NOT** skip path sanitisation. The `clean_posix` + `validate_within_root` functions are security-critical.
|
|
388
|
+
10. **DO NOT** implement WebDAV PROPFIND parsing with string matching. Use `xml.etree.ElementTree`.
|
|
389
|
+
11. **DO NOT** create a `config/vault.py` module. This package does NOT read Vault. All credentials arrive pre-resolved via `cloud_dog_config`.
|
|
390
|
+
12. **DO NOT** read `os.environ` for any credential (access_key, secret_key, password, client_secret, refresh_token). Backend constructors receive typed Pydantic config objects with credentials already populated.
|
|
391
|
+
13. **DO NOT** create a `secrets/` directory or any secret overlay/resolver/merge functions.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Expected Output
|
|
396
|
+
|
|
397
|
+
After successful build, the package directory should look like:
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
platform-storage/
|
|
401
|
+
cloud_dog_storage/
|
|
402
|
+
__init__.py
|
|
403
|
+
async_wrapper.py
|
|
404
|
+
errors.py
|
|
405
|
+
factory.py
|
|
406
|
+
models.py
|
|
407
|
+
backends/
|
|
408
|
+
__init__.py
|
|
409
|
+
base.py
|
|
410
|
+
local.py
|
|
411
|
+
s3.py
|
|
412
|
+
webdav.py
|
|
413
|
+
ftp.py
|
|
414
|
+
google_drive.py
|
|
415
|
+
config/
|
|
416
|
+
__init__.py
|
|
417
|
+
models.py # NO vault.py — credentials arrive pre-resolved
|
|
418
|
+
security/
|
|
419
|
+
__init__.py
|
|
420
|
+
path_sanitiser.py
|
|
421
|
+
tls.py
|
|
422
|
+
observability/
|
|
423
|
+
__init__.py
|
|
424
|
+
logging.py
|
|
425
|
+
api/
|
|
426
|
+
__init__.py
|
|
427
|
+
fastapi/
|
|
428
|
+
__init__.py
|
|
429
|
+
deps.py
|
|
430
|
+
testing/
|
|
431
|
+
__init__.py
|
|
432
|
+
conformance.py
|
|
433
|
+
fixtures.py
|
|
434
|
+
mock_backend.py
|
|
435
|
+
tests/
|
|
436
|
+
conftest.py
|
|
437
|
+
env-UT
|
|
438
|
+
env-ST
|
|
439
|
+
env-IT
|
|
440
|
+
env-AT
|
|
441
|
+
unit/ (32 test files)
|
|
442
|
+
system/ (10 test files)
|
|
443
|
+
integration/ (20 test files)
|
|
444
|
+
application/ (5 test files)
|
|
445
|
+
security/ (5 test files)
|
|
446
|
+
pyproject.toml
|
|
447
|
+
defaults.yaml
|
|
448
|
+
README.md
|
|
449
|
+
dist/
|
|
450
|
+
cloud_dog_storage-0.1.0.tar.gz
|
|
451
|
+
cloud_dog_storage-0.1.0-py3-none-any.whl
|
|
452
|
+
```
|