Skip to main content

Keystone — Decision Framing

Status: Framing only. No decision made. Prepared for the real Command Center planning session referenced in TRA-401. Cleaned up 2026-08-22 (section numbering fixed, prior document-merge inconsistencies resolved) — source material: traxs-identity-seed.md, Keycloak-decision.md, entra-cost-tally.md, keystone-external-login-proposal.md, strategy-pricing-architecture.md, infrastructure.md. Prepared: 2026-08-19. Deadline set 2026-08-21: January 2027 (Section 8). TRA-384/389 closed 2026-08-22 (Section 3). Scope: Whether Traxs Group should build a self-owned, OpenIddict-based identity service ("Keystone") shared across RoundTrip, Waypoint, and Relay — or continue with the current Entra External ID (CIAM) approach.


1. The question

RoundTrip currently authenticates through a single shared Azure Entra External ID (CIAM) tenant (roundtripapp.onmicrosoft.com). Waypoint has already committed, in its BRD, to reusing that same tenant so RoundTrip customers get shared-credential login into Waypoint with no new password. Relay does not exist yet, but is planned as a third multi-tenant SaaS product, subscribable independently or in any combination with RoundTrip and/or Waypoint.

Two confirmed, open bugs in RoundTrip's Entra implementation (TRA-384, TRA-389) raised the question of whether Entra External ID is the right long-term foundation for identity across three independently-subscribable products, or whether Traxs should own its identity layer outright via a self-hosted, OpenIddict-based service.

This document frames the decision. It does not make it — several material inputs (cost of ownership, current-model stability, centralized-risk exposure, and migration timing) are still open, and are called out explicitly in Section 5.

A pattern worth naming, added 2026-08-19: the March 16, 2026 case study log capturing the original Keycloak → Entra decision observed directly that "the architectural question... was never explicitly asked until this conversation" happened, specifically because there was space for it — no sprint deadline, no backlog pressure. This Architect conversation exists for the same reason, asking the same underlying question a second time. Worth treating that as confirmation the right kind of space has been created again, not a coincidence.


2. What's actually true today (not what the old docs say)

The original RoundTrip architecture documents (SAD, domain model, database design, DDD, API design, tech-stack-analysis) describe Keycloak with a realm-per-tenant as the identity architecture. That is no longer what's running. Current reality, confirmed via infrastructure.md and Linear:

  • RoundTrip runs on a single shared Entra External ID (CIAM) tenant, not Keycloak. Waypoint has a real, deployed Entra integration too — confirmed via infrastructure.md (live App Registrations for both the Waypoint API and Waypoint Web Client, real Key Vault secrets, a production App Service with an ADO pipeline actively deploying to it, and documented runtime bugs someone actually hit while integrating it — IDW10106, the CIAM tenant-ID claim needing the full schema URL instead of the short tid name, managed-identity SQL access needing to be granted manually). An earlier pass of this document claimed Waypoint had nothing wired in at all; Pete confirmed directly (2026-08-22) that was a genuine oversight, not a documentation nitpick. Settled: Waypoint needs the same thing RoundTrip does here — it's just materially lower risk right now, since Waypoint has zero live paying customers to disrupt if that changes. Same underlying need, different urgency, not "nothing to worry about."
  • Keycloak was abandoned mid-project — not because it was architecturally wrong, but because self-hosting and operating it became "extremely complicated and difficult to work with" for a solo developer. No document anywhere records why the Keycloak → Entra move happened. Resolved: Keycloak-decision.md records it directly.
  • Tenant scoping today is an application-layer concern — a custom tid-style attribute/claim resolved per user — not a provider-level boundary (e.g., not one Entra tenant per customer, and not the old Keycloak-realm-per-tenant model either). TRA-378 shows this mechanism is itself under active dispute ("whether SetTenantIdAsync is still needed").

Why the Waypoint correction actually matters, not just a factual nitpick: the original framing treated Waypoint's Entra commitment as though it carried no real migration weight at all, as if it were a pure green field. It isn't — there's a full, deployed integration that would need reworking if Keystone replaces Entra. What's still true, and still matters for Option B's risk picture (Section 6): RoundTrip carries the only real customer-facing migration risk today (real tenants, real data); Waypoint carries real technical rework but zero customer disruption; Relay carries neither, since it doesn't exist yet.

