Note (2026-04-24): After this document was written,
legal_entitywas renamed totenantand the oldtenantwas renamed toorganization. Read references to these terms with the pre-rename meaning.
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Admin UI to configure FP&A file store per legal entity, with connection testing on save.
Architecture: New file-store-admin multimethod for backend descriptors with Malli form schemas. Generic schema-to-form renderer emits hiccup from Malli schemas. HTMX-driven admin page at /tenants/-/legal-entities/:le-id/file-store with save (validates + tests connection), test existing, and remove actions.
Tech Stack: Clojure, Malli (schemas + coercion + validation), HTMX, Hiccup, Reitit, next-jdbc, HoneySQL
Design doc: docs/plans/2026-03-06-file-store-admin-design.md
file-store-admin MultimethodFiles:
src/com/getorcha/link/mcp/file_store.clj:34-42test/com/getorcha/link/mcp/file_store/local_test.cljStep 1: Write the failing test
Add to test/com/getorcha/link/mcp/file_store/local_test.clj:
(deftest test-file-store-admin-descriptor
(let [desc (file-store/file-store-admin "file")]
(testing "returns display name"
(is (= "Local Filesystem" (:display-name desc))))
(testing "returns a Malli map schema"
(is (= :map (m/type (:form-schema desc)))))
(testing "schema has :path field"
(let [children (m/children (:form-schema desc))
field-keys (set (map first children))]
(is (contains? field-keys :path))))))
Requires adding [malli.core :as m] to the test ns require.
Step 2: Run test to verify it fails
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp.file-store.local-test]'
Expected: FAIL — file-store-admin does not exist.
Step 3: Add the multimethod to file_store.clj
Add after make-file-store (after line 42):
(defmulti file-store-admin
"Returns an admin descriptor for a file store type.
Only backends that register a method here appear in the admin UI.
Dispatches on the protocol string (e.g., `\"file\"`, `\"s3\"`).
Returns a map:
- `:display-name` — human-readable label for the type selector
- `:form-schema` — flat Malli `:map` schema with `:form/*` properties"
identity)
Step 4: Register the local backend descriptor in local.clj
Add at the bottom of src/com/getorcha/link/mcp/file_store/local.clj:
(defmethod file-store/file-store-admin "file" [_]
{:display-name "Local Filesystem"
:form-schema [:map
[:path [:string {:min 1
:form/label "Directory Path"
:form/placeholder "/data/acme-gmbh/"
:error/message "Directory path is required"}]]]})
Step 5: Run test to verify it passes
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp.file-store.local-test]'
Expected: PASS
Step 6: Commit
git add src/com/getorcha/link/mcp/file_store.clj src/com/getorcha/link/mcp/file_store/local.clj test/com/getorcha/link/mcp/file_store/local_test.clj
git commit -m "feat: add file-store-admin multimethod with local backend descriptor"
A generic utility that renders hiccup form fields from a flat Malli :map schema. Not file-store-specific — reusable for any admin form.
Files:
src/com/getorcha/admin/ui/schema_form.cljtest/com/getorcha/admin/ui/schema_form_test.cljStep 1: Write the failing tests
Create test/com/getorcha/admin/ui/schema_form_test.clj:
(ns com.getorcha.admin.ui.schema-form-test
(:require [clojure.test :refer [deftest is testing]]
[com.getorcha.admin.ui.schema-form :as schema-form]))
(def test-schema
[:map
[:name [:string {:min 1
:form/label "Full Name"
:form/placeholder "John Doe"}]]
[:count [:int {:form/label "Count"
:min 0
:max 100}]]
[:role [:enum {:form/label "Role"}
"admin" "viewer"]]
[:active [:boolean {:form/label "Active?"}]]
[:notes {:optional true}
[:string {:form/label "Notes"
:form/type :textarea
:form/rows 4}]]])
(deftest test-render-fields-string
(let [fields (schema-form/render-fields test-schema {})]
(testing "renders a string field as text input"
(let [name-field (first (filter #(= "name" (get-in % [2 :name])) (rest fields)))]
(is (some? name-field))
;; Should contain a label
(is (some #(and (vector? %) (= :label (first %))) (rest name-field)))
;; Should contain an input
(is (some #(and (vector? %) (= :input (first %))) (rest name-field)))))))
(deftest test-render-fields-enum
(let [fields (schema-form/render-fields test-schema {})]
(testing "renders an enum field as select"
(let [role-field (first (filter #(= "role" (get-in % [2 :name])) (rest fields)))]
(is (some? role-field))
(is (some #(and (vector? %) (= :select (first %))) (rest role-field)))))))
(deftest test-render-fields-with-values
(let [fields (schema-form/render-fields test-schema {:values {:name "Alice" :count 42}})]
(testing "pre-fills values"
(let [name-field (first (filter #(= "name" (get-in % [2 :name])) (rest fields)))
input (first (filter #(and (vector? %) (= :input (first %))) (rest name-field)))]
(is (= "Alice" (get-in input [1 :value])))))))
(deftest test-render-fields-with-errors
(let [fields (schema-form/render-fields test-schema {:errors {:name ["is required"]}})]
(testing "renders error messages"
(let [name-field (first (filter #(= "name" (get-in % [2 :name])) (rest fields)))]
(is (some #(and (vector? %)
(= :div.field-errors (first %)))
(rest name-field)))))))
(deftest test-render-fields-textarea
(let [fields (schema-form/render-fields test-schema {})]
(testing "renders :form/type :textarea as textarea element"
(let [notes-field (first (filter #(= "notes" (get-in % [2 :name])) (rest fields)))]
(is (some #(and (vector? %) (= :textarea (first %))) (rest notes-field)))))))
(deftest test-render-fields-boolean
(let [fields (schema-form/render-fields test-schema {})]
(testing "renders boolean as checkbox"
(let [active-field (first (filter #(= "active" (get-in % [2 :name])) (rest fields)))]
(is (some? active-field))
(let [input (first (filter #(and (vector? %) (= :input (first %))) (rest active-field)))]
(is (= "checkbox" (get-in input [1 :type]))))))))
(deftest test-render-fields-int
(let [fields (schema-form/render-fields test-schema {})]
(testing "renders int as number input"
(let [count-field (first (filter #(= "count" (get-in % [2 :name])) (rest fields)))]
(is (some? count-field))
(let [input (first (filter #(and (vector? %) (= :input (first %))) (rest count-field)))]
(is (= "number" (get-in input [1 :type]))))))))
Step 2: Run tests to verify they fail
Run: clj -X:test:silent :nses '[com.getorcha.admin.ui.schema-form-test]'
Expected: FAIL — namespace does not exist.
Step 3: Implement the schema-to-form renderer
Create src/com/getorcha/admin/ui/schema_form.clj:
(ns com.getorcha.admin.ui.schema-form
"Renders hiccup form fields from a flat Malli :map schema.
Schema fields use `:form/*` properties for UI hints:
- `:form/label` — field label text
- `:form/placeholder` — input placeholder
- `:form/type` — override input type (e.g., `:textarea`, `:email`)
- `:form/rows` — textarea row count"
(:require [malli.core :as m]))
(defmulti ^:private render-field
"Renders a single form field based on its Malli type."
(fn [_key _entry-props schema _opts] (m/type schema)))
(defmethod render-field :default [key entry-props schema opts]
(let [props (m/properties schema)
label (or (:form/label props) (name key))
fname (name key)
ftype (or (some-> (:form/type props) name) "text")
value (get (:values opts) key "")]
[:div.form-group {:name fname}
[:label.form-label {:for fname} label]
[:input.form-input {:id fname
:name fname
:type ftype
:placeholder (:form/placeholder props)
:value value
:required (not (:optional entry-props))
:minlength (:min props)
:maxlength (:max props)}]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])]))
(defmethod render-field :string [key entry-props schema opts]
(let [props (m/properties schema)
label (or (:form/label props) (name key))
fname (name key)
value (get (:values opts) key "")]
(if (= :textarea (:form/type props))
[:div.form-group {:name fname}
[:label.form-label {:for fname} label]
[:textarea.form-input {:id fname
:name fname
:rows (or (:form/rows props) 3)
:placeholder (:form/placeholder props)
:required (not (:optional entry-props))}
value]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])]
[:div.form-group {:name fname}
[:label.form-label {:for fname} label]
[:input.form-input {:id fname
:name fname
:type (or (some-> (:form/type props) name) "text")
:placeholder (:form/placeholder props)
:value value
:required (not (:optional entry-props))
:minlength (:min props)
:maxlength (:max props)}]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])])))
(defmethod render-field :int [key entry-props schema opts]
(let [props (m/properties schema)
label (or (:form/label props) (name key))
fname (name key)
value (get (:values opts) key "")]
[:div.form-group {:name fname}
[:label.form-label {:for fname} label]
[:input.form-input {:id fname
:name fname
:type "number"
:value value
:min (:min props)
:max (:max props)
:required (not (:optional entry-props))}]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])]))
(defmethod render-field :enum [key entry-props schema opts]
(let [props (m/properties schema)
label (or (:form/label props) (name key))
fname (name key)
children (m/children schema)
value (get (:values opts) key)]
[:div.form-group {:name fname}
[:label.form-label {:for fname} label]
[:select.form-select {:id fname
:name fname
:required (not (:optional entry-props))}
[:option {:value ""} "Select..."]
(for [child children]
[:option {:value (str child)
:selected (= child value)}
(str child)])]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])]))
(defmethod render-field :boolean [key _entry-props schema opts]
(let [props (m/properties schema)
label (or (:form/label props) (name key))
fname (name key)
value (get (:values opts) key false)]
[:div.form-group {:name fname}
[:input.form-checkbox {:id fname
:name fname
:type "checkbox"
:checked value}]
[:label.form-label {:for fname} label]
(when-let [errors (get (:errors opts) key)]
[:div.field-errors
(for [msg errors]
[:span.field-error msg])])]))
(defn render-fields
"Renders hiccup form fields from a flat Malli `:map` schema.
`schema` — a Malli `:map` schema with `:form/*` properties on fields.
`opts` — a map:
- `:values` — map of field keyword → current value (for pre-filling)
- `:errors` — map of field keyword → seq of error strings (from `me/humanize`)
Returns a `:div.form-fields` containing one `:div.form-group` per field."
[schema opts]
(into [:div.form-fields]
(for [[key entry-props value-schema] (m/children schema)]
(render-field key entry-props value-schema opts))))
Step 4: Run tests to verify they pass
Run: clj -X:test:silent :nses '[com.getorcha.admin.ui.schema-form-test]'
Expected: PASS
Step 5: Commit
git add src/com/getorcha/admin/ui/schema_form.clj test/com/getorcha/admin/ui/schema_form_test.clj
git commit -m "feat: generic Malli schema-to-form renderer for admin UI"
Files:
src/com/getorcha/admin/http/tenants/file_store.cljsrc/com/getorcha/admin/http.clj:11,46 (add require + route registration)src/com/getorcha/admin/http/tenants.clj:377-380 (add link to legal entity row)Context:
db-pool is available on the request via inject-config middleware (see src/com/getorcha/http/middleware.clj:10-16)json/parse-string ... true in src/com/getorcha/db.clj:83-84[:cast (json/generate-string data) :jsonb] in HoneySQL (see pattern at test/com/getorcha/link/mcp_test.clj:178)src/com/getorcha/admin/http/tenants/prompt_customizations.cljlayout/render for full pages, layout/partial-content for HTMX partialsStep 1: Create the file store admin namespace
Create src/com/getorcha/admin/http/tenants/file_store.clj:
(ns com.getorcha.admin.http.tenants.file-store
"File store configuration admin routes and views.
Allows admins to configure, test, and remove FP&A file store
settings per legal entity."
(:require [cheshire.core :as json]
[com.getorcha.admin.ui.layout :as layout]
[com.getorcha.admin.ui.schema-form :as schema-form]
[com.getorcha.db.sql :as db.sql]
[com.getorcha.link.mcp.file-store :as file-store]
;; Load backends so their multimethods are registered
[com.getorcha.link.mcp.file-store.local]
[malli.core :as m]
[malli.error :as me]
[malli.transform :as mt]
[next.jdbc :as jdbc]
[ring.util.http-response :as ring.resp]))
;; Helpers
;; -----------------------------------------------------------------------------
(defn ^:private get-legal-entity
"Fetch a legal entity by ID (name + fpna_data_source)."
[db-pool legal-entity-id]
(jdbc/execute-one! db-pool
["SELECT id, name, fpna_data_source FROM legal_entity WHERE id = ?"
legal-entity-id]))
(defn ^:private available-backends
"Returns a seq of [protocol-key descriptor] for all registered file store admin backends."
[]
(for [protocol-key (keys (methods file-store/file-store-admin))]
[protocol-key (file-store/file-store-admin protocol-key)]))
(defn ^:private save-data-source!
"Persists a file store config as JSONB on the legal entity."
[db-pool legal-entity-id config]
(db.sql/execute-one!
db-pool
{:update :legal-entity
:set {:fpna-data-source [:cast (json/generate-string config) :jsonb]}
:where [:= :id legal-entity-id]}))
(defn ^:private clear-data-source!
"Removes the file store config from a legal entity."
[db-pool legal-entity-id]
(db.sql/execute-one!
db-pool
{:update :legal-entity
:set {:fpna-data-source nil}
:where [:= :id legal-entity-id]}))
(defn ^:private test-file-store
"Attempts to construct a FileStore and list root. Returns nil on success,
or an error message string on failure."
[config]
(try
(let [store (file-store/make-file-store config)]
(file-store/list-files store "" {})
nil)
(catch Exception e
(str "Connection test failed: " (.getMessage e)))))
(def ^:private form-transformer
(mt/transformer
(mt/strip-extra-keys-transformer {:accept (constantly true)})
mt/string-transformer))
(defn ^:private decode-and-validate
"Decodes form params with the given Malli schema and validates.
Returns {:ok decoded-data} or {:errors humanized-errors}."
[schema params]
(let [decoded (m/decode schema params form-transformer)
explain (m/explain schema decoded)]
(if explain
{:errors (me/humanize explain)}
{:ok decoded})))
;; Views
;; -----------------------------------------------------------------------------
(defn ^:private status-message
"Renders a success or error status message."
[type message]
[:div {:id "file-store-status" :class (str "alert " (case type :success "alert-success" "alert-danger"))}
message])
(defn ^:private render-form-fields
"Renders the backend-specific form fields for a protocol type."
[protocol-key {:keys [values errors]}]
(when-let [desc (file-store/file-store-admin protocol-key)]
[:div#file-store-form-fields
[:input {:type "hidden" :name "protocol" :value protocol-key}]
(schema-form/render-fields (:form-schema desc) {:values values :errors errors})]))
(defn ^:private render-page
"Renders the file store configuration page."
[legal-entity data-source & [{:keys [values errors status] :as _opts}]]
(let [{:legal-entity/keys [id name]} legal-entity
backends (available-backends)
protocol (:protocol data-source)
configured (some? data-source)]
[:div.page-content
[:div.page-header
[:a.btn.btn-ghost {:href "/tenants"} "← Back to tenants"]
[:h1 (str name " — File Store Configuration")]]
[:div#file-store-content
(when status
(status-message (:type status) (:message status)))
(if configured
;; Configured state: show filled form
[:div
[:div.form-group
[:label.form-label "Type"]
[:div.form-value
(:display-name (file-store/file-store-admin protocol))]]
[:form {:hx-post (str "/tenants/-/legal-entities/" id "/file-store")
:hx-target "#file-store-content"
:hx-swap "innerHTML"}
(render-form-fields protocol {:values (or values (dissoc data-source :protocol))
:errors errors})
[:div.form-actions
[:button.btn.btn-primary {:type "submit"} "Save"]
[:button.btn.btn-secondary {:type "button"
:hx-post (str "/tenants/-/legal-entities/" id "/file-store/test")
:hx-target "#file-store-status"
:hx-swap "outerHTML"}
"Test Connection"]
[:button.btn.btn-danger {:type "button"
:hx-delete (str "/tenants/-/legal-entities/" id "/file-store")
:hx-target "#file-store-content"
:hx-swap "innerHTML"
:hx-confirm "Remove file store configuration?"}
"Remove"]]]
[:div#file-store-status]]
;; Unconfigured state: show type selector + empty form
[:div
[:form {:hx-post (str "/tenants/-/legal-entities/" id "/file-store")
:hx-target "#file-store-content"
:hx-swap "innerHTML"}
[:div.form-group
[:label.form-label {:for "protocol"} "File Store Type"]
[:select.form-select {:id "protocol"
:name "protocol"
:hx-get (str "/tenants/-/legal-entities/" id "/file-store")
:hx-target "#file-store-form-fields"
:hx-swap "outerHTML"
:hx-include "this"
:required true}
[:option {:value ""} "Select a file store type..."]
(for [[proto-key desc] backends]
[:option {:value proto-key} (:display-name desc)])]]
[:div#file-store-form-fields
(when-let [selected-protocol (or (:protocol values) (when (= 1 (count backends))
(ffirst backends)))]
(render-form-fields selected-protocol {:values values :errors errors}))]
[:div.form-actions
[:button.btn.btn-primary {:type "submit"} "Save"]]]]))]))
;; Route Handlers
;; -----------------------------------------------------------------------------
(defn ^:private index
"GET - Show file store config page."
[{:keys [db-pool parameters] :as request} respond _raise]
(let [legal-entity-id (get-in parameters [:path :id])
protocol-param (get-in parameters [:query :protocol])]
(if-let [legal-entity (get-legal-entity db-pool legal-entity-id)]
(let [data-source (:legal-entity/fpna-data-source legal-entity)]
(if protocol-param
;; HTMX request: return just the form fields for selected protocol
(respond
(ring.resp/ok
(layout/partial-content
(or (render-form-fields protocol-param {})
[:div#file-store-form-fields]))))
;; Full page request
(respond
(ring.resp/ok
(layout/render request
{:title "File Store Configuration" :current-path "/tenants"}
(render-page legal-entity data-source))))))
(respond
(ring.resp/not-found
(layout/partial-content
[:div.alert.alert-danger "Legal entity not found."]))))))
(defn ^:private save!
"POST - Save file store config (decode, validate, test connection, store)."
[{:keys [db-pool parameters] :as _request} respond _raise]
(let [legal-entity-id (get-in parameters [:path :id])
protocol-key (get-in parameters [:form :protocol])]
(if-let [legal-entity (get-legal-entity db-pool legal-entity-id)]
(if-let [desc (when (seq protocol-key) (file-store/file-store-admin protocol-key))]
(let [{:keys [ok errors]} (decode-and-validate (:form-schema desc)
(get-in parameters [:form]))]
(if errors
;; Validation failed — re-render with errors
(let [data-source (:legal-entity/fpna-data-source legal-entity)]
(respond
(ring.resp/ok
(layout/partial-content
(render-page legal-entity data-source
{:values (get-in parameters [:form])
:errors errors})))))
;; Validation passed — test connection
(let [config (assoc ok :protocol protocol-key)
test-error (test-file-store config)]
(if test-error
;; Connection test failed
(let [data-source (:legal-entity/fpna-data-source legal-entity)]
(respond
(ring.resp/ok
(layout/partial-content
(render-page legal-entity data-source
{:values ok
:status {:type :error :message test-error}})))))
;; All good — save
(do
(save-data-source! db-pool legal-entity-id config)
(respond
(ring.resp/ok
(layout/partial-content
(render-page legal-entity config
{:status {:type :success
:message "File store configured and connection verified."}})))))))))
;; Invalid protocol
(respond
(ring.resp/ok
(layout/partial-content
(render-page legal-entity nil
{:status {:type :error :message "Please select a file store type."}})))))
(respond
(ring.resp/not-found
(layout/partial-content
[:div.alert.alert-danger "Legal entity not found."]))))))
(defn ^:private test-connection!
"POST /test - Test an existing saved file store config."
[{:keys [db-pool parameters]} respond _raise]
(let [legal-entity-id (get-in parameters [:path :id])
legal-entity (get-legal-entity db-pool legal-entity-id)
data-source (:legal-entity/fpna-data-source legal-entity)]
(respond
(ring.resp/ok
(layout/partial-content
(if-not data-source
(status-message :error "No file store configured.")
(if-let [error (test-file-store data-source)]
(status-message :error error)
(status-message :success "Connection successful."))))))))
(defn ^:private remove!
"DELETE - Remove file store config."
[{:keys [db-pool parameters]} respond _raise]
(let [legal-entity-id (get-in parameters [:path :id])
legal-entity (get-legal-entity db-pool legal-entity-id)]
(clear-data-source! db-pool legal-entity-id)
(respond
(ring.resp/ok
(layout/partial-content
(render-page legal-entity nil
{:status {:type :success :message "File store configuration removed."}}))))))
(defn routes
"File store configuration routes."
[_config]
[["/tenants/-/legal-entities/:id/file-store"
[""
{:name ::index
:get {:parameters {:path {:id :uuid}
:query [:map
[:protocol {:optional true} :string]]}
:handler #'index}
:post {:parameters {:path {:id :uuid}
:form [:map
[:protocol :string]]}
:handler #'save!}
:delete {:parameters {:path {:id :uuid}}
:handler #'remove!}}]
["/test"
{:name ::test-connection
:post {:parameters {:path {:id :uuid}}
:handler #'test-connection!}}]]])
Step 2: Register routes in admin HTTP router
Modify src/com/getorcha/admin/http.clj:
Add to requires (after line 11, alphabetically):
[com.getorcha.admin.http.tenants.file-store :as admin.http.tenants.file-store]
Add route registration (after line 46, next to prompt-customizations):
(admin.http.tenants.file-store/routes config)
Step 3: Add link in legal entity row
Modify src/com/getorcha/admin/http/tenants.clj:377-380.
Add a file-store link before the prompts link. After line 377 ([:td.actions), insert:
[:a.btn-icon {:href (str "/tenants/-/legal-entities/" id "/file-store")
:title "Configure file store"}
[:iconify-icon {:icon "lucide:folder-cog"}]]
So the actions cell becomes:
[:td.actions
[:a.btn-icon {:href (str "/tenants/-/legal-entities/" id "/file-store")
:title "Configure file store"}
[:iconify-icon {:icon "lucide:folder-cog"}]]
[:a.btn-icon {:href (str "/tenants/-/legal-entities/" id "/prompts")
:title "Customize prompts"}
[:iconify-icon {:icon "lucide:message-square-text"}]]
[:button.btn-icon {:type "button"
:title "Edit legal entity"
:hx-get (str "/tenants/-/legal-entities/" id)
:hx-target "#modal"
:hx-swap "innerHTML"}
[:iconify-icon {:icon "lucide:pencil"}]]]
Step 4: Verify linting passes
Run: clj-kondo --lint src/com/getorcha/admin/http/tenants/file_store.clj src/com/getorcha/admin/http.clj
Fix any issues.
Step 5: Commit
git add src/com/getorcha/admin/http/tenants/file_store.clj src/com/getorcha/admin/http.clj src/com/getorcha/admin/http/tenants.clj
git commit -m "feat: file store admin page with save, test, and remove"
Step 1: Start the system
Evaluate (reset) in the REPL (allow ~10s for worker pool shutdown).
Step 2: Navigate to admin
Open http://localhost:3001/tenants (or whatever the admin port is). Find a legal entity row — confirm the new folder icon link appears.
Step 3: Test unconfigured state
Click the file store icon for a legal entity that has no fpna_data_source. Verify:
Step 4: Test save with invalid path
Enter a non-existent path like /tmp/does-not-exist-xyz and click Save. Verify:
Step 5: Test save with valid path
Create a test directory: mkdir -p /tmp/orcha-test-fs
Enter /tmp/orcha-test-fs and click Save. Verify:
Step 6: Test connection button
Click "Test Connection". Verify success message appears.
Step 7: Test remove
Click "Remove", confirm the dialog. Verify:
Step 8: Verify DB
psql -h localhost -U postgres -d orcha -c "SELECT id, name, fpna_data_source FROM legal_entity LIMIT 5"
Confirm the column is NULL after removal.
Step 9: Commit any fixes from manual testing
If any fixes were needed, commit them:
git add <fixed-files>
git commit -m "fix: file store admin adjustments from manual testing"