@fleetbase/solid-engine 0.0.5 → 0.0.7

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.
@@ -5,8 +5,8 @@ export default {
5
5
  const menuService = universe.getService('menu');
6
6
 
7
7
  // Register menu item in header
8
- // const iconOptions = { iconComponent: new ExtensionComponent('@fleetbase/solid-engine', 'solid-brand-icon'), iconComponentOptions: { width: 19, height: 19 } };
9
- menuService.registerHeaderMenuItem('Solid', 'console.solid-protocol', { priority: 5 });
8
+ const iconOptions = { iconComponent: new ExtensionComponent('@fleetbase/solid-engine', 'solid-brand-icon'), iconComponentOptions: { width: 19, height: 19 } };
9
+ menuService.registerHeaderMenuItem('Solid', 'console.solid-protocol', { ...iconOptions, priority: 5 });
10
10
 
11
11
  // Register admin settings -- create a solid server menu panel with it's own setting options
12
12
  universe.registerAdminMenuPanel(
@@ -58,35 +58,6 @@
58
58
  </div>
59
59
  </ContentPanel>
60
60
 
61
- <ContentPanel @title="Quick Actions" @open={{true}} @wrapperClass="bordered-classic">
62
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
63
- <div class="text-center p-6 bg-gray-50 dark:bg-gray-700 rounded-lg">
64
- <FaIcon @icon="folder-tree" @size="2x" class="text-blue-600 dark:text-blue-400 mb-3" />
65
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Browse Data</h3>
66
- <p class="text-gray-600 dark:text-gray-400 mb-4">Explore and manage your Fleetops data in Solid</p>
67
- <div class="flex items-center justify-center">
68
- <Button @text="Browse Data" @icon="database" @type="primary" class="w-full" @onClick={{perform this.navigateToPods}} />
69
- </div>
70
- </div>
71
- <div class="text-center p-6 bg-gray-50 dark:bg-gray-700 rounded-lg">
72
- <FaIcon @icon="user" @size="2x" class="text-green-600 dark:text-green-400 mb-3" />
73
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Account Settings</h3>
74
- <p class="text-gray-600 dark:text-gray-400 mb-4">Manage your account and profile settings</p>
75
- <div class="flex items-center justify-center">
76
- <Button @text="Account" @icon="user" @type="secondary" class="w-full" @onClick={{perform this.navigateToAccount}} />
77
- </div>
78
- </div>
79
- <div class="text-center p-6 bg-gray-50 dark:bg-gray-700 rounded-lg">
80
- <FaIcon @icon="sync" @size="2x" class="text-purple-600 dark:text-purple-400 mb-3" />
81
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Sync Data</h3>
82
- <p class="text-gray-600 dark:text-gray-400 mb-4">Sync your Fleetbase data to Solid storage</p>
83
- <div class="flex items-center justify-center">
84
- <Button @text="Coming Soon" @icon="sync" @disabled={{true}} />
85
- </div>
86
- </div>
87
- </div>
88
- </ContentPanel>
89
-
90
61
  <ContentPanel @title="Connection Status" @open={{true}} @wrapperClass="bordered-classic">
91
62
  <div class="space-y-3">
92
63
  <div class="flex items-center">
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/solid-api",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Solid Protocol Extension to Store and Share Data with Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "Solid",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Solid Protocol Extension to Store and Share Data with Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/solid",
6
6
  "license": "AGPL-3.0-or-later",
7
- "author": "Fleetbase Pte Ltd <hello@fleetbase.io>"
7
+ "author": "Fleetbase Pte Ltd <hello@fleetbase.io>",
8
+ "engine": "package.json",
9
+ "api": "composer.json"
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/solid-engine",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Solid Protocol Extension to Store and Share Data with Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "solid-protocol"
@@ -45,11 +45,12 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@babel/core": "^7.23.2",
48
- "@fleetbase/ember-core": "^0.3.9",
49
- "@fleetbase/ember-ui": "^0.3.15",
50
- "@fleetbase/fleetops-data": "^0.1.24",
48
+ "@fleetbase/ember-core": "^0.3.10",
49
+ "@fleetbase/ember-ui": "^0.3.17",
50
+ "@fleetbase/fleetops-data": "^0.1.25",
51
51
  "@fortawesome/ember-fontawesome": "^2.0.0",
52
52
  "@fortawesome/fontawesome-svg-core": "6.4.0",
53
+ "@fortawesome/free-brands-svg-icons": "6.4.0",
53
54
  "@fortawesome/free-solid-svg-icons": "6.4.0",
54
55
  "broccoli-funnel": "^3.0.8",
55
56
  "ember-auto-import": "^2.7.4",
@@ -138,6 +138,35 @@ class DataController extends BaseController
138
138
  'parent_url' => $parentUrl,
139
139
  ]);