A second real gap, worth grounding before this goes further: the requirement in Section 4 that "product entitlement needs to be a first-class part of the identity/authorization model" was reasoned about abstractly at first, without the actual, already-decided pricing and subscription architecture behind it — three-tier pricing per product (Starter/Standard/Professional), bundle discounts at 10% (two products) and 20% (full suite), a general pricing-change policy (90 days' notice, no silent changes), and the Founders Program's price-lock commitment extending across future products at launch-day rates for 5 years, including products a tenant hasn't subscribed to yet. Entitlement design done against real, decided pricing looks meaningfully different from entitlement design done in the abstract — see Section 4 for the concrete version of this requirement.

This is flagged here because it's the clearest evidence yet of the gap between originally-planned and actually-true architecture that the RoundTrip docs were already known to have.


3. The evidence base: two confirmed bugs

Status, 2026-08-22 — both closed. ProvisionTenantJob was migrated to CreateMemberUserAsync, the same pattern team invites already used, and verified against the exact regression case (a fresh email on a real verified-M365 domain). Confirmed directly in Linear: TRA-384 and TRA-389 are both marked Done. Left in full below as the evidence base — they're why this document exists, and the pattern they're part of (Section 5) is still fully live even though these two specific tickets are closed. See Section 8 for what "closed" does and doesn't mean for the bigger question.

TRA-384 — First-user provisioning never migrated off the guest path. TRA-223 replaced the guest-based InviteUserAsync flow with CreateMemberUserAsync for team invites, after discovering guest accounts unreliably got 403s (app-role assignment failures) and couldn't self-service password resets. ProvisionTenantJob — which creates every tenant's very first TenantAdmin — was never migrated to match. Resolved: now uses CreateMemberUserAsync, same as team invites.

TRA-389 — Registration redirects to a real Microsoft 365 login for verified-domain business emails. Confirmed with a genuinely fresh identity (hello@traxsgroup.com): Entra performs home-tenant discovery before falling back to email OTP. If the email's domain is a verified Microsoft 365 tenant — a common setup for real small businesses — Entra redirects the user to authenticate against that tenant's real credentials, not RoundTrip's. This happens on a completely clean first signup, no prior history required. The failure mode is worse than an obvious error, since the prompt looks like a legitimate Microsoft login. Resolved: the CreateMemberUserAsync migration above sidesteps home-tenant discovery entirely, confirmed on this exact scenario.

Question that's now been empirically answered: did routing registration through CreateMemberUserAsync resolve both? Yes — confirmed, not just theorized. Per your own read at the time: the near-term code fix and the long-term architecture question are separate concerns. That held — fixing TRA-384/389 protected the near-term registration flow, but does not by itself answer whether Entra is right as the long-term foundation for a three-product platform. That question is exactly as open as it was before these two tickets closed.


4. What's already settled (not open for this decision)

  • Shared login across products is core to the platform thesis, not a nice-to-have. In your words: "probably the key to the whole thing." Waypoint's BRD already banks on it — the day-one experience assumes a RoundTrip TenantAdmin logs into Waypoint with no new password, and one of Waypoint's three stated success metrics is RoundTrip/Waypoint cross-sell. Any Keystone outcome has to preserve this, or deliberately and explicitly justify breaking it.
  • Relay's subscription model changes the shape of the problem. Relay is a multi-tenant CRM, and any of the three products — RoundTrip, Waypoint, Relay — can be subscribed to individually or in any combination. This means "shared login" cannot just mean "one login for everything" — it means one identity, with per-account, per-product entitlement determining what that identity can actually reach. A RoundTrip-only customer and a RoundTrip+Relay customer must hit the same login experience but end up with different access. Whatever identity approach Traxs lands on, product entitlement needs to be a first-class part of the identity/authorization model from day one — not something each product re-derives independently. This is real, new scope beyond what Entra is doing for RoundTrip alone today, and beyond what the old Keycloak-realm-per-tenant plan was ever designed for.

This isn't abstract — strategy-pricing-architecture.md makes the requirement concrete, revised 2026-08-22: three tiers per product, bundle discounts that key off how many products an account has (10% at two, 20% at the full suite, symmetric regardless of adoption order), a general pricing-change policy (list prices can change for existing customers with 90 days' notice and a real explanation — not a permanent freeze), and the Founders Program's price lock — a 5-year, not permanent, commitment that applies to products a founding tenant hasn't subscribed to yet, after which the account rolls onto the general policy like everyone else. That means entitlement can't just be "which products does this account have right now." It has to carry per-account, time-bounded pricing commitments (a rate-card snapshot with an expiry, not an open-ended lock) that apply to future subscriptions too, and it has to compute bundle-tier discounts as products get added or removed. Whatever ends up owning entitlement — Keystone itself, or a separate billing/entitlement service it delegates to — needs to be scoped against this real structure, not the simpler current-access-only version. This directly matters for the concrete architecture in Section 9: as currently scoped there, "product entitlement" means only current access — worth confirming explicitly that it's meant to grow to cover this before the spike gets built, not discovered as a gap afterward.


