Skip to main content

Keystone — Build Log

Status: Active. Tracks real, ongoing Keystone product development — the spike itself is complete and fully documented separately in keystone-spike-build-log.md (Entries 1–14) and synthesized in keystone-spike-results.md. This document picks up from the session immediately following that log's Entry 14, once it became clear the work had moved well past spike scope and deserved its own record.

Format: matches the spike log's own established structure — what was built, friction encountered, real decisions made, what's proven, time spent, and open items carried forward.


Entry 1 — Password reset test coverage closed out; signup orchestration design substantially advanced​

Date: 2026-09-11 Scope: closing out the last piece of Entry 14 (automated test coverage for password reset), then real design work on Command Center's POST /v1/signup orchestration from Entry 13 — including a genuine cross-team review with RoundTrip's own engineering, and a significant strategic discussion about migration sequencing that remains open, not decided.

1. Password reset — test coverage completed​

4 unit tests (Sha256PasswordResetTokenGeneratorTests — the token generator's own internal consistency), 4 integration tests (RequestPasswordResetCommandHandlerTests, ConfirmPasswordResetCommandHandlerTests — full lifecycle: success, garbage token, expiry, reuse, all substituting IOpenIddictTokenManager/ICommandCenterEmailClient rather than re-proving what was already confirmed manually against the real database in Entry 14), and 4 functional tests (RequestPasswordResetFunctionalTests, ConfirmPasswordResetFunctionalTests — real HTTP contracts, including genuine end-to-end proof via an actual subsequent login that the password really changed). 83 total tests, up from 69. Zero of the manual-testing friction from Entry 14 (the smart-quote curl failure, OIDC Debugger's silent auto-exchange, the inconclusive Swagger test) carried into the automated suite — it sidesteps HTTP tooling entirely by testing at the handler level and substituting the one genuinely external dependency.

Real, deliberate design simplification worth naming: the original test plan called for capturing and cross-verifying the exact plaintext token passed to the email client using NSubstitute's argument-capture syntax for Task-returning members — a pattern never used elsewhere in this project and not fully verified. Split into two simpler, lower-risk pieces instead: a unit test proving the token generator's own internal consistency, and an integration test confirming the email client was called with the right person's email and that a token row exists with the right shape. Together, equivalent real confidence, no unverified syntax risk.

2. Signup orchestration — real design work​

Real finding, caught before building anything wrong: ProvisionTenantJob is a Hangfire background job — structurally incompatible with the "no half-provisioned state" requirement, since Command Center can't guarantee it's actually finished by the time a request returns. This was reasoned through from the framing docs' own description before any RoundTrip code was seen directly, then confirmed by asking RoundTrip's own team directly rather than guessing.

RoundTrip's real, substantive response (command-center-tenant-admin-provisioning.md): the tenant-provisioning logic was extracted from the Hangfire job into a shared service, with a new synchronous POST /v1/tenant-admin/provision endpoint calling the same logic directly. Genuinely proven idempotent, not just assumed — RoundTrip's own account: a real mutation-testing pass caught that their first idempotency test didn't actually prove the claim (it passed even with the safety guard disabled, since a normal second call never exercised that code path), and a second, more targeted test was built specifically to force the real risk path. Two real open questions raised in their document, both resolved this session:

  • Entra identity resolution: RoundTrip assumed no pre-existing Entra identity exists for a new signup. Confirmed correct for our actual flow — email/password registration into Keystone directly, no Entra federation involved.
  • Authentication mechanism: resolved by reusing the existing X-CommandCenter-ApiKey convention in reverse (a new equivalent header, Command Center authenticating to RoundTrip rather than the reverse).

A real, significant consequence surfaced and explicitly not glossed over: accepting RoundTrip's design as-is means a new signup whose first product is RoundTrip ends up with two separate credentials — a real, working Keystone password, and a separate Entra identity (with its own temp password and invitation email) that's what actually grants RoundTrip access, since RoundTrip hasn't migrated to Keystone-based auth yet. Reasoned through as consistent with keystone-framing.md §8's own already-decided sequencing (RoundTrip migrates last, deliberately) rather than a flaw in this session's design — but named as a real, explicit tradeoff requiring sign-off, not silently accepted.

