The first Foundry posts showed a narrow but important thing: a real FOLIO Licenses application running outside Okapi, loaded dynamically by Lattice, authenticated through Zitadel, routed by Envoy, and backed by the real mod-licenses service.
That is still the visible proof, but the project has moved on. The interesting part now is that the local proof has started to acquire a reusable platform shape.
What works now
The current local path is not just a static page with a familiar FOLIO screen on it. It is a full browser workflow:
- start the local substrate;
- create or replay the
foundry-devworkspace lifecycle state in Loom; - reconcile the workspace;
- visit the workspace hostname;
- login through Zitadel;
- load Lattice;
- dynamically load the Licenses MFE;
- create a real license;
- search for that license and find it again.


The browser verification now proves that ui-licenses is calling mod-licenses through Envoy with the current OIDC access token. It also proves the support calls to settings and tags route to foundry-platform-services, rather than being hidden inside the MFE or faked by the shell.
That last point matters. The app is not only visible. It is using the same boundaries the architecture claims to have.
The split is becoming concrete
Foundry started with a simple separation:
- Loom is the control plane.
- Lattice is the runtime application shell.
- Envoy is the routing and data plane.
That is now more than vocabulary.
Loom has moved from spike-era static data toward a real Micronaut 5 / JDK 25 service with Postgres-backed lifecycle state. It can persist workspace intent, presentation hostnames, capability activation, idempotency keys, reconciliation runs, step state, executor claims, leases, and retry reset.
Lattice still does what it should do: it is dumb-hosted behind a workspace hostname and asks Loom for the runtime block associated with that hostname. It does not parse tenant identifiers out of URLs and it does not decide which backend services exist.
Envoy executes the generated routing and compatibility transforms. It is not a product catalog, not a tenant manager, and not a control plane. It is the data plane.
The local Docker substrate is the first executor, not the platform model. The current reconciliation script creates or replays a Loom run, claims dependency-ready steps with a distinct executor credential, performs local side effects, and reports sanitized results back to Loom with a lease token.
How the Licenses tenant actually appears
One useful question is whether the tenant is real. If a license can be created and searched, then mod-licenses must have been tenant-provisioned and it must be receiving the expected FOLIO tenant header.
That is exactly what happens.
Loom declares that the foundry-dev workspace has Licenses activated and needs a FOLIO module tenant provisioning action. The local reconciliation executor claims that action and maps it through the local executor registry to the Licenses provisioning script.
That script calls the real tenant-control interface on mod-licenses:
POST /_/tenant
X-Okapi-Tenant: foundry_dev
{}
So the tenant is not faked in the browser and not inserted directly into the database. The real module receives the same kind of tenant provisioning call it expects in FOLIO.
At request time, the browser still does not invent the tenant header. Lattice resolves workspace context from Loom and publishes it to the MFE, but backend routing stays with Envoy. Loom’s routing intent says that the Licenses backend route needs the FOLIO compatibility tenant header. The local renderer turns that into Envoy configuration. When the browser calls:
/licenses/licenses
Envoy routes the request to mod-licenses and injects:
X-Okapi-Tenant: foundry_dev
That value comes from Loom’s workspace model. X-Okapi-Tenant remains a compatibility header at the boundary to FOLIO modules, not a Foundry-native tenant contract.
Platform services, not miscellaneous glue
Licenses also exposed a familiar problem in FOLIO UI work: a module screen often expects platform support APIs around it. In this path, shared Stripes components call settings and tags endpoints.
Those calls are now routed to foundry-platform-services.
That service is intentionally not Licenses domain logic. It is also not Loom, not Lattice, and not an Envoy direct response. It is the first boundary for platform support APIs such as settings, tags, password validation, and similar interfaces that applications need but that do not deserve to become hidden inside a business module.
There is still a lot to harden there. Settings and tags are only implemented enough for the current Licenses path. The interface-versioning problem from FOLIO remains awkward: a module may expect a particular shape of /settings, and Foundry needs to map that to a selected versioned interface such as /settings/v1 without running a new service for every interface version.
But the ownership line is in the right place.
The Licenses wrapper is no longer the pattern
The first Licenses MFE was necessarily a learning wrapper. It discovered how much runtime shape a real Stripes module expects: React 18 isolation, react-router 5, Stripes context, Okapi-shaped runtime data, permissions, interface discovery, translations, React Query, Redux, callouts, module hierarchy, CSS conventions, icon loading, and a narrow fetch authorization bridge.
That learning has now started to move into lattice-stripes-module-adapter.
The Licenses wrapper now supplies Licenses-specific binding data:
- the
ui-licensesroot component; - the
/licensesroute; - the
@folio/licensesmodule name; - expected interface versions;
- translation bundles;
- protected API prefixes.
The adapter owns the generic Stripes island. That is an important step away from one hand-built wrapper per module. It is not the final publishing model yet, but it gives the publisher something real to automate.
Developer substitutions need to be first class
Another question came up while reviewing the roadmap: can a developer run most of the stack normally, but swap one component for something running in an IDE?
That should be a first-class workflow.
For a backend, the desired shape is:
- keep the local substrate running;
- run one service from the IDE;
- route Envoy to that IDE service instead of the container;
- preserve the normal workspace URL, auth, tenant headers, path rewrites, and same-origin behaviour.
For a frontend MFE, the equivalent is:
- keep Lattice running at the workspace hostname;
- run the MFE from a JavaScript dev server;
- point the MFE remote entry at that dev server;
- keep dynamic loading through the same Lattice runtime composition model.
The important thing is that developer overrides should not encourage bypassing Envoy or Lattice. If a developer talks directly to the service under test, they may miss the exact boundary defects Foundry is trying to surface.
This is now tracked as a roadmap item: local developer component overrides.
Self-hosted Foundry is not the only use case
The current sprint path is still a self-contained self-hosted subset of modules. That is the right proving ground.
But there is a second strategic use case:
I already run FOLIO, Koha, TIND, or another ILS. I want to extend that system with a Foundry capability.
This must remain possible.
Nothing in the current work intentionally blocks that capability, but it has not been proven yet. It needs its own boundary. Foundry modules should not directly learn every target ILS. That is where the internal Sovereign project matters: an ILS-neutral interface to library capabilities, so a module can call a stable operation such as Sovereign.circItem without knowing whether the backing system is FOLIO, Koha, TIND, or something else.
That is now also on the backlog as an explicit external ILS extension boundary. The goal is to keep the self-hosted path and the “extend my existing system” path from being confused.
What is next
The backlog has been pruned. Most of the day 0 and lifecycle foundation work is complete. The deliberately open work is narrower now.
The next strong chunk is publisher automation.
The question becomes:
Can we turn the manual Licenses wrapping procedure into a repeatable publisher that tracks FOLIO UI module releases, selects the right adapter, builds an MFE artifact, and records the result?
That likely means:
- define the
lattice-stripes-module-publishertracking model; - make
ui-module-list.yamlrecord tracked modules, release state, adapter version, and artifact status; - teach the publisher to inspect
ui-licenses; - reproduce the current Licenses MFE artifact from the publisher path;
- defer full artifact-bucket publishing until local repeatability is solid.
The other important future chunks are now named rather than implicit:
- local developer backend and MFE overrides;
- external ILS extension through Sovereign-style interfaces;
- fuller platform-service settings and tags implementations;
- production identity for Loom and executor principals;
- expired-lease recovery and stronger executor concurrency;
- Kubernetes rendering and execution;
- route publication beyond generated local Envoy files;
- multi-workspace isolation.
The shape is clearer than it was a few days ago. Foundry has a working local application, but more importantly it now has a growing set of boundaries that can be replaced, hardened, and automated without collapsing back into a single platform component that does everything.