5. What's still genuinely open (the actual crux)

Four things, and all need real answers before this becomes a recommendation rather than a plausible idea:

Real, lived evidence worth weighing directly, added 2026-08-19 — from the original March 16-17, 2026 case study log capturing the Keycloak → Entra decision and migration: every identity choice made so far has come with real, substantial friction — just different kinds. Keycloak's was operational (Docker overhead, realm configuration, realm_access.roles transformation). Entra's was a string of undocumented CIAM surprises discovered only by hitting them directly — ROPC silently returning login-page HTML instead of failing cleanly, a non-obvious {client-id}/.default scope format, a required trailing slash on the Instance URL, Custom Security Attributes quietly not working despite having the correct role assigned. This reframes the actual question. It's not "will Keystone have friction" — every option so far has. It's "self-authored friction I have visibility into, or friction imposed by someone else's undocumented behavior." That's a meaningfully different, more answerable question than the abstract cost-of-ownership framing above.

  1. Realistic cost of ownership for a self-hosted, OpenIddict-based Keystone, honestly weighed against the ongoing cost of continuing to firefight Entra's B2B/CIAM edge cases. This is not just the original doc's licensing-cost comparison (Keycloak/OpenIddict were both free; Duende was the one ruled out on cost). The real comparison is operational: Keycloak was abandoned for becoming too complex to run solo, and OpenIddict is also something Traxs would build and operate entirely from scratch — no packaged admin UI, no built-in MFA/SSO/password-reset tooling, no realm console. Everything Keycloak and Entra gave for free (account recovery, brute-force protection, session management, admin tooling) would need to be built and maintained in-house. The open question is whether that's a smaller, more legible burden than what Entra is currently costing in bug-chasing, or whether it risks becoming "the next Keycloak" — technically sound but operationally too heavy for one person to sustain. entra-cost-tally.md is the running evidence file for this question — Side 1 (Entra's real historical cost, ~18 incidents over five months, now including a distinct admin-tooling friction category surfaced 2026-08-21) is done; Side 2 (a real, time-boxed OpenIddict spike) is still needed before this item can actually close. Update, 2026-08-23: Side 2 is now underway. keystone-spike-build-log.md is the running evidence file — dated entry per spike item, starting with item 1 (OpenIddict token issuance, complete as of this date).
  2. Whether the current shared-Entra-tenant / app-level tid-claim model is stable enough to build Relay on top of before this gets resolved either way. This is a stated open concern, not a settled fact. Building Relay on the current model and later migrating to Keystone is a different risk profile than resolving the identity question before Relay's first line of code.
  3. Keystone centralizes risk, not just infrastructure — from traxs-identity-seed.md. Today, if RoundTrip's Entra integration has a problem, Waypoint is completely unaffected — they're independent, even though Waypoint's BRD assumes shared credentials. A single Keystone service becomes a single point of failure for the entire platform at once — an outage or a security flaw in it doesn't degrade one product, it takes down (or exposes) all of them simultaneously. Not a reason to avoid building it — most successful multi-product platforms are built exactly this way — but it means the bar for "done" is meaningfully higher than for any one product's auth today, and that bar should be set explicitly before other products start depending on it. Section 8 proposes a first pass at this bar (a tiered minimum-safe/production standard) — proposed, not yet settled.
  4. A real, time-sensitive migration cost — not evaluative like the others, closing on its own schedule regardless of when a decision gets made. Entra never exposes the actual password hash to RoundTrip's own database — only Entra's own opaque credential store holds it, by design. There is no legitimate way to port that data into a self-hosted system. Any real migration means every existing user going through a one-time "set up your new login" event — a genuine UX cost, not just an engineering one. Currently free, because there are zero real paying customers today. That fact does not hold indefinitely — this is close to the cheapest this specific migration will ever be, and it gets categorically more expensive in real customer friction the moment a real paying customer exists. Worth weighing directly against Relay's build timeline, not treated as a background detail. See Section 8 for the concrete deadline this now has.