A real ambiguity found and resolved: RoundTrip's endpoint checks for a Tenant status (Provisioning/ProvisioningFailed) that doesn't exist anywhere in Command Center's real, current schema (confirmed against the actual Tenant table reviewed in Entry 13 — no status field at all). Resolved by reasoning through why that check likely exists (a security guard against arbitrary TenantId guessing) and concluding it's superseded entirely once real API-key authentication is in place — recommended dropping the check from RoundTrip's side rather than building a new status field into Command Center to satisfy it.

3. SignupAttempt schema, refined​

SignupAttempt
SignupAttemptId Guid PK
IdempotencyKey string, unique (client-generated, sessionStorage)
Email string
ProductSlug string
TierName string
TenantId Guid?, nullable (set once Tenant/Subscription created)
KeystoneUserId Guid?, nullable (set once Keystone credential created)
TenantUserProvisioned bool (set true once the product's admin-provisioning call succeeds)
CreatedUtc datetime
CompletedUtc datetime?, nullable

Real sequencing change from Entry 13's original design: the Keystone credential-creation step now happens before Tenant/Subscription creation, not after — deliberately, so the person's password only ever exists in memory for the duration of one request to Keystone, never persisted anywhere in Command Center's own database even transiently.

A known, accepted v1 gap, named explicitly rather than silently left: if Keystone's registration call succeeds but the response is lost before SignupAttempt records the returned UserId, a retry has no way to recover that ID — Keystone's real 409 Conflict response carries no UserId in its body, and no lookup-by-email endpoint exists today. Accepted as a known v1 limitation (surfaces as a real error asking the person to contact support) rather than building a new Keystone lookup endpoint to close a genuinely rare double-failure case — matching the same proportional-effort reasoning already used in Entry 14 for deferring async retry infrastructure.

4. Real strategic discussion — deliberately left open, not decided tonight​

A significant sequencing question surfaced and was substantially worked through, but explicitly not resolved — correctly treated as needing real thought, not a quick call:

  • Disabling RoundTrip's registration entirely, as a deliberate, reversible business decision — not just excluding RoundTrip from the new signup flow, but genuinely pausing new RoundTrip customers while real Keystone migration work happens, free of new-signup pressure. Reasoned as low-risk and reversible given RoundTrip has zero real paying customers today (the same "cheapest this migration will ever be" window keystone-framing.md has referenced from the start).
  • A real, previously-unstated piece of context that reframes migration sequencing: Waypoint's current auth was built as bare-minimum scaffolding, not something considered a keeper. This directly strengthens keystone-framing.md §8's own already-decided sequencing logic (Waypoint/Relay get real Keystone integration before RoundTrip) with concrete, current backing — Waypoint may have less working machinery to protect than assumed, making it a better candidate to be the first product the new signup flow actually offers.
  • Left genuinely open: whether to proceed with disabling RoundTrip registration, and whether Waypoint should be the first real product integrated with Keystone rather than RoundTrip. Both flagged explicitly as needing more thought, not decided under time pressure.

5. What's proven​

  • ✅ Password reset now has complete automated coverage across all three test layers — 83 total tests
  • ✅ RoundTrip's real, synchronous, genuinely-proven-idempotent provisioning endpoint exists and is reviewed
  • ✅ The SignupAttempt schema and step sequence are concrete and ready to build, pending the open strategic questions above
  • ✅ Both of RoundTrip's own open questions (Entra identity assumption, auth mechanism) resolved with real answers

6. Time — fill in actual hours​

CategoryActual time spent
Total, Entry 1___

7. Open items carried forward​

  • Real decision needed: disable RoundTrip registration now, and/or make Waypoint (not RoundTrip) the first product the new signup flow offers
  • The two-credential RoundTrip signup experience — needs explicit sign-off if RoundTrip proceeds as a signup option before its own Keystone migration
  • RoundTrip's own recommendation to drop the Provisioning/ProvisioningFailed tenant-status check — needs to actually happen on RoundTrip's side
  • The new X-RoundTrip-ApiKey-style header (or equivalent) — needs to actually be built on both sides
  • POST /v1/signup itself — fully designed in shape, not yet built
  • The corrected keystone-prd.md §3.11 — still not actually rewritten, despite being resolved in design since Entry 13
  • Everything else carried forward from keystone-spike-build-log.md's own final open-items list (Entry 14)

Entry 2 — Dedicated signup design document created; real payment architecture decided​

Date: 2026-09-12 Scope: no code — real design and documentation work. Created keystone-signup-orchestration.md as a consolidated, durable reference for the design scattered across the last few sessions, then used it as the working document while resolving two significant, previously-unaddressed questions: how Stripe actually fits into signup, and whether a card should be required.

1. keystone-signup-orchestration.md created​

Pete's own observation prompted this: the design had been scattered across keystone-spike-build-log.md Entries 12–13 and keystone-build-log.md Entry 1, with no single place showing the settled shape. New document built with real Mermaid diagrams (matching the convention already used in keystone-db-design.md/keystone-api-design.md), consolidating the overall flow, SignupAttempt retry mechanics, and RoundTrip's provisioning step into one reference — kept current as the design evolves, rather than another point-in-time snapshot.

2. A real gap found while reviewing the document together​

Working through the document together, a real inconsistency surfaced: the signup flow (as drawn) showed Tenant/Subscription being written as a plain, synchronous database operation, with no Stripe interaction anywhere — but command-center-database.md (Entry 13) had already decided Subscription.Status/StripeSubscriptionId are written by each product's own Stripe webhook handler, an asynchronous, event-driven path entirely absent from the signup design. Neither had been reconciled with the other.

3. Real decision — Stripe moves to Command Center​

Pete's own instinct, worked through and confirmed rather than just agreed with:

  • This reopens command-center-database.md's own prior decision to keep Stripe local to each product, which had explicitly named its own trigger for revisiting: "once Waypoint actually has real billing and there's a real second case to generalize from." The multi-product signup flow is that second case.
  • Two already-stated objectives make this concrete: command-center-brd.md §3's "one Stripe customer per tenant" goal is structurally incompatible with per-product Stripe integrations; the cross-product bundle discount logic requires visibility no single product's isolated integration has.
  • Timing: RoundTrip has zero real paying customers today, the same low-risk migration window already used to justify every other RoundTrip-side change in this project.
  • Not yet designed: the actual migration of RoundTrip's real, working Stripe integration (services-stripe.md) into Command Center. Pete confirmed comfort with fully rebuilding rather than needing to preserve implementation details.

4. Real decision — orchestration now triggers from Stripe's webhook, not form submission​

A direct consequence of bringing Stripe into the flow: the original design (orchestration starts the moment the form is submitted) risked real orphaned accounts if someone abandoned checkout before paying. Revised so nothing — not the Tenant, not the Keystone credential, nothing — gets created until Stripe's webhook confirms the charge succeeded. A real, honest UX tradeoff named explicitly: the person now waits briefly on a "finishing setup" screen after the Stripe redirect, rather than being instantly signed in.

Stripe Checkout (hosted) recommended over Stripe Elements (embedded) for the actual card-collection step — keeps traxsgroup.com from needing significant new capability (redirect out, handle the return), avoids PCI scope entirely, matches Stripe's own standard guidance for this exact scenario.

5. Real decision — card required at signup, backed by real research​

Surfaced a genuine tension with existing marketing copy (traxsgroup.com/roundtrips.app currently promise "no credit card needed"). Rather than decide on instinct alone, real research was pulled (web search, 2026-09-12) — two independent, credible primary sources (ChartMogul's 200-product 2026 study; First Page Sage's 86-client independent analysis) agree: card-required trials convert 2.5–3x higher, and produce roughly 3x more total paying customers per visitor despite fewer signups. The load-bearing caveat, taken directly from the source rather than assumed: this holds "unless the product warrants the friction" — reasoned as true for RoundTrip's considered-B2B-purchase profile, not a casual-trial consumer app.

Decided: card required, not deferred to end-of-trial. Marketing copy update needed on both sites — not yet done. No live-traffic risk in making the change, since neither site currently has a working signup flow generating real customers.

6. What's proven / resolved​

  • ✅ A single, durable, diagram-backed design reference now exists, consolidating what had been scattered across three prior entries
  • ✅ Stripe's real place in the architecture resolved, with the "why now" reasoning made explicit and traceable
  • ✅ The orchestration trigger corrected to webhook-driven, closing a real gap (orphaned accounts on abandoned checkout) the original design hadn't accounted for
  • ✅ The card-required-vs-no-card question resolved with real, cited data rather than instinct alone

7. Time — fill in actual hours​

CategoryActual time spent
Total, Entry 2___

8. Open items carried forward​

  • The actual Stripe-to-Command-Center migration design (API keys, Price IDs, webhook endpoint, real event handling) — not yet started
  • Marketing copy updates on traxsgroup.com/roundtrips.app reflecting card-required signup — not yet done
  • Everything from Entry 1 (RoundTrip registration disable, Waypoint-vs-RoundTrip sequencing, POST /v1/signup build, keystone-prd.md §3.11 rewrite) — untouched this session

Entry 3 — Real Waypoint auth review; the "bare-minimum scaffolding" assumption corrected​

Date: 2026-09-12 Scope: to actually answer the Waypoint-vs-RoundTrip sequencing question left open in Entry 1, requested and reviewed Waypoint's real, complete source (API + frontend) rather than continue reasoning from secondhand recollection.

Full findings written up separately: waypoint-auth-review.md — kept as its own document given the real weight of what turned up, rather than buried in this entry.

Summary of what changed​

The working assumption going into this ("bare-minimum scaffolding, not attached to it") was corrected by direct evidence: Waypoint is a substantial, real Clean Architecture application (60+ endpoints, five bounded contexts, real MSAL frontend) with a working — if flawed — multi-tenant identity system underneath it, not a thin shell.

Real findings, in order of significance:

  1. A live-looking Microsoft Graph client secret found in plaintext in appsettings.Development.json — flagged urgently, separate from everything else, likely local-only but worth confirming
  2. WAY-49 confirmed directly (not secondhand) — Waypoint's real config points at RoundTrip's own shared CIAM tenant, and tenant resolution is independently re-implemented from the Entra claim in two separate places
  3. Zero role-based authorization anywhere in the entire endpoint layer — a real, independent security gap
  4. CurrentUserService never resolves an actual WaypointUser row, only raw JWT claims
  5. The user-invite flow uses Entra's guest-invitation API — the same pattern RoundTrip's own TRA-223 already found unreliable and replaced

A real, important correction made explicitly: "ripping out auth and replacing it with Keystone" does not automatically close all of these gaps. WAY-49 and the exposed-secret risk are genuinely, structurally resolved by the migration. Zero authorization and the missing WaypointUser lookup are not — those are separate, must-be-explicit work items within the migration's scope, unrelated to which service issues the token. The guest-invite pattern is fixed only if the invite flow itself gets rebuilt, not merely if login is swapped.

Where this leaves the sequencing question: the case for Waypoint-first is arguably stronger now, just for a different reason than originally assumed — not "cheap because there's nothing there," but "worth doing now because real, independent problems get fixed as part of it," provided the migration is explicitly scoped to include the authorization and user-resolution work rather than treating it as an automatic byproduct.

Time — fill in actual hours​

CategoryActual time spent
Total, Entry 3___

Open items carried forward​

  • Confirm the exposed Graph secret's actual git status and rotate if warranted
  • The real Waypoint-Keystone migration itself — not yet scoped, now with explicit required work items (authorization, user resolution) identified
  • Whether to fix the zero-authorization gap and/or the guest-invite pattern independently of and before any Keystone migration, given they're real problems on their own
  • Everything else carried forward from Entry 2

Entry 4 — keystone-prd.md fully reconciled; real service-to-service provisioning endpoint built and proven; UserId value object refactor complete​

Date: 2026-09-13 Scope: three real, separate pieces of work — closing out documentation drift that had been sitting open since Entry 13, building Keystone's first non-cookie/non-OAuth auth mechanism from scratch, and a platform-wide consistency refactor.

1. keystone-prd.md reconciled, in full​

What started as "just fix §3.11" turned into a fuller pass once the real, current file was reviewed directly — several sections had drifted from reality independently of the entitlement question: stale /account/tenant-login/* route paths, the outdated inviteToken/personId registration contract, and Sections 3.8–3.10 still marked "Proposed" despite password reset (including real, database-confirmed revocation) having been fully built and proven in a later session. All corrected in one pass, version bumped to 1.1 with a real document-history entry describing the scope of the fix. Section 3.11 itself rewritten to state plainly that entitlement delegation is not Keystone's responsibility at all — retained only so a future reader doesn't rediscover the same incorrect assumption.

2. POST /v1/accounts/provision — Keystone's first service-to-service endpoint, built and proven​

Real design decision, reasoned from direct platform precedent: rather than let Command Center's signup orchestration call the existing browser-facing /account/register, built a distinct endpoint reusing the same underlying RegisterWithPassword-equivalent logic. Followed RoundTrip's own real precedent exactly — its POST /v1/tenant-admin/provision endpoint solves the identical shape of problem (a browser-facing flow and a separate, synchronous, API-key-authenticated orchestration entry point over the same domain logic). Three concrete reasons this was the right call, not just extra ceremony: audience/trust-tier mismatch with the PRD's own stated model, a real forward-looking risk (future anti-bot measures on the public endpoint would silently break a service integration coupled to it), and rate-limiting design targeting per-person abuse, which a single server-side caller acting on behalf of many people would statistically look nothing like.

A real, worthwhile improvement over the pattern being followed: keystone-signup-orchestration.md §4 had explicitly accepted a known gap — a 409 from the browser endpoint carries no UserId, so a lost-response retry can't recover it. Since this was a brand-new endpoint with no existing contract to preserve, that gap was closed outright rather than carried forward: the new endpoint returns 200 with wasAlreadyRegistered: true/false instead of ever returning 409, always including the real UserId either way.

Built alongside it: Keystone's first real API-key authentication scheme (ApiKeyAuthenticationHandler, constant-time comparison, matching the platform's existing X-CommandCenter-ApiKey service-to-service convention in shape, renamed to X-Keystone-ApiKey for this direction).

Real friction, working through it start to finish:

  • An accidental self-inflicted bug during editing (AddAuthorization() deleted while intending to touch AddAuthentication()) produced a real, correctly-diagnosed-from-the-error-text InvalidOperationException — resolved directly from the exception's own remediation text, no guessing needed
  • A hardcoded header-name constant (X-CommandCenter-ApiKey) left stale after the header itself was renamed to X-Keystone-ApiKey — caused uniform 401s across every test case regardless of key correctness, correctly diagnosed by reasoning through why all four cases failed identically rather than just the expected ones
  • A real config-key mismatch (ApiKeys:CommandCenter in Keystone's own settings vs. ApiKeys:Keystone expected by the handler as written) — found and fixed independently
  • A namespace/folder mismatch on the handler file, correctly identified as real but non-causal (C#'s namespace declaration is independent of physical file location; the class was reachable the whole time) — a worthwhile question to ask and verify rather than assume either way

Fully proven end to end: missing key rejected (401), wrong key rejected (401), correct key on a new email succeeds (200, wasAlreadyRegistered: false), and — the real proof of the design's core guarantee — an identical retry against the same email returns the same UserId with wasAlreadyRegistered: true, confirmed by matching GUIDs across both calls.

3. UserId value object — platform consistency refactor, complete​

Wrapped User's identity in a real UserId value object, matching RoundTrip's TenantUserId/TechnicianId and Waypoint's WaypointUserId conventions exactly — closing a gap flagged as far back as Entry 11 (the spike's plain-Guid approach was never a deliberate divergence from platform convention, just never revisited).

Real design decision, reasoned rather than assumed: the wire format never changes. Every endpoint continues to accept/return a bare Guid string exactly as before — confirmed as the right call by direct precedent (RoundTrip's own real provisioning endpoint wraps TenantId internally but still exposes a plain string over HTTP). UserId exists purely inside Core/Infrastructure; Web's DTOs stay untouched, unwrapping via .Value only at the boundary. Zero contract risk to the just-proven /v1/accounts/provision integration.

Deliberately scoped narrow: only User's own identity got wrapped — ExternalLogin.Id and PasswordResetToken.TokenHash stay plain, since neither crosses an aggregate or service boundary the way UserId does (it's the token's own sub claim).

A real, useful design detail worth remembering: UserId.ToString() was deliberately overridden to return the plain Guid string, specifically so call sites like RevokeBySubjectAsync(user.Id.ToString(), ct) needed no change at all — most of the refactor's real work concentrated at explicit API-boundary unwrap points, not scattered everywhere.

A real, correctly-resolved question about EF Core migrations: no PendingModelChangesWarning appeared after this change, and after reasoning through it directly rather than assuming a migration was needed regardless — confirmed correct. Entry 10's ValueGeneratedNever() finding was about runtime change-tracking behavior; this change only affects the value converter EF Core uses to translate between the .NET type and the database value. The actual column (uniqueidentifier, non-nullable, ValueGeneratedNever()) is byte-for-byte unchanged, so there was genuinely nothing for a migration to capture — the absence of a warning was the correct signal, not a gap.

4. What's proven​

  • ✅ keystone-prd.md fully matches reality across every section, not just the one originally targeted
  • ✅ POST /v1/accounts/provision built, secured, and proven idempotent end to end — Command Center's signup orchestration has a real, tested endpoint to call
  • ✅ UserId refactor complete across every layer (Core, Infrastructure, UseCases, Web, all four test projects), clean build, all tests passing, zero wire-contract changes

5. Time — fill in actual hours​

CategoryActual time spent
Total, Entry 4___

6. Open items carried forward​

  • Project: Command Center — build the outbound HTTP client call to Keystone's POST /v1/accounts/provision, attaching Keystone:ApiKey as the X-Keystone-ApiKey header (carried over to platform-action-items.md)
  • Unlink External Login (DELETE /account/external-logins/{provider}) — PRD §3.7, still not built
  • Everything else carried forward from Entry 3

Entry 5 — Unlink External Login built and proven; a real tenant-ownership question correctly deferred to Command Center​

Date: 2026-09-14 Scope: the last remaining item from Keystone's own PRD punch-list — DELETE /account/external-logins/{provider} (PRD §3.7), previously only a raw data-access pattern in the original proposal doc, never built as a real, tested endpoint.

1. A real architecture question surfaced before any code — correctly routed, not built around​

Scoping the feature ("self-service, account holder only") prompted a genuinely important tangent: does the platform need a stronger-than-TenantAdmin "Owner" concept — someone who can't be demoted or removed, and who alone can designate a new TenantAdmin, closing a real business-continuity gap where nothing today stops a tenant's last admin from being removed and leaving nobody able to manage it?

Correctly identified as structurally out of Keystone's reach, for the same reason entitlement was: Keystone has no Role concept at all — TenantAdmin is RoundTrip's own local concept, Waypoint has its own separate WaypointRole. The real design question is where a cross-product "ownership" fact should live. Two options reasoned through: per-product (each role enum grows an Owner value, duplicating protection logic three times) versus centralizing it once on Command Center's own Tenant record (e.g., Tenant.OwnerId referencing KeystoneUserId, since ownership is a fact about the account holder, not about any one product). Recommended the latter, but deliberately not decided or built here — flagged in platform-action-items.md as a real design question for Command Center's own context, correctly kept separate from tonight's actual build so it doesn't block or get conflated with Unlink itself.

2. DELETE /account/external-logins/{provider} — built​

UnlinkExternalLoginCommand/Handler wraps the already-existing, already-unit-tested User.RemoveExternalLogin domain method — no new domain logic, just real authentication and wiring. The caller's own UserId is resolved from their authenticated cookie's sub claim, never from a caller-supplied identifier, so there's no way to unlink someone else's account by guessing an id. DomainException (the aggregate's own "would leave the account with no way to authenticate" guardrail) is caught locally and surfaced as a real 400, matching the established pattern from ConfirmPasswordResetCommandHandler.

A genuinely instructive FastEndpoints bug, worth keeping as a real lesson: Send.OkAsync(ct) (a single positional CancellationToken argument) compiled cleanly but resolved to the wrong overload at runtime — the compiler inferred a generic SendOkAsync<TResponse> and treated ct itself as the response body to serialize, rather than matching the non-generic "200, no body, just a token" overload that (per Rider's own listing) should have been the more natural match. The failure surfaced as a real, confusing NotSupportedException trying to serialize CancellationToken's internal IntPtr handle. Root cause not fully explained — normally a non-generic overload is preferred over inferring a generic one in C#'s resolution rules, so something about FastEndpoints' specific Send type is behaving less standard than expected; this wasn't chased down further. Fixed with a named argument — Send.OkAsync(cancellation: ct) — which forces the compiler to match the overload whose parameter is actually named cancellation, excluding the generic one from consideration. Worth remembering as a general pattern: name arguments explicitly when a call site touches an overload set that mixes generic and non-generic methods.

Testing note: proved the full auth/wiring chain — no-cookie rejected via redirect to login (expected, standard cookie-auth behavior, not a bug), valid cookie correctly resolves the caller's own account, and a no-op unlink (provider never linked) returns a clean 200. The actual guardrail (can't remove your only credential) was not re-exercised end-to-end this session — deliberately trusted based on existing unit coverage on User.RemoveExternalLogin itself, rather than performing a full real Entra link-then-unlink cycle to re-prove logic that already has real test coverage.

A real, open design question surfaced but not resolved: cookie authentication's standard behavior (redirecting an unauthenticated request to the login page rather than returning a bare 401) may not be the right shape for this specific endpoint, since it's more likely to be called from a future account-settings UI's fetch() than navigated to directly. Not fixed tonight — flagged for whenever real UI work against this endpoint happens.

3. What's proven​

  • ✅ DELETE /account/external-logins/{provider} built, authenticated, and proven for the common path
  • ✅ Keystone's own PRD punch-list (§3.7) is now fully closed — every endpoint in the document is either proven or explicitly still-proposed for the production tier, nothing left half-specified
  • ✅ A real, cross-product architecture question correctly identified and routed to its proper home rather than decided in passing or built around

4. Time — fill in actual hours​

CategoryActual time spent
Total, Entry 5___

5. Open items carried forward​

  • Project: Command Center — the tenant-ownership design question (§1 above), added to platform-action-items.md
  • Whether Unlink's cookie-redirect-on-401 behavior needs reconsidering once real account-settings UI work happens
  • The full guardrail path (removing your only credential) — never manually re-verified end-to-end against this new endpoint specifically, resting on existing aggregate-level unit coverage
  • Everything else carried forward from Entry 4

Entry 6 — Cross-project confirmation with Command Center; a stale limitation corrected​

Date: 2026-09-15 Scope: no new code — reviewed command-center-keystone-integration-summary.md, a real handoff document from Command Center's own conversation confirming its actual, tested integration against Keystone's endpoints. First real test of the cross-project shared-repo workflow set up a couple sessions back, working as intended: Command Center's session independently verified its assumptions against Keystone's real, tested behavior rather than trusting the original design docs blindly, and surfaced a genuine, worthwhile question back.

1. Confirmed, mutually consistent​

  • /v1/accounts/provision's real request/response contract, exactly as built and tested in Entry 4 — {email, password} in, {userId, wasAlreadyRegistered} out, always 200, never 409. Independent confirmation from the calling side, not just trust in the original design.
  • GET /v1/entitlements's real contract and the structural reasoning for why Keystone can't call it — matches Entry 13's own reasoning exactly, arrived at independently from Command Center's own side.

2. A real, worthwhile question, resolved with a precise answer​

Command Center's session asked whether keystone-signup-orchestration.md §4's previously-documented retry-recovery limitation (a lost response before KeystoneUserId is recorded leaves no way to recover it) still applies now that /v1/accounts/provision exists, given every observed call — first attempt or retry — returns a real UserId in its body.

Resolved: yes, fully fixed, for any number of retries, not just a second one. The handler's existence check is a live database lookup by email on every call, not an attempt counter — there's no state that could make it stop working on a third or further retry. keystone-signup-orchestration.md §4 updated to state this plainly, replacing the "known, accepted v1 gap" language that was accurate for /account/register but stale once /v1/accounts/provision became the real integration point. A different, lower-probability gap named in its place: the check-then-create sequence is technically racy under genuine concurrency (two simultaneous calls could both pass the existence check before either inserts) — accepted as out of scope, since Command Center's own retries are sequential from one orchestrator, never concurrent.

3. A stale reference caught in the other direction​

Command Center's summary described keystone-prd.md §3.11 as still needing its rewrite — that rewrite already happened, in Entry 4, before /v1/accounts/provision even existed. Flagged back to Command Center's session directly; nothing further needed on Keystone's side. Genuinely good, independent double-confirmation that both sides reasoned to the identical real answer about why Keystone can't call the entitlements endpoint, even though each session reached it from a different starting point.

4. What's proven​

  • ✅ The cross-project shared-knowledge workflow is genuinely working as designed — a real, substantive technical question got resolved precisely, without either session needing to re-explain context the other already had access to
  • ✅ keystone-signup-orchestration.md now accurately reflects the real, current state of the retry-safety guarantee, not a stale pre-implementation assumption

5. Time — fill in actual hours​

CategoryActual time spent
Total, Entry 6___

6. Open items carried forward​

  • Everything from Entry 5 — nothing here changes any of it