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.
Add an admin page to configure the FP&A file store for a legal entity. The UI lets the admin select a file store type, enter backend-specific config, validate and test the connection on save, and re-test or remove existing configurations.
A new file-store-admin multimethod in com.getorcha.link.mcp.file-store
dispatches on the protocol string (same dispatch as make-file-store).
Each backend that wants admin UI support registers a method returning a
descriptor map:
(defmulti file-store-admin
"Returns admin descriptor for a file store type.
Only backends that register here appear in the admin UI."
identity)
Descriptor shape:
{: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"}]]]}
The admin discovers available types via
(keys (methods file-store/file-store-admin)). Adding a new backend =
register both make-file-store and file-store-admin, it appears
automatically.
The :form-schema is a flat Malli :map schema with :form/*
properties on each field. A generic renderer walks m/children,
dispatches on m/type, and emits hiccup form fields. Supported types:
:string — text input (or textarea via :form/type :textarea):int — number input:enum — select dropdown:boolean — checkboxThe renderer accepts optional values (pre-fill) and errors
(me/humanize output) maps.
Malli handles the full pipeline from a single schema:
m/decode with mt/string-transformer + mt/strip-extra-keys-transformer
— coerces string form params to typed values, strips extra keysm/explain + me/humanize — per-field error messages back to the formNo per-backend coercion function needed for now. The decoded map gets
{"protocol" protocol-key} merged in before storage.
After schema validation passes, the save handler:
{"protocol" ...} into the decoded configmake-file-store — catches construction errors (e.g., "not a directory")list-files on root — catches IO errorslegal_entity.fpna_data_sourceNamespace: com.getorcha.admin.http.tenants.file-store
Base path: /tenants/-/legal-entities/:le-id/file-store
| Method | Path | Purpose |
|---|---|---|
| GET | / |
Render page (empty form or pre-filled) |
| POST | / |
Save config (decode → validate → test → store) |
| POST | /test |
Test existing saved config on demand |
| DELETE | / |
Remove config (set fpna_data_source to NULL) |
<select> triggers hx-get with ?protocol=file
to swap in the matching form fieldsfile-store-admin methods)hx-confirm)Add a folder icon link on the legal entity row pointing to
/tenants/-/legal-entities/:le-id/file-store, next to the existing
prompts link.
The only backend for now. Registers both multimethods:
;; Already exists:
(defmethod file-store/make-file-store "file" [config] ...)
;; New:
(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"}]]]})
No schema changes needed. Uses existing legal_entity.fpna_data_source
JSONB column (added in migration 20260304215718).
Stored format: {"protocol": "file", "path": "/data/acme-gmbh/"}