6. The real options on the table

None of these is a recommendation. They're the shapes the decision could actually take, each with what it would require and what it risks.

Option A — Fix the two known bugs, stay on Entra. Migrate ProvisionTenantJob to CreateMemberUserAsync and confirm/resolve the home-tenant-discovery redirect. Lowest near-term effort. Risk: if the redirect/collision problems are structural to Entra's CIAM model rather than fully explained by the guest-vs-member code path, this treats a platform-level risk as a one-off bug fix — and Relay would be built on the same foundation without the underlying question ever being answered.

Option B — Build Keystone now, before Relay exists. Stand up a self-hosted, OpenIddict-based identity service as the shared foundation for all three products, with product entitlement built in from the start, before Waypoint or Relay accumulate more dependency on the current model. Risk picture (see Section 2): RoundTrip carries the only real customer-facing migration risk today — real tenants, real data. Waypoint has a real, deployed Entra integration that would need reworking, but zero paying customers to disrupt while doing it. Relay has neither, since it doesn't exist yet. Combined with zero real paying customers on RoundTrip today (Section 5, item 4), this is genuinely close to the cheapest window this migration will ever have — not because there's nothing built elsewhere, but because nothing built elsewhere has real users yet either.

Option C — Fix the near-term bugs on Entra while evaluating Keystone in parallel, decide before Relay's identity work starts. Ships the immediate bug fixes without blocking on the bigger architecture question, but treats the Keystone evaluation (cost of ownership, entitlement design) as real, scheduled work with a decision deadline tied to Relay's timeline — not an open-ended "someday" question. Lower near-term risk than B, but only works if the evaluation is actually resourced and the deadline is real; otherwise this quietly becomes Option A by default.


Worth recording, since it came up directly and deserves a real reason for not pursuing it rather than silently dropping: Keystone as its own standalone product — selling identity-as-a-service to unrelated companies, not just using it internally across Traxs's own products.

The instinct to hold off is well-founded, not overcautious. Building identity for your own products and selling identity-as-a-service to other companies are fundamentally different categories of business. The moment RoundTrip authenticates other companies' end-users, it inherits their compliance exposure too — GDPR/CCPA obligations multiply across every company being served, breach liability extends to businesses that aren't Traxs's own, and the security bar real enterprise buyers expect from a dedicated identity vendor (SOC 2, real audited certifications) is much higher than what's needed to run one platform's own login.

Recommended sequencing: build it for Traxs's own internal use first, solving the real problem already in front of the platform. If it turns out excellent, "should this become its own product" is a legitimate question worth bringing to actual legal counsel later — with real evidence behind it instead of speculation. Same discipline as the bigger Keystone question itself: prove it small before deciding how big it gets.


8. Separating the urgent thing from the big thing, 2026-08-21

Real progress on the six open questions, worked through directly rather than left as an abstract list:

Relay is genuinely far out — no requirements gathering, no architecture, nothing. Waypoint's real technical Entra dependency exists (see the correction in Section 2/Option B) but currently carries zero real risk, no real customers to be disrupted by a future migration. RoundTrip is the only product under genuine, real time pressure right now.

This reframes Option C's real risk in a more honest, two-sided way. The original text worried Option C could quietly become Option A by default if the evaluation never gets properly resourced — implicitly assuming a deadline (Relay's timeline) would provide the forcing function. Relay's genuine distance removes that deadline entirely, which doesn't make the risk go away — it just means the deadline has to be self-imposed rather than inherited from Relay's schedule. Worth being explicit about that rather than assuming "no external pressure" means "safe to take our time."

The actual synthesis: separate the urgent thing from the big thing, explicitly, rather than let the big decision's complexity delay the urgent one.

  • Urgent, RoundTrip-specific, not blocked on the bigger question at all: run the CreateMemberUserAsync migration for real and get an empirical answer on whether it resolves TRA-384/389 (item 1 below). This protects the closing credential-migration window (item 5 below) regardless of which way Keystone eventually goes — worth scheduling as genuinely near-term work, not someday-research.
  • Big, genuinely unhurried, but given a real, self-imposed deadline rather than left open-ended — since Relay won't provide one the way the original framing assumed. The Side 2 OpenIddict spike (item 2 below) is this piece, and it needs an actual date attached to it, not just an intention.