140
140
 
141
+ // Check if parent URL is writable before attempting folder creation
142
+ $aclService = app(\Fleetbase\Solid\Services\AclService::class);
143
+
144
+ if (!$aclService->isWritable($identity, $parentUrl)) {
145
+ Log::warning('[FOLDER CREATE] Location not writable', [
146
+ 'parent_url' => $parentUrl,
147
+ 'webid' => $webId,
148
+ ]);
149
+
150
+ // Find writable locations
151
+ $writableLocations = $aclService->findWritableLocations($identity, $profile);
152
+
153
+ if (!empty($writableLocations)) {
154
+ $suggestion = array_values($writableLocations)[0];
155
+ return response()->json([
156
+ 'success' => false,
157
+ 'error' => 'Cannot create folder at specified location. You do not have write permissions.',
158
+ 'suggestion' => "Try creating the folder at: {$suggestion}",
159
+ 'writable_locations' => $writableLocations,
160
+ ], 403);
161
+ } else {
162
+ return response()->json([
163
+ 'success' => false,
164
+ 'error' => 'No writable locations found in your pod.',
165
+ 'help' => 'You may need to configure ACL permissions. See: https://docs.solidproject.org/managing-permissions',
166
+ ], 403);
167
+ }
168
+ }
169
+
141
170
  // Use POST with Slug header (Solid Protocol standard)
142
171
  $result = $this->podService->createFolder($identity, $parentUrl, $folderName);
143
172
 
@@ -237,6 +266,34 @@ class DataController extends BaseController
237
266
  'resource_types' => $resourceTypes,
238
267
  ]);
239
268
 
269
+ // Check if pod URL is writable before importing
270
+ $aclService = app(\Fleetbase\Solid\Services\AclService::class);
271
+
272
+ if (!$aclService->isWritable($identity, $podUrl)) {
273
+ Log::warning('[IMPORT RESOURCES] Pod root not writable', [
274
+ 'pod_url' => $podUrl,
275
+ 'webid' => $webId,
276
+ ]);
277
+
278
+ // Find writable locations
279
+ $writableLocations = $aclService->findWritableLocations($identity, $profile);
280
+
281
+ if (!empty($writableLocations)) {
282
+ return response()->json([
283
+ 'success' => false,
284
+ 'error' => 'Cannot import resources to pod root. You do not have write permissions.',
285
+ 'writable_locations' => $writableLocations,
286
+ 'help' => 'Resources can only be imported to writable locations.',
287
+ ], 403);
288
+ } else {
289
+ return response()->json([
290
+ 'success' => false,
291
+ 'error' => 'No writable locations found in your pod.',
292
+ 'help' => 'You may need to configure ACL permissions. See: https://docs.solidproject.org/managing-permissions',
293
+ ], 403);
294
+ }
295
+ }
296
+
240
297
  $result = $this->resourceSyncService->importResources($identity, $podUrl, $resourceTypes);
241
298
 
