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.
Files changed (127) hide show
  1. cloud_dog_storage-0.1.4/.gitignore +12 -0
  2. cloud_dog_storage-0.1.4/AGENT-INSTRUCTION-BUILD-STORAGE.md +452 -0
  3. cloud_dog_storage-0.1.4/AGENT-INSTRUCTION-FIX-STORAGE.md +313 -0
  4. cloud_dog_storage-0.1.4/ARCHITECTURE.md +496 -0
  5. cloud_dog_storage-0.1.4/LICENCE +190 -0
  6. cloud_dog_storage-0.1.4/LICENSE +176 -0
  7. cloud_dog_storage-0.1.4/NOTICE +7 -0
  8. cloud_dog_storage-0.1.4/PKG-INFO +129 -0
  9. cloud_dog_storage-0.1.4/QUALITY-GATE.md +127 -0
  10. cloud_dog_storage-0.1.4/README.md +88 -0
  11. cloud_dog_storage-0.1.4/REQUIREMENTS.md +270 -0
  12. cloud_dog_storage-0.1.4/TESTS.md +428 -0
  13. cloud_dog_storage-0.1.4/cloud_dog_storage/__init__.py +68 -0
  14. cloud_dog_storage-0.1.4/cloud_dog_storage/api/__init__.py +8 -0
  15. cloud_dog_storage-0.1.4/cloud_dog_storage/api/fastapi/__init__.py +12 -0
  16. cloud_dog_storage-0.1.4/cloud_dog_storage/api/fastapi/deps.py +42 -0
  17. cloud_dog_storage-0.1.4/cloud_dog_storage/async_wrapper.py +133 -0
  18. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/__init__.py +45 -0
  19. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/base.py +89 -0
  20. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/ftp.py +252 -0
  21. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/google_drive.py +386 -0
  22. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/local.py +209 -0
  23. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/s3.py +287 -0
  24. cloud_dog_storage-0.1.4/cloud_dog_storage/backends/webdav.py +280 -0
  25. cloud_dog_storage-0.1.4/cloud_dog_storage/config/__init__.py +28 -0
  26. cloud_dog_storage-0.1.4/cloud_dog_storage/config/models.py +89 -0
  27. cloud_dog_storage-0.1.4/cloud_dog_storage/errors.py +53 -0
  28. cloud_dog_storage-0.1.4/cloud_dog_storage/factory.py +49 -0
  29. cloud_dog_storage-0.1.4/cloud_dog_storage/models.py +54 -0
  30. cloud_dog_storage-0.1.4/cloud_dog_storage/observability/__init__.py +12 -0
  31. cloud_dog_storage-0.1.4/cloud_dog_storage/observability/logging.py +65 -0
  32. cloud_dog_storage-0.1.4/cloud_dog_storage/path_utils.py +224 -0
  33. cloud_dog_storage-0.1.4/cloud_dog_storage/security/__init__.py +13 -0
  34. cloud_dog_storage-0.1.4/cloud_dog_storage/security/path_sanitiser.py +62 -0
  35. cloud_dog_storage-0.1.4/cloud_dog_storage/security/tls.py +37 -0
  36. cloud_dog_storage-0.1.4/cloud_dog_storage/testing/__init__.py +13 -0
  37. cloud_dog_storage-0.1.4/cloud_dog_storage/testing/conformance.py +88 -0
  38. cloud_dog_storage-0.1.4/cloud_dog_storage/testing/fixtures.py +111 -0
  39. cloud_dog_storage-0.1.4/cloud_dog_storage/testing/mock_backend.py +164 -0
  40. cloud_dog_storage-0.1.4/cloud_dog_storage/traceability_ids.py +36 -0
  41. cloud_dog_storage-0.1.4/cloud_dog_storage/transfer.py +157 -0
  42. cloud_dog_storage-0.1.4/defaults.yaml +68 -0
  43. cloud_dog_storage-0.1.4/pyproject.toml +67 -0
  44. cloud_dog_storage-0.1.4/scaffold/cloud_dog_storage/__init__.py +48 -0
  45. cloud_dog_storage-0.1.4/scaffold/pyproject.toml +64 -0
  46. cloud_dog_storage-0.1.4/scaffold/tests/conftest.py +165 -0
  47. cloud_dog_storage-0.1.4/tests/application/AT1.1_FastAPIFileUploadDownload/test_fastapi_file_ops.py +64 -0
  48. cloud_dog_storage-0.1.4/tests/application/AT1.2_FastAPIStorageDependency/test_fastapi_storage_dep.py +44 -0
  49. cloud_dog_storage-0.1.4/tests/application/AT1.3_AsyncStorageInFastAPI/test_async_storage_fastapi.py +48 -0
  50. cloud_dog_storage-0.1.4/tests/application/AT1.4_BackendSwitchViaConfig/test_backend_switch.py +36 -0
  51. cloud_dog_storage-0.1.4/tests/application/AT1.5_ConformanceSuiteRunner/test_conformance_runner.py +33 -0
  52. cloud_dog_storage-0.1.4/tests/conftest.py +388 -0
  53. cloud_dog_storage-0.1.4/tests/env-AT +2 -0
  54. cloud_dog_storage-0.1.4/tests/env-IT +20 -0
  55. cloud_dog_storage-0.1.4/tests/env-QT +2 -0
  56. cloud_dog_storage-0.1.4/tests/env-ST +2 -0
  57. cloud_dog_storage-0.1.4/tests/env-UT +2 -0
  58. cloud_dog_storage-0.1.4/tests/integration/IT1.10_FtpListDir/test_ftp_list_dir.py +46 -0
  59. cloud_dog_storage-0.1.4/tests/integration/IT1.11_FtpCreateDir/test_ftp_create_dir.py +41 -0
  60. cloud_dog_storage-0.1.4/tests/integration/IT1.12_FtpCopyMove/test_ftp_copy_move.py +48 -0
  61. cloud_dog_storage-0.1.4/tests/integration/IT1.13_GoogleDriveReadWriteDelete/test_gdrive_crud.py +47 -0
  62. cloud_dog_storage-0.1.4/tests/integration/IT1.14_GoogleDriveListDir/test_gdrive_list_dir.py +43 -0
  63. cloud_dog_storage-0.1.4/tests/integration/IT1.15_GoogleDriveCreateDir/test_gdrive_create_dir.py +45 -0
  64. cloud_dog_storage-0.1.4/tests/integration/IT1.16_GoogleDriveCopyMove/test_gdrive_copy_move.py +51 -0
  65. cloud_dog_storage-0.1.4/tests/integration/IT1.17_S3ConformanceSuite/test_s3_conformance.py +47 -0
  66. cloud_dog_storage-0.1.4/tests/integration/IT1.18_WebDavConformanceSuite/test_webdav_conformance.py +40 -0
  67. cloud_dog_storage-0.1.4/tests/integration/IT1.19_FtpConformanceSuite/test_ftp_conformance.py +38 -0
  68. cloud_dog_storage-0.1.4/tests/integration/IT1.1_S3ReadWriteDelete/test_s3_crud.py +50 -0
  69. cloud_dog_storage-0.1.4/tests/integration/IT1.20_GoogleDriveConformanceSuite/test_gdrive_conformance.py +41 -0
  70. cloud_dog_storage-0.1.4/tests/integration/IT1.2_S3ListDir/test_s3_list_dir.py +54 -0
  71. cloud_dog_storage-0.1.4/tests/integration/IT1.3_S3CopyMove/test_s3_copy_move.py +52 -0
  72. cloud_dog_storage-0.1.4/tests/integration/IT1.4_S3StatExists/test_s3_stat_exists.py +51 -0
  73. cloud_dog_storage-0.1.4/tests/integration/IT1.5_WebDavReadWriteDelete/test_webdav_crud.py +45 -0
  74. cloud_dog_storage-0.1.4/tests/integration/IT1.6_WebDavListDir/test_webdav_list_dir.py +48 -0
  75. cloud_dog_storage-0.1.4/tests/integration/IT1.7_WebDavCopyMove/test_webdav_copy_move.py +50 -0
  76. cloud_dog_storage-0.1.4/tests/integration/IT1.8_WebDavCreateDir/test_webdav_create_dir.py +43 -0
  77. cloud_dog_storage-0.1.4/tests/integration/IT1.9_FtpReadWriteDelete/test_ftp_crud.py +49 -0
  78. cloud_dog_storage-0.1.4/tests/security/QT1.1_PathTraversalAttack/test_path_traversal_attack.py +30 -0
  79. cloud_dog_storage-0.1.4/tests/security/QT1.2_CredentialRedactionInLogs/test_credential_redaction.py +32 -0
  80. cloud_dog_storage-0.1.4/tests/security/QT1.3_TlsEnforcement/test_tls_enforcement.py +35 -0
  81. cloud_dog_storage-0.1.4/tests/security/QT1.4_RootBoundaryEscape/test_root_boundary.py +35 -0
  82. cloud_dog_storage-0.1.4/tests/security/QT1.5_NullByteInjection/test_null_byte_injection.py +30 -0
  83. cloud_dog_storage-0.1.4/tests/system/ST1.10_StoredFileSerialisation/test_stored_file_serialisation.py +42 -0
  84. cloud_dog_storage-0.1.4/tests/system/ST1.1_LocalFullLifecycle/test_local_full_lifecycle.py +47 -0
  85. cloud_dog_storage-0.1.4/tests/system/ST1.2_LocalOverwriteSemantics/test_local_overwrite.py +35 -0
  86. cloud_dog_storage-0.1.4/tests/system/ST1.3_LocalRecursiveListing/test_local_recursive_listing.py +35 -0
  87. cloud_dog_storage-0.1.4/tests/system/ST1.4_LocalIterPaths/test_local_iter_paths.py +37 -0
  88. cloud_dog_storage-0.1.4/tests/system/ST1.5_FactoryConfigRoundTrip/test_factory_config_roundtrip.py +34 -0
  89. cloud_dog_storage-0.1.4/tests/system/ST1.6_ConfigFromDefaults/test_config_from_defaults.py +38 -0
  90. cloud_dog_storage-0.1.4/tests/system/ST1.7_VaultConfigIntegration/test_vault_config.py +54 -0
  91. cloud_dog_storage-0.1.4/tests/system/ST1.8_ConformanceSuiteLocal/test_conformance_local.py +33 -0
  92. cloud_dog_storage-0.1.4/tests/system/ST1.9_AsyncWrapperLifecycle/test_async_wrapper_lifecycle.py +42 -0
  93. cloud_dog_storage-0.1.4/tests/test_traceability_ids.py +26 -0
  94. cloud_dog_storage-0.1.4/tests/test_transfer.py +106 -0
  95. cloud_dog_storage-0.1.4/tests/unit/UT1.10_S3SigningLogic/test_s3_signing.py +41 -0
  96. cloud_dog_storage-0.1.4/tests/unit/UT1.11_S3KeyMapping/test_s3_key_mapping.py +37 -0
  97. cloud_dog_storage-0.1.4/tests/unit/UT1.12_WebDavPropfindParsing/test_webdav_propfind.py +47 -0
  98. cloud_dog_storage-0.1.4/tests/unit/UT1.13_WebDavRetryLogic/test_webdav_retry.py +56 -0
  99. cloud_dog_storage-0.1.4/tests/unit/UT1.14_FtpPathMapping/test_ftp_path_mapping.py +37 -0
  100. cloud_dog_storage-0.1.4/tests/unit/UT1.15_GoogleDriveFolderIdExtraction/test_gdrive_folder_id.py +42 -0
  101. cloud_dog_storage-0.1.4/tests/unit/UT1.16_GoogleDriveTokenRefresh/test_gdrive_token_refresh.py +79 -0
  102. cloud_dog_storage-0.1.4/tests/unit/UT1.17_StorageConfigModels/test_config_models.py +32 -0
  103. cloud_dog_storage-0.1.4/tests/unit/UT1.18_StorageConfigDefaults/test_config_defaults.py +35 -0
  104. cloud_dog_storage-0.1.4/tests/unit/UT1.19_FactoryDispatch/test_factory_dispatch.py +42 -0
  105. cloud_dog_storage-0.1.4/tests/unit/UT1.1_StorageBackendInterface/test_backend_interface.py +75 -0
  106. cloud_dog_storage-0.1.4/tests/unit/UT1.20_FactoryLazyImports/test_factory_lazy_imports.py +42 -0
  107. cloud_dog_storage-0.1.4/tests/unit/UT1.21_ErrorTaxonomy/test_error_taxonomy.py +53 -0
  108. cloud_dog_storage-0.1.4/tests/unit/UT1.22_PathSanitiser/test_path_sanitiser.py +46 -0
  109. cloud_dog_storage-0.1.4/tests/unit/UT1.23_PathTraversalPrevention/test_path_traversal.py +51 -0
  110. cloud_dog_storage-0.1.4/tests/unit/UT1.24_TlsConfigHelpers/test_tls_config.py +43 -0
  111. cloud_dog_storage-0.1.4/tests/unit/UT1.25_StorageEntryModel/test_storage_entry.py +36 -0
  112. cloud_dog_storage-0.1.4/tests/unit/UT1.26_StorageStatModel/test_storage_stat.py +36 -0
  113. cloud_dog_storage-0.1.4/tests/unit/UT1.27_StoredFileModel/test_stored_file.py +37 -0
  114. cloud_dog_storage-0.1.4/tests/unit/UT1.28_MockBackend/test_mock_backend.py +43 -0
  115. cloud_dog_storage-0.1.4/tests/unit/UT1.29_AsyncWrapper/test_async_wrapper.py +43 -0
  116. cloud_dog_storage-0.1.4/tests/unit/UT1.2_LocalStorageBasic/test_local_basic.py +49 -0
  117. cloud_dog_storage-0.1.4/tests/unit/UT1.30_ObservabilityLogging/test_observability_logging.py +42 -0
  118. cloud_dog_storage-0.1.4/tests/unit/UT1.31_NotSupportedErrorHandling/test_not_supported.py +55 -0
  119. cloud_dog_storage-0.1.4/tests/unit/UT1.32_CleanPosixHelper/test_clean_posix.py +30 -0
  120. cloud_dog_storage-0.1.4/tests/unit/UT1.3_LocalStorageReadWrite/test_local_read_write.py +42 -0
  121. cloud_dog_storage-0.1.4/tests/unit/UT1.4_LocalStorageListDir/test_local_list_dir.py +51 -0
  122. cloud_dog_storage-0.1.4/tests/unit/UT1.5_LocalStorageStat/test_local_stat.py +49 -0
  123. cloud_dog_storage-0.1.4/tests/unit/UT1.6_LocalStorageCopyMove/test_local_copy_move.py +46 -0
  124. cloud_dog_storage-0.1.4/tests/unit/UT1.7_LocalStorageChmod/test_local_chmod.py +36 -0
  125. cloud_dog_storage-0.1.4/tests/unit/UT1.8_LocalStorageCreateDir/test_local_create_dir.py +40 -0
  126. cloud_dog_storage-0.1.4/tests/unit/UT1.9_LocalStorageDiskSpace/test_local_disk_space.py +35 -0
  127. cloud_dog_storage-0.1.4/working/W28A-118B-PLATFORM-STORAGE-IT-REPAIR-REPORT.md +123 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.pyc
3
+
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+
12
+ .venv/
@@ -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
+ ```