Update, 2026-08-22 — the urgent item is now done

TRA-384/389 are closed, live-verified in both dev and production. ProvisionTenantJob migrated to CreateMemberUserAsync, the same pattern team invites already used successfully — no home-realm-discovery redirect, confirmed on the exact regression scenario (a fresh email on a real verified-M365 domain). Full detail in docs/INVESTIGATION-tra-384-389.md.

Worth being explicit about what this does and doesn't mean, so it isn't misread: this is the small, near-term fix (Option A-shaped, staying on Entra) — not the external-federation/OpenIddict architecture explored in Section 9. The urgent half of the split above is now genuinely closed. The big half — whether Traxs builds Keystone — remains fully open, with its January 2027 deadline unchanged. Today's success is a reason to feel good about the near-term protection working, not a reason the bigger question is any less worth resolving properly.

What "done" means before other products depend on it (item 6 below) — settled, 2026-08-22. Two tiers, not one vague standard, plus a third, RoundTrip-specific gate that sits between them.

  • "Ready to use" tier — the bar for Waypoint and Relay to build against, since for them this is a build, not a live cutover: OpenIddict issuing real tokens, real email/password login, the optional Entra-federation path working end to end, the ExternalLogins linking guardrails live, entitlement delegation to Command Center actually wired, a stable person ID other products can FK to for real, and some password-reset flow so nobody gets permanently locked out.
  • "Production" tier — the fuller bar for the platform's long-term operating standard, given that a shared identity service is a single point of failure for every product at once (Section 5, item 3): MFA, rate-limiting, real audit logging, proper session/token revocation, real uptime monitoring and alerting, a tested backup/recovery story for the credential store, and the unified admin app actually built. Ongoing rather than a box checked once — a standard kept, not a milestone hit.
  • RoundTrip's migration-readiness gate — the answer to "not too early, not too late," 2026-08-22. RoundTrip is live with real users, so moving it is a cutover, not a build — Entra never exposes the password hash, so every existing user goes through a real "set up your new login" moment (Section 5, item 4) regardless of which tier Keystone has reached. "Ready to use" alone isn't enough for RoundTrip specifically: its floor of "some account recovery" is fine for a product with zero live users and not good enough for one where a locked-out customer means lost trust and lost revenue in the moment. Waiting for the full production tier isn't the answer either — that tier may never fully close, and waiting for it means running two identity systems side by side indefinitely, undercutting the shared-login-across-products point that started this whole evaluation. The actual gate, specific to RoundTrip and layered on top of "ready to use": a tested (not just built) account-recovery flow, a real rollback path if the cutover goes wrong mid-migration, and the "set up your new login" user communication actually designed and ready. Sequencing that falls out of this for free: let Waypoint and Relay run on Keystone first — they're the natural audience for "ready to use" anyway — so Keystone gets genuine production mileage under real (if lower-stakes) traffic before the one migration where a mistake costs an actual customer. RoundTrip moves last, once both the bake-in period and its own readiness gate are satisfied, not first and not "whenever it feels done."

This is the settled answer to Section 5, item 3.

A real, concrete deadline (added 2026-08-21) — item 3's answer, made explicit rather than left open

This decision needs to be resolved by January 2027, tied to the actual target start of Relay's development. Not "Relay can wait indefinitely" — "Relay can wait, and here is the date this needs to be settled by."

Worth being honest that this date is conditional, not fully independent: it's tied to Waypoint reaching a genuine "finished enough to ship" milestone first. If Waypoint's own timeline moves, this deadline moves with it — worth revisiting explicitly if that happens, rather than letting January quietly stop being the real date without anyone noticing.

~4 months is genuinely comfortable time for the two real action items above (the CreateMemberUserAsync migration and the OpenIddict spike), not a rushed window — both are explicitly scoped as small/time-boxed, leaving real room for an honest decision afterward and, if Keystone is chosen, actual runway to start building before Relay needs it.

Command Center's own stated goal — enabling cross-product data sharing — is relevant broader context for why getting the platform's foundation right matters, though it's a related, distinct concern from identity specifically and doesn't change this deadline's calculation directly.


9. A concrete architecture emerged, 2026-08-22 — not a RoundTrip-only fix, the sharpest form this decision has taken yet

