Development notes from across the CaseHub ecosystem.
RAS was wearing a reactive costume. Every one of its 226 Uni references fell into exactly two categories: Uni.createFrom().item(synchronousResult) on the provider side, .await().indefinitely() on the consumer side. Ze...
The old pages-ui-tokens system generated themes at runtime from four numbers: base hue, accent hue, chroma, contrast. Call generateThemeCSS(config) in the browser, get 72 colour tokens. Simple. But it couldn’t express...
Platform had two reactive SPIs — ReactiveNotificationStore and ReactiveSubscriptionStore — each with three implementations: NoOp @DefaultBean, InMemory @Alternative, and JPA native. Plus a ReactiveAgentIdentityVerific...
The investigation triage worker was a stub — every case got SAR_WARRANTED regardless of what the specialist findings said. That was fine for wiring the engine layers together, but it meant the investigation-cleared go...
Ganglion-level evidence templates landed last session — extract fields from every CloudEvent regardless of which rule matched or which outcome won. The obvious next question: what about evidence that depends on the de...
The CBR feedback loop had a gap. CbrOutcomeConsumer existed — it could convert a CbrOutcomeData record into a CbrOutcome and call recordOutcome() — but nothing delivered events to it. The consumer was wired to nothing.
Multi-agent conversations accumulate assertions but nothing tracks what’s established vs what’s still contested. The conversation projection folds messages into structured state — points, threads, statuses — but the f...
Filling the Gaps
The commit that fell between the cracks
The subject view toolkit shipped yesterday. Today was about what it still needed before casehub-work could actually use it.
Continues from The Test That Found Two Bugs Before It Passed.
Issue #175 asked for a generic queue toolkit. AbstractQueueEntity, AbstractQueueService, QueueSubject — the container model. Items enqueued by name, escalated by moving to a different queue. Straightforward JPA @Mappe...
The OpenClawAgentRegistry had a silent data-corruption bug hiding behind a “works for now” 1:1 constraint. The registry maps agents to cases for channel routing — when a Qhorus COMMAND arrives, the backend needs to kn...
Two separate problems landed on the same branch: how to let modules receive specific CloudEvent types without the full DataSource machinery, and how to make MvelExpressionEngine work with POJOs instead of just Maps. T...
Delivery tracking tells you whether a notification reached the channel. It says nothing about whether anyone read it. That gap — between “delivered” and “engaged” — is what #170 closes.
The demo UI started as five hand-rolled Lit components with hardcoded CSS values and its own token system (--blocks-*). It worked, but it was carrying 380 lines of layout, channel rendering, and modal management that ...
Supersede, Score, Split
The pipeline had a method called refreshDataSet. It didn’t refresh anything.
NaiveBayesGanglion had a gap that became obvious once I looked at it through the persistence lens: it holds running log-posteriors in a ConcurrentHashMap that evaporates on restart. For windowed situations this barely...
CBR has four phases — Retain, Retrieve, Reuse, Revise — and until now neocortex implemented three of them. Cases were stored, retrieved with similarity scoring, and their outcomes fed back via EMA confidence. But the ...
CaseHub’s decomposition infrastructure already has both halves — StaticDecomposition for guard-based method selection, LlmDecomposition for when the LLM needs to reason about which agents to deploy. What it didn’t hav...
The Grouped Window Nobody Had
The summary that was already there
Issue #31 proposed putting ganglion internal state on a case blackboard — Drools CEP sessions, Bayesian posteriors, anything a stateful ganglion accumulates over time. The original RAS spec had this as an optional pat...
I’d been expecting this one to be a slog. Modals have a reputation — focus traps, scroll locking, backdrop click detection, screen reader announcements, z-index wars. Every framework has a modal component and none of ...
The branch that nobody owned
The Type That Replaced Object
Showing the Work in Similarity
Five small issues had been sitting in the backlog since the structured fields (#89) and sequence similarity (#92) work landed. Each was a gap noticed during implementation and deferred — not because it was hard, but b...
CBR cases in neocortex have been point-in-time snapshots since day one — flat features like race, MMR, and game phase. The structured fields work (#89) added nested objects and lists for richer case anatomy, but those...
The DataSource system was add-only. You could register a DataSource, subscribe to it, push events through the alpha network — but you couldn’t remove one. No CDI event for removal, no cleanup in the router, and regist...
CaseHub Neocortex — When Flat Features Aren’t Enough
CaseHub RAS — The Store That Learned to Fail
Part of a series on #124 — Consolidate RrfFusion with generic CbrFusion. Previous: Three Implementations of the Same Algorithm.
Three Implementations of the Same Algorithm
The Score That Lied
The desired-state runtime started with an implicit assumption: one domain per deployment. A single ActualStateAdapter, a single EventSource, a single NodeProvisioner. That was fine for teaching examples and single-pur...
The Workbench Problem
The approval-gate component had a quiet blind spot. It sent a PUT to complete a gate decision, checked response.ok, and threw the response body away. The gate.decided event carried only what the client already knew — ...
The reranking issue asked for the decorator to live in rag/ alongside HybridCaseRetriever. That felt wrong before I could articulate why — a gut reaction to the dependency graph. rag/ depends on inference-api and infe...
The in-memory digest buffer worked fine until I started thinking about what happens when the process restarts mid-digest. Notifications buffered for an hourly email digest just vanish. For most deployments that’s acce...
Every design-review workspace already has a version history. Each implementor round produces a git commit, and the tracker records **Spec commit:** → abc123 per issue. The timeline was sitting in the data — it just wa...
A platform convention isn’t real until the second project adopts it. The first adopter shaped it; the second one proves it generalises.
The original design was straightforward — attach a LocalSimilarityFunction to each FeatureField and let the scorer call it. LocalSimilarityFunction is a @FunctionalInterface, already used for per-field overrides via t...
CaseHub — Trust Closes the Loop
Part of a series on the data-table build. Previous: The Platform Table.
Part of a series on #147 — notification system. Previous: Digest and Batching.
Arc’s @Decorator support has a gap nobody warns you about. If the bean being decorated was created by a @Produces method, the decorator registers at build time, passes every diagnostic check, and then silently does no...
The notification pipeline has been delivering every matched event immediately to every channel since it shipped. That’s fine for in-app — real-time is the point. But for email? A user subscribed to “all comments on my...
Every CaseHub app needs tables. Until today, every CaseHub app that needed a table would have had to build its own — or use the one baked into pages-viz, which is coupled to the pages-runtime data pipeline and has no ...
I filed a bug that was already fixed. That’s the short version.
A routine question — “other repos say governance is missing” — turned into a full audit of the casehub-parent BOM against every child repo in the build chain. Governance was fine. The BOM was not.
The pages codebase has been accumulating conventions — token naming, event contracts, component strategy — but none of them lived anywhere a fresh session could find them. They were implicit in the code. This session ...
When desired state learns to finish
The queue board redesign left blocks-ui with a solid inbox and detail panel, but no shared primitives for the things every CaseHub app actually needs: deadline indicators, metric dashboards, and approval gates. Four i...
The issue said “only when profiling demonstrates the O(n) scan is a bottleneck.” I nearly deferred it on those grounds — current scale is under 100 patterns. But the more I looked at TopicRegistry, the more the trie f...
Three Bugs Where There Was One
Part of a series on #7 — DroolsSessionStore: persistent implementation for restart survival.
The SNAPSHOT That Wasn’t There
Added JSON Schema validation to the worker executor — but with an asymmetry that took some design thought: input validation rejects (returning a Failed result before the function ever runs), while output validation on...
The Document That Nobody Read
The iframe component API had been sitting in the “if patterns emerge” queue since we wrote the first batch of casehub-pages protocols last session. I’d assumed there was something to formalise around the React renderi...
When I started the subscription system a few days ago, I left a design flaw in place knowingly. The userId field on a subscription was both the person who created it and the person who received notifications. For pers...
Subscription Management: From CloudEvents to POJOs
Nine Issues, One Branch, and a Chicken-and-Egg Problem
The batch that closed six issues taught me something I should have seen earlier: neocortex’s CBR retrieval was architecturally wrong.
Part of a series on #172 — CloudEvent WorkItem Bridge. Previous: Six Issues, One Branch.
The notification store is the first new store SPI in platform-api since CaseMemoryStore moved to neocortex. It’s the terminal sink in the notification pipeline — the routing layer evaluates subscriptions against incom...
The CBR dual-store architecture has a blind spot I hadn’t noticed until reconciliation forced the question. QdrantCbrCaseMemoryStore delegates durable storage to CaseMemoryStore — JPA or SQLite — and maintains a Qdran...
The last session landed the universal routing strategy convention (#634) — NamedStrategy, StrategyResolver, five SPIs retrofitted. That work left a trail of follow-on issues: evaluation happening in the wrong place, n...
From Drools Alpha Networks to CaseHub DataSources
The sixth worker module — workers-k8s — shipped two days ago. A K8s Job can run for hours. The in-memory completion registry doesn’t survive a process restart. That’s a qualitatively different problem from HTTP worker...
The CBR store has been embedding problem() text at write time since it was built. Every case goes in with a dense vector alongside its payload filters. But retrieval never used it — scrollAsync with payload filters, s...
The backlog had one item in it. That’s not because there’s one thing to do — it’s because nobody had looked at what was open. Ten issues in GitHub, and the handover said “pick up #152.”
The Qhorus normative layer has had a clear speech act taxonomy for months — nine types, seven commitment states, a sealed CommitmentStore. casehub-work has had a complete WorkItem lifecycle for nearly as long — twelve...
The Tag That Didn’t Match
The Remote That Pointed Somewhere Else
casehub-pages has been 100% TypeScript since the migration from GWT. Every feature — data pipeline, layout, navigation, rendering — runs in the browser with no server dependency. That constraint was always intentional...
casehub-pages — The Dedup Semantics Question
The backend shipped last session with auth and layout persistence, but the data module was a placeholder — just a package-info.java. The original #21 issue described a SQL data provider with push-down operations, and ...
Seven issues, all small, all on one branch. The kind of session where the value isn’t in any single change but in the cumulative surface area they open for downstream consumers.
The Great Memory Migration
The question behind #279 was whether WorkItemGroupLifecycleEvent should integrate into the WorkItemEvent hierarchy — share a common base, implement the interface, or remain standalone. It was filed during the #278 API...
Closing the Result Model
I started this branch thinking issue #13 was about making provisioners return PendingApproval. The issue body described a “HumanNodeProvisioner” that returns PendingApproval with a plan artifact, gets called back afte...
The Rename That Touched Everything
Three features on one branch again. Discord needed two fixes — message history wasn’t downloading attachments, and the inbound translator was silently dropping them. The MCP tool needed the rest of the embed surface e...
One Model, Three Signals
I started this session thinking I was adding a similarity search API. I finished it questioning whether the repo should be renamed.
Sometimes the most useful thing you can do with a filed issue is close it.
The guided walkthrough pages for casehub-clinical looked complete — shipped under #105 with all 8 steps and 6 explore pages. Issue #98 asked for specific things: a target-vs-actual enrollment bar chart, an endorsement...
Three Legs and a Flat Namespace
The issue was straightforward: add a ScimDIDResolver that constructs synthetic DID documents from SCIM certificate data, so enterprises don’t need to host DID documents externally. SCIM already has the certificates. J...
The Semaphore That Was Already There
I came into this branch with a clear problem: two JVMs processing events for the same situation both evaluate to CREATE_CASE, both fire caseTrigger.fire(), and you get a duplicate case. The #18 work had already put OC...
The Hortora engine benchmark proved what I suspected — nomic-embed-text treats @DefaultBean and ConcurrentHashMap as generic English tokens. Dense vector search returns WireMock entries when you search for CDI annotat...
Part of a series on #11 — runtime enhancements. Previous: The deferrals that took an afternoon.
What I was trying to achieve: giving the Agent API eyes
Three issues landed on one branch: plugin endpoint auth (#42), a CDI cleanup (#48), and a documentation update (#47). The CDI cleanup was mechanical — OidcCurrentPrincipal @Alternative @Priority(100) shipped in platfo...
The issue for #31 described a tiered search pipeline: store both full-precision and truncated+binary-quantized vectors per point, use the compact one for fast first-pass retrieval, rescore against the full-precision o...
The question that started it
The Hortora engine’s garden corpus hit ~6,500 entries. When CorpusIngestionService bootstraps from an empty cursor, it passes every chunk to QdrantEmbeddingIngestor.ingest() in a single call. Three unbounded operation...
Part of a series on #3 — JavaSwitchGanglion. Previous: The Deferrals That Took an Afternoon.
The DirectCallBridge from the previous session had a known gap: no eviction for orphaned CompletableFutures. If a webhook never arrives and the subscriber doesn’t cancel, the map entry leaks. The fix is CompletableFut...
The rag-hyde module had the wrong name from the start. It was always a generic query expansion decorator — it called QueryExpander.expand() and delegated. It didn’t know or care about HyDE. But HyDE was the first stra...
Part of a series on #116 — CredentialResolver bridge to Quarkus CredentialsProvider. Previous: The Quarkus CredentialsProvider Shaped Everything.
CaseHub RAS — The Runtime That Found Its Own Clock
Three issues sat in the casehub-ras backlog since Epic 2 closed: a YAML provider for situation definitions (#13), ANTI signal handling in the trigger policy (#15), and compact() invocation for persistent situations (#...
The Quarkus CredentialsProvider Shaped Everything
The native image gate passed weeks ago. C2 proved both JNI layers — ONNX Runtime and HuggingFace Tokenizers — work in Quarkus native image on macOS ARM. Reachability metadata shipped in inference-quarkus. The gate was...
I’d been staring at CaseRetriever.retrieve(String query, ...) for weeks without seeing the problem. The String query parameter was doing three separate things: providing text for dense embedding, providing text for sp...
The plan was simple: fix a broken import (PlannedAction moved packages), close a test-polish ticket, and be done. Neither issue was hard. I had a branch in under ten minutes.
CaseHub RAS — The Collection Strategy That Started With the Wrong Ordering
casehub-life got its auth wiring three days ago (life#40). Now it was openclaw’s turn. The task looked the same — add casehub-platform-oidc, configure OIDC, annotate REST resources. But openclaw is a different kind of...
CaseHub Worker — First Blood
There’s a GE rule that says always use .exceptionally() when calling fireAsync() for CloudEvent dispatch. It’s right. Except in Kafka and AMQP stream processors, where following it would silently eat your messages.
Platform#104 was started on 19 June, code-reviewed, fixed, and then orphaned — the branch never made it to main before the session ended. Wrapped it today.
ARC42STORIES.MD had fallen six weeks behind the code.
Part of a series on #4 — DroolsGanglion. Previous: The API That Didn’t Wrap Anything.
The spec for reactive CRAG looked straightforward: mirror the blocking CorrectiveCaseRetriever as a @Decorator on ReactiveCaseRetriever, swap fire() for fireAsync(), wrap the evaluator call in runSubscriptionOn(). Two...
The RAS design spec was written on June 12. Two days later, the parent repo’s layering decisions landed — and they made a quiet but significant choice: io.cloudevents.CloudEvent is the platform’s typed CDI event envel...
Seven issues, all flagged S or XS, all on one branch. The kind of session that should be mechanical. It wasn’t.
The plan for #31 was straightforward enough: move OversightGateService out of casehub-openclaw and into casehub-engine-api where it belongs, classified as a Known Placement Violation in PLATFORM.md. I assumed it would...
CaseHub Neural-Text — Fifteen Branches and Nine Missing Posts
CaseHub Neural-Text — The Guard That Isn’t a Guard
For three months the platform has been able to produce things — ledger entries, memory stores, WorkItems, channel messages, agent sessions. It couldn’t receive anything from outside. That changes today.
Three issues on one branch — #70, #90, #99. The sequencing was forced: #90 (moving ReactiveCaseMemoryStore to platform-api) had to land before #99 could add methods to it. You can’t add a method to an interface that’s...
Prompt caching was always the point. quarkus-langchain4j-anthropic doesn’t support cache_control breakpoints — langchain4j#1591 has been open for a while — so repeated calls with the same large system prompt pay full ...
The spec for this took seven review passes before anyone was happy with it. I’m not complaining — the reviewers were right every time, and working through the @Blocking deadlock, the SESSION__KEY double-underscore, an...
The multi-turn agent API has been sitting in the backlog since the single-shot AgentProvider.invoke() shipped in June. Platform#58 was the explicit “finish this later” — the spec said multi-turn was deferred because n...
The trailing edges, trailed
Trailing Edges
The last entry added AgentKey(agentId, tenancyId) to ChannelContextWindowService to prevent same-agentId cross-tenant collision. Sound reasoning at the time. Except OpenClawAgentRegistry — the other map that tracks ag...
Showing the Work
Two issues closed today that were deferred from the endpoint registry session. EndpointPermissions (#89) was the simpler one — a static utility class mirroring MemoryPermissions exactly, stripped of the async overload...
Four Reviews and a Fake Class
Fifty-Six Files and a Hash Bug
The Storage Layer That Wasn’t There
The casehub platform has four foundation primitives now. Preferences let modules read scoped configuration. Identity lets code know who’s acting. Memory gives agents a store that survives cases. And as of today: a nam...
The Qhorus multi-tenancy work landed (#260) and openclaw had zero tenancy awareness. Not a single tenancyId reference anywhere in the source. That’s not unusual at this stage of integration work, but it meant a full p...
The adapter’s erase() has thrown MemoryCapabilityException since the day it was written. Graphiti’s REST server offers one deletion primitive: DELETE /group/{group_id}, which cascades everything for a group — episodes...
When Your Bridge Is the Problem It Solves
The platform had five open correctness issues — none of them features, all of them things that worked but worked wrong. No external users means no reason to tread carefully. I fixed each in the intended way, which mea...
Where the Protocol Said One Thing and the Codebase Said Another
The RAG Pipeline That Dropped Its Framework
The task for today’s second session was supposed to be straightforward — implement reactive SPI variants for the OpenClaw provisioner and channel provider. S-scale, a few hours, done.
The gate entry point has been dead code since openclaw#28. When we removed the speech act classification layer, we also removed the code path that decided whether an agent action needed human review. The casehub_done ...
After delivering the Graphiti memory adapter last session, I had a short list of follow-up items to resolve — none of them new features, all of them correctness questions the adapter had left open.
CaseHub Neural Text — The Qualifier That Couldn’t Share Its Name
CaseHub Neural Text — The Layer That Gives Floats Meaning
Issue #28 was filed as a narrow fix: inject the speech act protocol text into the COMMAND message via OpenClawChannelBackend.post() instead of loading it through the always-active casehub-global skill. A few lines of ...
The idea for this one started as an impedance mismatch problem. I wanted to add Graphiti as a Tier 3 memory backend — temporal knowledge graphs, bi-temporal fact validity, the kind of thing that can answer “what did t...
CaseHub Neural Text — The SPI That Argued Back
The issue existed for a good reason. Mem0CaseMemoryStore had no storeAll() override — the SPI default fires one REST call per item, and Mem0 OSS had no batch endpoint to improve on that. Platform#69 deferred the work:...
Three small issues, one branch. I’ve been calling them “S-items” in the handover — #20 (CommitmentTools crash recovery), #22 (CDI wiring test), #25 (oversight channel type constraints). Nothing architecturally new. Bu...
Neural Text — The Native Image Gate
ARC42STORIES.MD for casehub-platform had been a stub since June. §1 and §2 were solid; §3 through §13 were placeholders — “complete in dedicated session” appearing in every heading. Issue #56 existed specifically to c...
InMemoryMemoryStore was at @Alternative @Priority(1), the same as SqliteMemoryStore and Mem0CaseMemoryStore. In production this is fine — you never deploy them together. In @QuarkusTest you might: production adapter o...
The Phase 1 placeholder was always going to need replacing: every OpenClaw agent output dispatched as DONE, regardless of what the agent actually said. If the case step closed correctly, great. If not — the case silen...
The quarkus-flow worker arrived in the engine. My working assumption was that it would answer the open questions in §6.5 of the ACL spec — what identity does a flow worker carry, how does provisioning write grants, wh...
The starting assumption was wrong, and catching it early changed the whole design.
The real design work this session happened before any code was written. The spec went through three review rounds and came back substantially different each time — not style tweaks, actual structural errors.
Four backlog items. Three were paperwork — a groupId typo in a spec, a CLAUDE.md table that didn’t mention the agent modules yet, a test that used Thread.sleep(300) to paper over a timing race. The fourth was the inte...
The session was backlog cleanup — six small items that had been sitting since Epic 7 closed. Most of them were administrative: close an issue the code had outrun, check an upstream bug’s status, file a parent repo iss...
Shipping casehub-platform-agent was a longer journey than the module count suggests. Two flat modules: agent-api for the SPI, agent-claude for the Claude integration. The spec looked solid going in.
The session started with what looked like a simple question: why is claudony’s CI red?
The first thing I noticed when starting ARC42STORIES.MD was that LAYER-LOG.md was completely wrong. Every Epic from 2 through 7 was marked “Pending” or “In progress” — but all of them had been closed in GitHub weeks a...
ACL has been the obvious missing piece for a while. The platform has identity, RBAC, and preferences — it knows who an actor is, what groups they belong to, what settings apply to them. It has nothing to say about wha...
The session opened with a heads-up from another Claude working in casehub-work: casehub-platform-api was shipping without a Jandex index. It’s the pure-SPI jar — interfaces and records, zero dependencies — and until n...
Seven issues with S or XS labels. I wanted to get through them in one session rather than let the list drift further.
OpenClaw agents are capable. They call APIs, read calendars, send messages, run heartbeats. But when an agent commits to do something, there’s no machine-readable record it ever happened. No deadline. No escalation if...
The CaseHub memory stack had two endpoints: an in-memory ConcurrentHashMap for tests and ephemeral deployments, and a PostgreSQL-backed adapter for production. There was nothing in between. If you wanted durable memor...
Platform#48 came in as feedback from devtown — the first consumer to wire up CaseMemoryStore in a real application. The core interface was right; the gaps were in what surrounded it. No standard way to emit memories. ...
Epic 6 was supposed to be the wiring-up epic. COMMAND arrives on a Qhorus work channel, gets forwarded to OpenClaw via /hooks/agent, OpenClaw completes, POSTs the result back, the result lands on the channel. Most of ...
GroupMembershipProvider.membersOf(groupName) has been in platform-api since the beginning. It answers the inverse membership question — not “what groups is Alice in?” but “who’s in the reviewers group?” — and it has a...
The research spec had @agent.on("before_prompt_build") in Python. That code doesn’t run anywhere. OpenClaw’s hook system is TypeScript only.
The branch opened with a clear goal: build a REST client adapter for Memori, the SQL-native AI memory engine. Tier 1 of the CaseMemoryStore adapter ladder. By the time we were done, there was no Memori REST adapter. T...
The previous entry ended with associate(agentId, channelIds) as the registration mechanism — WorkerProvisioner knows the agentId, knows the channels, calls associate. Tidy in theory. In practice: the provisioner knows...
Every CaseHub case starts cold. A new work item opens, the engine spins up, and it knows nothing about the entity it’s about to reason over — not what happened in prior cases, not what agents learned, not what was obs...
The ChannelContextWindow is what makes the multi-agent mesh actually work at the LLM level. Without it, an OpenClaw agent waking up to a COMMAND knows only what that message says. It doesn’t know that finance-agent po...
A Quarkus library module with no end users, no versioning commitment, no backward compat — that’s the license to design cleanly. The OpenClaw hook API client is the first real implementation in casehub-openclaw, and i...
The first items off the small-issues queue: one already done, just never closed.
I started this session thinking the hook infrastructure was sorted. It wasn’t.
The session opened with a question I’d been ignoring: had any of the casehub-platform blog entries actually made it to mdproctor.github.io? I brought Claude in to check. We looked at the _notes/ directory on the Pages...
Root scope — Path.root(), an empty path — was never included in the preference resolution ancestor chain for any non-root scope. Call resolve() with Path.of("casehubio", "devtown") and the walk produces ["casehubio", ...
The persistence-jpa module backs PreferenceProvider with Postgres. The platform needed a second option: MongoDB, same SPI, no Flyway.
A separate Claude session — running earlier today — added Path.root() for the zero-segment root scope. The commit was solid: correct logic, clear motivation. When I picked up the session I asked Claude to review it be...
Platform#24 opened a question: where do application-layer SPIs live when they’re domain-agnostic but too specific for the foundation tier? The proposed answer was a casehub-platform-apps-api module. The actual answer,...
The persistence-jpa/ module is the first in the platform that actually writes to a database. Config reads from YAML files. OIDC reads from JWT claims. Nothing persists state — until now.
The engine team filed a request: JQEvaluator needs to live in casehub-platform, not casehub-engine-common. Foundation-tier repos like casehub-work can’t reach into the Orchestration tier to borrow a JQ evaluator. The ...
The ActorType migration was straightforward until review caught something I’d missed.
Three small things that had been sitting on the list since the oidc module shipped.
The previous entry planted tenancyId() and isCrossTenantAdmin() in the SPI before anyone could build on top. This one fills in the real implementation — OidcCurrentPrincipal, now in its own optional module.
Every multi-tenant system I’ve seen retrofitted from single-tenant has scar tissue. Columns added after the fact, cache keys that leak data between customers, foreign keys that don’t include tenant scope. The longer y...
The previous entry ended with the platform foundation shipped. Devtown read the spec and came back with six specific problems. The response changed the shape of everything.
casehub-platform is the zero-dependency foundation for everything else in the stack — Path hierarchies, typed preferences, principal identity. Before any other module can ship, this one has to exist. We spent two sess...