242
299
  return response()->json([
@@ -143,4 +143,263 @@ TURTLE;
143
143
  Log::info('[ACL NEEDS UPDATE]', ['pod_url' => $podUrl]);
144
144
  return $this->grantWritePermissions($identity, $podUrl, $webId);
145
145
  }
146
+
147
+ /**
148
+ * Ensure a folder has proper ACL permissions after creation.
149
+ *
150
+ * @param SolidIdentity $identity
151
+ * @param string $folderUrl The folder URL (must end with /)
152
+ * @param string $webId The WebID to grant permissions to
153
+ * @return bool
154
+ */
155
+ public function ensureFolderPermissions(SolidIdentity $identity, string $folderUrl, string $webId): bool
156
+ {
157
+ try {
158
+ // Ensure folder URL ends with /
159
+ $folderUrl = rtrim($folderUrl, '/') . '/';
160
+ $aclUrl = $folderUrl . '.acl';
161
+
162
+ Log::info('[ACL] Ensuring folder permissions', [
163
+ 'folder_url' => $folderUrl,
164
+ 'acl_url' => $aclUrl,
165
+ 'webid' => $webId,
166
+ ]);
167
+
168
+ // Check if ACL already exists and has write permissions
169
+ if ($this->hasFolderWritePermissions($identity, $folderUrl, $webId)) {
170
+ Log::info('[ACL] Folder already has write permissions', ['folder_url' => $folderUrl]);
171
+ return true;
172
+ }
173
+
174
+ // Create ACL with full permissions for the owner
175
+ $aclContent = $this->generateFolderAcl($folderUrl, $webId);
176
+
177
+ return $this->createFolderAcl($identity, $aclUrl, $aclContent);
178
+ } catch (\Throwable $e) {
179
+ Log::error('[ACL] Failed to ensure folder permissions', [
180
+ 'folder_url' => $folderUrl,
181
+ 'error' => $e->getMessage(),
182
+ ]);
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Check if a folder has write permissions.
189
+ *
190
+ * @param SolidIdentity $identity
191
+ * @param string $folderUrl
192
+ * @param string $webId
193
+ * @return bool
194
+ */
195
+ protected function hasFolderWritePermissions(SolidIdentity $identity, string $folderUrl, string $webId): bool
196
+ {
197
+ try {
198
+ $response = $identity->request('head', $folderUrl);
199
+
200
+ if (!$response->successful()) {
201
+ return false;
202
+ }
203
+
204
+ // Check WAC-Allow header
205
+ $wacAllow = $response->header('WAC-Allow');
206
+
207
+ if ($wacAllow) {
208
+ Log::debug('[ACL] WAC-Allow header', [
209
+ 'folder_url' => $folderUrl,
210
+ 'wac_allow' => $wacAllow,
211
+ ]);
212
+
213
+ // Parse WAC-Allow header: user="read write", public="read"
214
+ if (preg_match('/user="([^"]*)"/i', $wacAllow, $matches)) {
215
+ $userModes = strtolower($matches[1]);
216
+ return str_contains($userModes, 'write') || str_contains($userModes, 'append');
217
+ }
218
+ }
219
+
220
+ return false;
221
+ } catch (\Throwable $e) {
222
+ Log::debug('[ACL] Error checking folder permissions', [
223
+ 'folder_url' => $folderUrl,
224
+ 'error' => $e->getMessage(),
225
+ ]);
226
+ return false;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Create an ACL file for a folder.
232
+ *
233
+ * @param SolidIdentity $identity
234
+ * @param string $aclUrl
235
+ * @param string $aclContent
236
+ * @return bool
237
+ */
238
+ protected function createFolderAcl(SolidIdentity $identity, string $aclUrl, string $aclContent): bool
239
+ {
240
+ try {
241
+ $response = $identity->request('put', $aclUrl, $aclContent, [
242
+ 'headers' => [
243
+ 'Content-Type' => 'text/turtle',
244
+ ],
245
+ ]);
246
+
247
+ if ($response->successful()) {
248
+ Log::info('[ACL] Folder ACL created successfully', [
249
+ 'acl_url' => $aclUrl,
250
+ 'status' => $response->status(),
251
+ ]);
252
+ return true;
253
+ }
254
+
255
+ Log::error('[ACL] Failed to create folder ACL', [
256
+ 'acl_url' => $aclUrl,
257
+ 'status' => $response->status(),
258
+ 'body' => $response->body(),
259
+ ]);
260
+
261
+ return false;
262
+ } catch (\Throwable $e) {
263
+ Log::error('[ACL] Error creating folder ACL', [
264
+ 'acl_url' => $aclUrl,
265
+ 'error' => $e->getMessage(),
266
+ ]);
267
+ return false;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Generate ACL content for a folder with full owner permissions.
273
+ *
274
+ * @param string $folderUrl The folder URL
275
+ * @param string $webId The WebID to grant permissions to
276
+ * @return string
277
+ */
278
+ protected function generateFolderAcl(string $folderUrl, string $webId): string
279
+ {
280
+ return <<<TURTLE
281
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
282
+
283
+ <#owner>
284
+ a acl:Authorization;
285
+ acl:agent <{$webId}>;
286
+ acl:accessTo <./>;
287
+ acl:default <./>;
288
+ acl:mode acl:Read, acl:Write, acl:Control.
289
+ TURTLE;
290
+ }
291
+
292
+ /**
293
+ * Check if a specific URL is writable (has write or append permissions).
294
+ *
295
+ * @param SolidIdentity $identity
296
+ * @param string $url
297
+ * @return bool
298
+ */
299
+ public function isWritable(SolidIdentity $identity, string $url): bool
300
+ {
301
+ try {
302
+ $response = $identity->request('head', $url);
303
+
304
+ if (!$response->successful()) {
305
+ return false;
306
+ }
307
+
308
+ $wacAllow = $response->header('WAC-Allow');
309
+
310
+ if ($wacAllow && preg_match('/user="([^"]*)"/i', $wacAllow, $matches)) {
311
+ $modes = strtolower($matches[1]);
312
+ $isWritable = str_contains($modes, 'write') || str_contains($modes, 'append');
313
+
314
+ Log::debug('[ACL] Writable check', [
315
+ 'url' => $url,
316
+ 'wac_allow' => $wacAllow,
317
+ 'is_writable' => $isWritable,
318
+ ]);
319
+
320
+ return $isWritable;
321
+ }
322
+
323
+ return false;
324
+ } catch (\Throwable $e) {
325
+ Log::debug('[ACL] Writable check failed', [
326
+ 'url' => $url,
327
+ 'error' => $e->getMessage(),
328
+ ]);
329
+ return false;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Find writable storage locations from user profile.
335
+ *
336
+ * @param SolidIdentity $identity
337
+ * @param array $profile
338
+ * @return array
339
+ */
340
+ public function findWritableLocations(SolidIdentity $identity, array $profile): array
341
+ {
342
+ $writableLocations = [];
343
+ $webId = $profile['webid'] ?? null;
344
+
345
+ if (!$webId) {
346
+ return $writableLocations;
347
+ }
348
+
349
+ // Get pod URL from WebID
350
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
351
+
352
+ // Check common storage locations
353
+ $commonLocations = [
354
+ 'public' => rtrim($podUrl, '/') . '/public/',
355
+ 'private' => rtrim($podUrl, '/') . '/private/',
356
+ 'inbox' => rtrim($podUrl, '/') . '/inbox/',
357
+ ];
358
+
359
+ foreach ($commonLocations as $name => $url) {
360
+ if ($this->isWritable($identity, $url)) {
361
+ $writableLocations[$name] = $url;
362
+ }
363
+ }
364
+
365
+ // Check storage locations from profile
366
+ foreach ($profile['storage_locations'] ?? [] as $storage) {
367
+ $storageUrl = $this->resolveStorageUrl($storage, $webId, $podUrl);
368
+ if ($storageUrl && $this->isWritable($identity, $storageUrl)) {
369
+ $writableLocations['storage_' . count($writableLocations)] = $storageUrl;
370
+ }
371
+ }
372
+
373
+ Log::info('[ACL] Found writable locations', [
374
+ 'count' => count($writableLocations),
375
+ 'locations' => $writableLocations,
376
+ ]);
377
+
378
+ return $writableLocations;
379
+ }
380
+
381
+ /**
382
+ * Resolve a storage URL from profile data.
383
+ *
384
+ * @param string $storage
385
+ * @param string $webId
386
+ * @param string $podUrl
387
+ * @return string|null
388
+ */
389
+ protected function resolveStorageUrl(string $storage, string $webId, string $podUrl): ?string
390
+ {
391
+ // Handle relative URLs
392
+ if ($storage === '../' || $storage === './') {
393
+ return $podUrl;
394
+ }
395
+
396
+ // Handle absolute URLs
397
+ if (str_starts_with($storage, 'http://') || str_starts_with($storage, 'https://')) {
398
+ return $storage;
399
+ }
400
+
401
+ // Handle relative paths
402
+ $webIdBase = dirname($webId);
403
+ return rtrim($webIdBase, '/') . '/' . ltrim($storage, '/');
404
+ }
146
405
  }
@@ -712,20 +712,58 @@ class PodService
712
712
  {
713
713
  $items = [];
714
714
 
715
- // Parse contained resources
715
+ // Parse contained resources with ldp:contains
716
716
  if (preg_match_all('/ldp:contains\s+<([^>]+)>/', $content, $matches)) {
717
717
  foreach ($matches[1] as $resourceUrl) {
718
- $items[] = [
718
+ $item = [
719
719
  'url' => $resourceUrl,
720
720
  'name' => $this->extractPodName($resourceUrl),
721
721
  'type' => substr($resourceUrl, -1) === '/' ? 'container' : 'resource',
722
722
  ];
723
+
724
+ // Try to extract additional metadata for this resource
725
+ $item = array_merge($item, $this->extractResourceMetadata($content, $resourceUrl));
726
+
727
+ $items[] = $item;
723
728
  }
724
729
  }
725
730
 
726
731
  return $items;
727
732
  }
728
733
 
734
+ /**
735
+ * Extract metadata for a specific resource from Turtle content.
736
+ */
737
+ private function extractResourceMetadata(string $content, string $resourceUrl): array
738
+ {
739
+ $metadata = [];
740
+
741
+ // Escape special regex characters in URL
742
+ $escapedUrl = preg_quote($resourceUrl, '/');
743
+
744
+ // Extract resource type (e.g., ldp:BasicContainer, foaf:Document)
745
+ if (preg_match('/<' . $escapedUrl . '>\s+a\s+([^;\s]+)/', $content, $matches)) {
746
+ $metadata['rdf_type'] = trim($matches[1]);
747
+ }
748
+
749
+ // Extract dc:title
750
+ if (preg_match('/<' . $escapedUrl . '>.*?dc:title\s+"([^"]+)"/', $content, $matches)) {
751
+ $metadata['title'] = $matches[1];
752
+ }
753
+
754
+ // Extract dc:modified or posix:mtime
755
+ if (preg_match('/<' . $escapedUrl . '>.*?(?:dc:modified|posix:mtime)\s+(\d+)/', $content, $matches)) {
756
+ $metadata['modified'] = (int)$matches[1];
757
+ }
758
+
759
+ // Extract posix:size
760
+ if (preg_match('/<' . $escapedUrl . '>.*?posix:size\s+(\d+)/', $content, $matches)) {
761
+ $metadata['size'] = (int)$matches[1];
762
+ }
763
+
764
+ return $metadata;
765
+ }
766
+
729
767
  /**
730
768
  * Generate pod metadata in Turtle format.
731
769
  */
@@ -804,6 +842,15 @@ class PodService
804
842
  'folder_url' => $createdUrl,
805
843
  'status' => $response->status(),
806
844
  ]);
845
+
846
+ // Ensure the folder has proper ACL permissions
847
+ $aclService = app(AclService::class);
848
+ $webId = $identity->webid;
849
+
850
+ if ($webId) {
851
+ $aclService->ensureFolderPermissions($identity, $createdUrl, $webId);
852
+ }
853
+
807
854
  return true;
808
855
  }
809
856
 
@@ -299,6 +299,14 @@ class ResourceSyncService
299
299
  'url' => $containerUrl,
300
300
  'status' => $response->status(),
301
301
  ]);
302
+
303
+ // Ensure the container has proper ACL permissions
304
+ $aclService = app(AclService::class);
305
+ $webId = $identity->webid;
306
+
307
+ if ($webId) {
308
+ $aclService->ensureFolderPermissions($identity, $containerUrl, $webId);
309
+ }
302
310
  } catch (\Throwable $e) {
303
311
  // Container might already exist, that's okay
304
312
  Log::debug('[CONTAINER CREATION SKIPPED]', [
package/ACL_SOLUTION.md DELETED
@@ -1,72 +0,0 @@
1
- # THE REAL ROOT CAUSE - ACL Permissions
2
-
3
- ## Summary
4
-
5
- The 401 errors are NOT due to authentication or token issues. **The root ACL of each pod does not grant write permissions.**
6
-
7
- ## Key Facts
8
-
9
- 1. **CSS default root ACL grants only `acl:Read`** to the authenticated user
10
- 2. **Without `acl:Write` or `acl:Append` in the root ACL**, all write operations fail
11
- 3. **WAC-Allow header shows `user="read"`** - confirming no write permissions
12
- 4. **CSS has no built-in UI** for managing ACLs across pods
13
- 5. **Must update ACL programmatically** before first write operation
14
-
15
- ## The Solution
16
-
17
- Before creating any folders or resources, **update the pod's root ACL** to grant write permissions.
18
-
19
- ### Workflow
20
-
21
- 1. User logs in via OIDC (get access token + DPoP key)
22
- 2. Determine pod root URL (e.g., `http://solid:3000/test/`)
23
- 3. **Check if ACL grants write access** by inspecting WAC-Allow header
24
- 4. **If no write access, update the root ACL** at `<podRoot>/.acl`
25
- 5. After ACL update, create containers/resources
26
-
27
- ### ACL Document Format
28
-
29
- ```turtle
30
- @prefix acl: <http://www.w3.org/ns/auth/acl#>.
31
-
32
- # Full rights for the pod owner (required)
33
- <#owner>
34
- a acl:Authorization;
35
- acl:agent <https://solid.example/user#me>;
36
- acl:accessTo <https://solid.example/userpod/>;
37
- acl:default <https://solid.example/userpod/>;
38
- acl:mode acl:Read, acl:Write, acl:Control.
39
-
40
- # Append and read rights for the Fleetbase integration
41
- <#fleetbase>
42
- a acl:Authorization;
43
- acl:agent <https://fleetbase.com/agent#me>;
44
- acl:accessTo <https://solid.example/userpod/>;
45
- acl:default <https://solid.example/userpod/>;
46
- acl:mode acl:Append, acl:Read.
47
- ```
48
-
49
- ### Implementation Steps
50
-
51
- 1. **GET** `<podRoot>` to check WAC-Allow header
52
- 2. If lacks `append`/`write`, prepare ACL Turtle document
53
- 3. **PUT** `<podRoot>/.acl` with DPoP authentication
54
- 4. After successful ACL update, proceed with folder creation
55
-
56
- ## Why This Matters
57
-
58
- - **acl:agent** identifies who gets the permissions (use WebID)
59
- - **acl:accessTo** applies to the pod root
60
- - **acl:default** inherits to all descendants
61
- - **acl:Append** allows creating resources
62
- - **acl:Write** allows updating existing ones
63
-
64
- ## For Fleetbase Integration
65
-
66
- The integration should:
67
- 1. Check ACL permissions on first access
68
- 2. Prompt user or automatically update ACL
69
- 3. Use `acl:Append` mode (safer than full Write)
70
- 4. Store ACL update status to avoid repeated checks
71
-
72
- This is **not a bug in our code** - it's the expected CSS behavior. Every pod needs explicit ACL configuration for write access.