A separate exploration (started outside this Project, working through OpenIddict specifics with limited RoundTrip context) proposed a real, working pattern for fixing TRA-384/389 directly: RoundTrip owns its own primary login (email/password), with Microsoft Entra available only as an optional federation choice — a "Sign in with Microsoft" button, not the default, required path. Full technical detail preserved separately in keystone-external-login-proposal.md, since the actual code patterns are worth keeping intact for whenever the spike below gets built.

Why it actually resolves both tickets, precisely

TRA-389's real failure was never Home Realm Discovery existing — it was HRD firing silently, inside a hosted sign-in page, while someone believed they were just filling out a registration form. They never chose to authenticate via Microsoft; it happened to them. Moving Microsoft sign-in behind an explicit, clearly-labeled button changes the context completely — HRD still exists, but now only fires when someone deliberately chooses it, which is precisely how "Sign in with Microsoft" is supposed to work everywhere on the internet. Not a bug anymore; the correct behavior for a chosen action.

TRA-384 resolves for a related reason — primary registration becomes "create a row with a real password," never touching Entra's guest-invitation flow at all. The collision problem has nothing left to collide with.

The critical reframe — this is not "Option A, but smarter"

Initially easy to mistake as a scoped, RoundTrip-only quick fix — it isn't. It requires RoundTrip to own genuine, primary credential infrastructure (password hashing, reset flows, real session management), which is a materially different, bigger decision than "add a workaround for two bugs." Worth being honest that this arrived wearing a bug-fix's clothes but is actually the Keystone question itself, just now with a concrete, buildable shape instead of an abstract one.

The actual platform-level architecture this implies, worked through directly

Central and local pieces split cleanly, following the proposal's own core principle ("Microsoft's token proves who, your data decides what") one level up, applied to the whole platform rather than one login flow:

  • One central identity service (OpenIddict-based) owns only what's genuinely shared: the credential itself (password hash or linked external identity), a stable person ID, and — already established as non-negotiable, Section 4 — product entitlement (which products this person can actually reach).
  • Each product keeps its own fully local, product-specific tables, referencing the central ID via foreign key, nothing duplicated. RoundTrip's TenantUsers/Technician stay exactly as they are today. Waypoint and Relay get whatever's actually specific to them. Nothing about "Technician" needs to mean anything outside RoundTrip.

This is not "one shared mega-table" and not "each product builds its own separate auth" — both were live open questions this conversation resolved. Centralize identity and entitlement only; keep everything product-specific local.

What actually happens to Entra in this model — a real, load-bearing clarification

OpenIddict doesn't sit on top of Entra coordinating access — it becomes the authentication system. Entra is demoted to one optional external federation choice, exactly like a "Sign in with Google" button — not the required default path everyone still gets funneled through. This distinction is the entire reason the fix works: if Entra remained the default path in any sense, every user would still land on its hosted sign-in page and hit the exact same HRD bug being escaped. The fix only holds if Entra genuinely becomes optional, not just secondary.

How this sharpens Side 2's OpenIddict spike (Section 5, item 1)

No longer a vague "build minimal auth and see what it takes." Now concrete: OpenIddict as the central token issuer; the linking-not-coupling pattern (owned ExternalLogins collections, never silent account merging, verified-email-required linking) as the credential layer; and explicit confirmation that RoundTrip's existing TenantUsers/Technician split needs no restructuring — just a foreign key back to the new central identity. The spike's actual scope just got meaningfully smaller and better-defined than it was a day ago.

Entitlement delegation — resolved, 2026-08-22

The gap flagged here originally (entitlement scope narrower than Section 4 requires, unclear whether Keystone owns it or delegates it) is settled: Keystone delegates. Command Center (api.traxsgroup.com, already live) already names "unified billing hub" and "platform analytics" as its own long-term vision — this isn't a new service being invented, it's Command Center growing into a role it was already headed toward. Keystone owns credentials and a stable person ID only; Command Center grows into owning subscriptions, entitlement, and billing, and Keystone calls out to it the same way Command Center already calls RoundTrip today. Real consequence for Command Center: it has no database in v1 today (pure pass-through by design) — taking on entitlement/billing state is a genuine architectural step up for it, not a small addition, even though it's the direction it was already scoped to go.

Waypoint's EntraObjectId FK migration — settled, 2026-08-22

Went back to infrastructure.md and waypoint-brd.md directly rather than treating this as a one-line flag, and it turned out to be two separate things tangled together under one name:

The user-identity key (the actual "EntraObjectId FK migration" item) is small — settled. WaypointUsers.EntraObjectId is just the join key used to resolve which person is signed in. It follows the exact same pattern already designed for RoundTrip's TenantUser: add WaypointUserId.KeystoneUserId (the stable central person ID) as the new resolution key, retire EntraObjectId as a direct column. Correction, 2026-08-22, made while building keystone-db-design.md: no separate WaypointUserExternalLogins table is needed — that was written before centralization was fully worked through. Linking now happens once, centrally, on Keystone's own User/ExternalLogin tables; Waypoint (like RoundTrip) just references the central KeystoneUserId, nothing more. Zero live users means zero real data to migrate — this is a schema decision, not a backfill. No longer an open question.

A second, separate gap surfaced while checking this — logged independently, not folded in. WaypointUsers.TenantId and Workspaces.TenantId are today populated directly from the Entra tenant ID claim, not a Waypoint-generated identifier — infrastructure.md documents this as "One Entra tenant = One Waypoint workspace." Since Waypoint authenticates against the single shared roundtripapp CIAM tenant, that value is constant for every user — there's currently no mechanism for two real customers to get two different workspace IDs. It also contradicts waypoint-brd.md's original intent (ADR-009: reuse RoundTrip's own tenant concept for shared suite login, not derive it from Entra). This is a workspace-resolution problem, not an identity-provider problem — it would need fixing whether Waypoint stays on Entra or moves to Keystone, and it's independent of this decision. Logged as WAY-49 in Linear rather than tracked here, since it belongs to Waypoint's own backlog and shouldn't get lost inside a cross-product identity document. Not urgent today (zero live customers), but needs resolving before Waypoint's first real second customer.

The spike itself, scoped — 2026-08-22

Full scope in keystone-spike-scope.md, including a third piece that came out of this same conversation: one unified admin app (React + TypeScript, matching the Vite/Cloudflare Pages pattern already used for RoundTrip and Waypoint), not separate bespoke tools for identity administration, ops monitoring (TRA-386), and billing — talking to both Keystone and Command Center, and itself authenticated through Keystone as a real early test of the service. The admin app and Command Center's billing buildout are explicitly not part of the spike; the spike itself is scoped to prove OpenIddict token issuance, email/password auth, the optional-Entra-federation pattern, the ExternalLogins linking model, and RoundTrip's FK compatibility — nothing more.


10. What would turn this from framing into a recommendation

  1. A real answer on whether CreateMemberUserAsync resolves TRA-384/389done, 2026-08-22. Both tickets closed and verified (Section 3). The near-term fix and the long-term architecture question turned out to be exactly as separable as expected — TRA-384/389 closing doesn't resolve item 2 below.
  2. A concrete estimate — even rough — of what a minimum-viable Keystone actually takes to build and keep running solo, measured against actual hours spent on Entra firefighting over a comparable period. Sharper now than when this was written (Section 9): OpenIddict as central token issuer, the linking-not-coupling credential pattern, entitlement delegated to Command Center (resolved, Section 9), Waypoint's user-key migration settled (Section 9). This is the Side 2 OpenIddict spike, scoped in keystone-spike-scope.md. Underway as of 2026-08-23 — item 1 (token issuance) complete; see keystone-spike-build-log.md for the running record and item 5 below for the deadline.
  3. A decision on whether Relay's build timeline can accommodate Option B or Canswered directionally, 2026-08-21: Relay is genuinely far out with no forcing deadline of its own, so a self-imposed one was set instead (item 5 below). Still open in the narrower sense of which option actually gets chosen.
  4. Documentation of why Keycloak was abandoneddone, see Keycloak-decision.md (Section 2).
  5. A real decision on timing relative to the credential-migration window (Section 5, item 4) — answered, 2026-08-21: January 2027, tied to Waypoint reaching a "finished enough to ship" milestone and Relay's target start (Section 8). Conditional on Waypoint's own timeline holding — worth revisiting explicitly if that moves. Not yet reflected as a due date on TRA-401 in Linear — worth adding there too, so it's tracked the same way as everything else rather than only in this document.
  6. A deliberate answer on what "done" means for a centralized Keystone before other products depend on itdone, 2026-08-22. Two-tier bar (ready-to-use vs. production) plus a third, RoundTrip-specific migration-readiness gate and bake-in sequencing (Waypoint and Relay first, RoundTrip last). Full detail in Section 8.