Skip to content

Moving oauth & flow consumable interfaces to pkg#2906

Draft
anushasunkada wants to merge 4 commits into
thunder-id:mainfrom
anushasunkada:local_main
Draft

Moving oauth & flow consumable interfaces to pkg#2906
anushasunkada wants to merge 4 commits into
thunder-id:mainfrom
anushasunkada:local_main

Conversation

@anushasunkada
Copy link
Copy Markdown
Contributor

@anushasunkada anushasunkada commented May 21, 2026

Purpose

Approach

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Introduced public Thunder ID engine package enabling developers to embed OAuth 2.0/OIDC authorization server and flow execution runtime directly in Go applications.
    • Added comprehensive documentation on engine configuration, host provider setup, and integration patterns.
  • Documentation

    • New guide covering embedding Thunder's OAuth and flow capabilities in custom applications.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 945f9619-8cc8-45a1-aea7-a51de3e1a823

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a public embedding API for OAuth/flow runtime, provider bridges, host-backed stores, executor init refactor, OAuth engine initializer, server runtime wiring via enginebridge, tests validating route registration and stores, and docs.

Changes

Embedded Engine and Runtime Wiring

Layer / File(s) Summary
Engine bootstrap, bridges, runtime stores, flow/oauth refactors, server wiring, tests and docs
backend/pkg/thunderidengine/*, backend/internal/enginebridge/*, backend/internal/oauth/**/*, backend/internal/flow/**/*, backend/cmd/server/servicemanager.go, docs/*, backend/tests/integration/*
Public embed surface with engine initialization; bridges for client/authn/authz/resource/OU/IDP/observability; host runtime stores for PAR/AuthZ/flow context; OAuth engine initializer; PAR/AuthZ store interfaces/adapters; flow context interface refactor and host store; server runtime registration; executor init refactor; tests and docs.

Sequence Diagram(s)

sequenceDiagram
  participant HostApp
  participant ThunderEngine
  participant EngineBridge
  participant FlowExec
  participant OAuthEngine
  HostApp->>ThunderEngine: Initialize(mux)
  ThunderEngine->>EngineBridge: Initialize(config, mux)
  EngineBridge->>FlowExec: Initialize(opts)
  EngineBridge->>OAuthEngine: InitializeEngine(deps)
  OAuthEngine-->>HostApp: /.well-known, /oauth2, PAR, authz
  FlowExec-->>HostApp: /flow/meta, /flow/execute
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

skip-changelog

Suggested reviewers

  • ThaminduDilshan
  • darshanasbg
  • thiva-k
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@anushasunkada anushasunkada marked this pull request as draft May 21, 2026 02:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (3)
backend/internal/enginebridge/bridge_client.go (1)

101-105: ⚡ Quick win

Return a populated certificate error instead of an empty one.

At Line 104, returning &inboundclient.CertOperationError{} hides the actual failure reason. Please return a populated not-implemented error payload so callers can log/branch correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/enginebridge/bridge_client.go` around lines 101 - 105, The
GetCertificate method currently returns an empty
&inboundclient.CertOperationError{} which hides the failure reason; update
clientBridge.GetCertificate to return a populated CertOperationError indicating
"not implemented" (set available fields such as Message or Error and a Code or
Type field to "NotImplemented" or similar) so callers can log/branch correctly
and see the intent that GetCertificate is unimplemented.
backend/pkg/thunderidengine/engine.go (1)

30-32: ⚡ Quick win

Make bootstrap registration single-assignment.

RegisterBootstrap can currently be called multiple times (or with nil), which can silently replace/disable engine wiring process-wide. Consider guarding registration to the first non-nil value.

Proposed change
 package thunderidengine
 
-import "net/http"
+import (
+	"net/http"
+	"sync"
+)
@@
 var registeredBootstrap bootstrapFunc
+var registerBootstrapOnce sync.Once
@@
 func RegisterBootstrap(fn bootstrapFunc) {
-	registeredBootstrap = fn
+	if fn == nil {
+		return
+	}
+	registerBootstrapOnce.Do(func() {
+		registeredBootstrap = fn
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/pkg/thunderidengine/engine.go` around lines 30 - 32,
RegisterBootstrap currently overwrites registeredBootstrap on every call; change
it to perform a single-assignment of the first non-nil bootstrapFunc and ignore
subsequent calls (and nil args) to avoid accidental replacement. Implement this
by guarding the assignment with a check that registeredBootstrap is nil and fn
!= nil (or use a sync.Once named bootstrapOnce and call bootstrapOnce.Do to set
registeredBootstrap) so the first non-nil fn is preserved and further
RegisterBootstrap calls are no-ops; keep the symbol names RegisterBootstrap,
registeredBootstrap, bootstrapFunc and make the behavior concurrency-safe.
backend/tests/integration/providers.go (1)

131-145: ⚡ Quick win

Use unique PAR keys in the in-memory runtime store.

Store currently keys PAR entries as urn:par:<clientID>, so subsequent requests from the same client overwrite prior ones. Generating unique keys better matches real PAR behavior and improves integration-test fidelity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/integration/providers.go` around lines 131 - 145, The Store
method in memoryRuntimeStore currently uses a non-unique key
"urn:par:"+parRequest.ClientID which causes overwrites; update
memoryRuntimeStore.Store to generate a unique key (e.g., UUID or
timestamp+random) such as "urn:par:<unique-id>", store the parRequest in m.par
under that generated key, and return that key; keep newMemoryRuntimeStore and
the m.par map unchanged but ensure the generated key format is used wherever
callers of Store expect the returned key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ARCHITECTURE.md`:
- Line 31: Rename the markdown heading text "Embeddable engine
(`pkg/thunderidengine`)" to a Vale-friendly variant (for example "Embeddable
Engine (`pkg/thunderidengine`)" or "Embeddable runtime (`pkg/thunderidengine`)")
to resolve the lint spelling warning; update the exact heading string in
ARCHITECTURE.md so the new title replaces the original "Embeddable engine
(`pkg/thunderidengine`)" occurrence and ensure the package reference
`pkg/thunderidengine` remains intact.

In `@backend/internal/enginebridge/bootstrap_integration_test.go`:
- Around line 41-50: The test currently calls
Initialize(thunderidengine.EngineConfig{ConfigPath: serverHome}, mux) and skips
on any error which hides real regressions; change the test to require no error
from Initialize (use require.NoError or equivalent on the Initialize call) so
initialization failures make the test fail, then perform the HTTP request
against mux and assert a successful metadata response (assert rec.Code ==
http.StatusOK and check content-type is application/json or that the response
body contains expected OpenID metadata keys like "issuer" or
"authorization_endpoint") instead of only asserting non-404; update assertions
around Initialize, mux.ServeHTTP, and the recorder to enforce success.

In `@backend/internal/enginebridge/bootstrap.go`:
- Around line 64-67: The code creates a private http.NewServeMux when the
incoming mux is nil, which hides any registered routes; change Initialize to
fail fast instead: check the incoming mux parameter and if it's nil return an
error (or the existing error-handling path) instead of creating a private
routeMux, so callers know they passed nil; update logic referencing routeMux and
mux in Initialize to remove the private ServeMux fallback and surface an
explicit error when mux == nil.

In `@backend/internal/flow/flowexec/host_runtime_store.go`:
- Around line 32-60: NewContextStoreFromRuntime currently returns a
hostContextStore that dereferences s.host in methods (StoreFlowContext,
GetFlowContext, UpdateFlowContext, DeleteFlowContext) and will panic if the
provided RuntimeStore is nil; change the constructor to validate the host
argument and return an error or a no-op implementation when host is nil, or
store an explicit nil-check flag, and update each method to check s.host != nil
and return a clear error (e.g., fmt.Errorf("nil runtime store") or a
package-level sentinel error) instead of panicking; ensure GetFlowContext still
returns (nil, nil) when appropriate and keep conversions
(toPublicFlowContext/fromPublicFlowContext) unchanged.

In `@backend/internal/flow/flowexec/init.go`:
- Around line 75-86: The custom ContextStore path in resolveFlowContextStore
currently still initializes a runtime DB transactioner for non-Redis runtimes;
change resolveFlowContextStore so that whenever opts != nil && opts.ContextStore
!= nil it immediately returns opts.ContextStore with
transaction.NewNoOpTransactioner() (do not call dbprovider.GetDBProvider() or
GetRuntimeDBTransactioner()), so a provided ContextStore does not force
initialization of the runtime DB transactioner.

In `@backend/internal/flow/mgt/runtime_flow_service.go`:
- Around line 170-186: hostFlowToComplete should validate def.FlowType before
constructing CompleteFlowDefinition: convert the raw value with
common.FlowType(def.FlowType) and reject unknown/unsupported values (e.g.,
zero/unknown enum) rather than accepting them silently. Update
hostFlowToComplete to check the mapped flowType against the known/allowed values
(use a small switch, a common.IsValidFlowType helper, or explicit list of
common.FlowType constants) and return an appropriate service error (e.g.,
serviceerror.InvalidArgument or a new ErrorInvalidFlowType) when invalid; only
then populate CompleteFlowDefinition.FlowType with the validated
common.FlowType.

In `@backend/internal/oauth/engine_init.go`:
- Around line 84-87: The local creation of a new http.ServeMux when deps.Mux is
nil causes an orphaned mux; instead make deps.Mux required by replacing the nil
branch with an explicit error return (do not call http.NewServeMux()), e.g.
check deps.Mux and if nil return an error from the enclosing init function (the
function that contains "mux := deps.Mux"), update callers to handle the error,
and remove any code that registers routes on the locally-created mux; reference
the symbols deps.Mux and mux to locate and change the behavior.

In `@backend/internal/oauth/oauth2/authz/host_runtime_store.go`:
- Around line 176-180: The json.Unmarshal error for pub.ClaimsRequestJSON in
fromPublicAuthorizationCode is being discarded; capture the error (err :=
json.Unmarshal(pub.ClaimsRequestJSON, &claims)) and handle it: if
pub.ClaimsRequestJSON is non-empty and err != nil, record/log the error (e.g.,
using the package/logger available in this component) or set a clear sentinel on
the returned AuthorizationCode indicating invalid claims instead of silently
leaving claims nil; ensure you reference fromPublicAuthorizationCode,
pub.ClaimsRequestJSON, claims and the unmarshalling call so reviewers can find
and validate the change.
- Around line 154-156: The toPublicAuthorizationCode function currently discards
the error from json.Marshal(code.ClaimsRequest) which can drop ClaimsRequest
silently; update to handle the marshal error consistently with
marshalOAuthParameters by either (a) changing toPublicAuthorizationCode's
signature to return (thunderidengine.AuthorizationCode, error) and propagate the
json.Marshal error, or (b) keep the signature and log a warning via the same
logger used elsewhere and set ClaimsRequest to nil/empty on marshal failure;
locate toPublicAuthorizationCode and the json.Marshal call and implement one of
these fixes so the error is not silently ignored and behavior matches
marshalOAuthParameters.

In `@backend/internal/oauth/oauth2/authz/init.go`:
- Around line 70-85: Defer calling initializeAuthorizationStores until defaults
are actually needed: if opts is nil, call initializeAuthorizationStores() and
return defaultCode, defaultRequest, transactioner; if opts is non-nil and both
opts.CodeStore and opts.RequestStore are non-nil, return opts.CodeStore,
opts.RequestStore, nil (no transactioner initialization); otherwise call
initializeAuthorizationStores(), then substitute only the missing stores into
defaultCode/defaultRequest and return the resolved codeStore, requestStore and
transactioner. Use the existing identifiers initializeAuthorizationStores,
defaultCode, defaultRequest, transactioner, opts, CodeStore and RequestStore to
locate and update the logic.

In `@backend/internal/oauth/oauth2/par/init.go`:
- Around line 53-56: The code currently calls initializePARStore()
unconditionally which initializes the default PAR store even when an override is
provided; change the logic so initializePARStore() is only invoked when no
override is supplied: check opts and opts.Store first and use opts.Store if
present, otherwise call initializePARStore() to assign store (referencing the
initializePARStore function, the opts variable and opts.Store field and the
store local variable).

In `@backend/tests/integration/engine_test.go`:
- Around line 55-57: The test is skipping on any non-nil err after the
deployment file check which hides Initialize failures; replace the t.Skip call
(where err is checked) with a hard failure such as t.Fatalf (or t.Fatalf-like
usage) so the test fails and prints the actual error (e.g., use t.Fatalf("engine
bootstrap requires provisioned server home: %v", err)). Locate the err check
where t.Skip is used and change it to fail the test and include err in the
message.

In `@docs/content/guides/deployment-patterns/embed-thunderidengine.mdx`:
- Line 5: Replace all hardcoded occurrences of "Thunder" and "ThunderID" in this
document: in the YAML frontmatter swap product name values to use
{{ProductName}}; in prose, headings and any JSX use the component form
<ProductName />; and inside fenced code blocks or inline code (non-symbol text)
use {{ProductName}}. Search for the literal strings "Thunder" and "ThunderID"
(including in frontmatter, paragraph text, headings, JSX fragments and code
blocks) and update each instance accordingly so frontmatter uses
{{ProductName}}, prose/JSX uses <ProductName />, and code blocks/inline code use
{{ProductName}}.
- Line 33: Replace the incorrect Vale term "UserInfo" in the table row string
"`GET` / `POST /oauth2/userinfo` | UserInfo" with the correct "Userinfo"; locate
the table entry in embed-thunderidengine.mdx (search for the exact text "`GET` /
`POST /oauth2/userinfo`") and update the right-hand cell value to "Userinfo" so
the Vale linting passes.

In `@docs/sidebars.ts`:
- Around line 406-410: The new sidebar entry with id
'guides/deployment-patterns/embed-thunderidengine' and label 'Embed OAuth and
Flow Engine' is placed as a top-level sibling; move it into the nested array for
the 'Deployment Paths' section so it appears under 'Deployment Paths' ->
'Deployment Patterns'. Locate the object that defines the 'Deployment Paths'
section and insert the { type: 'doc', id:
'guides/deployment-patterns/embed-thunderidengine', label: 'Embed OAuth and Flow
Engine' } entry inside its 'items' (or the 'Deployment Patterns' subsection's
items) instead of at the top level.

---

Nitpick comments:
In `@backend/internal/enginebridge/bridge_client.go`:
- Around line 101-105: The GetCertificate method currently returns an empty
&inboundclient.CertOperationError{} which hides the failure reason; update
clientBridge.GetCertificate to return a populated CertOperationError indicating
"not implemented" (set available fields such as Message or Error and a Code or
Type field to "NotImplemented" or similar) so callers can log/branch correctly
and see the intent that GetCertificate is unimplemented.

In `@backend/pkg/thunderidengine/engine.go`:
- Around line 30-32: RegisterBootstrap currently overwrites registeredBootstrap
on every call; change it to perform a single-assignment of the first non-nil
bootstrapFunc and ignore subsequent calls (and nil args) to avoid accidental
replacement. Implement this by guarding the assignment with a check that
registeredBootstrap is nil and fn != nil (or use a sync.Once named bootstrapOnce
and call bootstrapOnce.Do to set registeredBootstrap) so the first non-nil fn is
preserved and further RegisterBootstrap calls are no-ops; keep the symbol names
RegisterBootstrap, registeredBootstrap, bootstrapFunc and make the behavior
concurrency-safe.

In `@backend/tests/integration/providers.go`:
- Around line 131-145: The Store method in memoryRuntimeStore currently uses a
non-unique key "urn:par:"+parRequest.ClientID which causes overwrites; update
memoryRuntimeStore.Store to generate a unique key (e.g., UUID or
timestamp+random) such as "urn:par:<unique-id>", store the parRequest in m.par
under that generated key, and return that key; keep newMemoryRuntimeStore and
the m.par map unchanged but ensure the generated key format is used wherever
callers of Store expect the returned key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f648e151-24de-4f9d-9a63-8ee52db3f666

📥 Commits

Reviewing files that changed from the base of the PR and between 4fce387 and c917693.

📒 Files selected for processing (72)
  • ARCHITECTURE.md
  • backend/cmd/server/servicemanager.go
  • backend/internal/authnprovider/manager/model.go
  • backend/internal/enginebridge/bootstrap.go
  • backend/internal/enginebridge/bootstrap_config.go
  • backend/internal/enginebridge/bootstrap_integration_test.go
  • backend/internal/enginebridge/bootstrap_internal.go
  • backend/internal/enginebridge/bootstrap_providers.go
  • backend/internal/enginebridge/bridge.go
  • backend/internal/enginebridge/bridge_authn.go
  • backend/internal/enginebridge/bridge_authz.go
  • backend/internal/enginebridge/bridge_client.go
  • backend/internal/enginebridge/bridge_entity.go
  • backend/internal/enginebridge/bridge_executor_registry.go
  • backend/internal/enginebridge/bridge_idp.go
  • backend/internal/enginebridge/bridge_observability.go
  • backend/internal/enginebridge/bridge_ou.go
  • backend/internal/enginebridge/bridge_resource.go
  • backend/internal/enginebridge/bridges_compile_test.go
  • backend/internal/enginebridge/bridges_provider_test.go
  • backend/internal/enginebridge/bridges_test.go
  • backend/internal/enginebridge/convert.go
  • backend/internal/enginebridge/default_runtime_store.go
  • backend/internal/enginebridge/flow_definition_bridge.go
  • backend/internal/enginebridge/init.go
  • backend/internal/enginebridge/register.go
  • backend/internal/enginebridge/runtime_store_test.go
  • backend/internal/enginebridge/runtime_stores.go
  • backend/internal/enginebridge/server_runtime.go
  • backend/internal/flow/executor/deps.go
  • backend/internal/flow/executor/init.go
  • backend/internal/flow/executor/register.go
  • backend/internal/flow/executor/register_test.go
  • backend/internal/flow/flowexec/host_runtime_store.go
  • backend/internal/flow/flowexec/init.go
  • backend/internal/flow/flowexec/redis_store.go
  • backend/internal/flow/flowexec/service.go
  • backend/internal/flow/flowexec/store.go
  • backend/internal/flow/mgt/host_flow_provider.go
  • backend/internal/flow/mgt/runtime_flow_service.go
  • backend/internal/flow/mgt/runtime_init.go
  • backend/internal/oauth/engine_init.go
  • backend/internal/oauth/init.go
  • backend/internal/oauth/oauth2/authz/auth_code_store.go
  • backend/internal/oauth/oauth2/authz/auth_req_redis_store.go
  • backend/internal/oauth/oauth2/authz/auth_req_store.go
  • backend/internal/oauth/oauth2/authz/auth_req_store_test.go
  • backend/internal/oauth/oauth2/authz/host_runtime_store.go
  • backend/internal/oauth/oauth2/authz/init.go
  • backend/internal/oauth/oauth2/authz/init_stores_test.go
  • backend/internal/oauth/oauth2/authz/init_test.go
  • backend/internal/oauth/oauth2/authz/service.go
  • backend/internal/oauth/oauth2/granthandlers/init.go
  • backend/internal/oauth/oauth2/par/host_runtime_store.go
  • backend/internal/oauth/oauth2/par/init.go
  • backend/internal/oauth/oauth2/par/init_stores_test.go
  • backend/internal/oauth/oauth2/par/redis_store.go
  • backend/internal/oauth/oauth2/par/service.go
  • backend/internal/oauth/oauth2/par/store.go
  • backend/pkg/thunderidengine/config.go
  • backend/pkg/thunderidengine/doc.go
  • backend/pkg/thunderidengine/engine.go
  • backend/pkg/thunderidengine/engine_bootstrap_test.go
  • backend/pkg/thunderidengine/errors.go
  • backend/pkg/thunderidengine/executor.go
  • backend/pkg/thunderidengine/model.go
  • backend/pkg/thunderidengine/providers.go
  • backend/pkg/thunderidengine/providers_test.go
  • backend/tests/integration/engine_test.go
  • backend/tests/integration/providers.go
  • docs/content/guides/deployment-patterns/embed-thunderidengine.mdx
  • docs/sidebars.ts

Comment thread ARCHITECTURE.md Outdated
Comment thread backend/internal/enginebridge/bootstrap_integration_test.go Outdated
Comment thread backend/internal/enginebridge/bootstrap.go Outdated
Comment on lines +32 to +60
func NewContextStoreFromRuntime(host thunderidengine.RuntimeStore) ContextStoreInterface {
return &hostContextStore{host: host}
}

func (s *hostContextStore) StoreFlowContext(
ctx context.Context, dbModel FlowContextDB, expirySeconds int64,
) error {
return s.host.StoreFlowContext(ctx, toPublicFlowContext(dbModel), expirySeconds)
}

func (s *hostContextStore) GetFlowContext(ctx context.Context, executionID string) (*FlowContextDB, error) {
pub, err := s.host.GetFlowContext(ctx, executionID)
if err != nil {
return nil, err
}
if pub == nil {
return nil, nil
}
internal := fromPublicFlowContext(*pub)
return &internal, nil
}

func (s *hostContextStore) UpdateFlowContext(ctx context.Context, dbModel FlowContextDB) error {
return s.host.UpdateFlowContext(ctx, toPublicFlowContext(dbModel))
}

func (s *hostContextStore) DeleteFlowContext(ctx context.Context, executionID string) error {
return s.host.DeleteFlowContext(ctx, executionID)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against nil host runtime store to prevent panics.

The adapter methods dereference s.host without validation. A nil RuntimeStore will panic on first call instead of returning a controlled error.

Proposed fix
 import (
 	"context"
+	"fmt"

 	"github.com/thunder-id/thunderid/pkg/thunderidengine"
 )
@@
 type hostContextStore struct {
 	host thunderidengine.RuntimeStore
 }
+
+func (s *hostContextStore) validateHost() error {
+	if s.host == nil {
+		return fmt.Errorf("runtime store is not configured")
+	}
+	return nil
+}
@@
 func (s *hostContextStore) StoreFlowContext(
 	ctx context.Context, dbModel FlowContextDB, expirySeconds int64,
 ) error {
+	if err := s.validateHost(); err != nil {
+		return err
+	}
 	return s.host.StoreFlowContext(ctx, toPublicFlowContext(dbModel), expirySeconds)
 }
@@
 func (s *hostContextStore) GetFlowContext(ctx context.Context, executionID string) (*FlowContextDB, error) {
+	if err := s.validateHost(); err != nil {
+		return nil, err
+	}
 	pub, err := s.host.GetFlowContext(ctx, executionID)
@@
 func (s *hostContextStore) UpdateFlowContext(ctx context.Context, dbModel FlowContextDB) error {
+	if err := s.validateHost(); err != nil {
+		return err
+	}
 	return s.host.UpdateFlowContext(ctx, toPublicFlowContext(dbModel))
 }
@@
 func (s *hostContextStore) DeleteFlowContext(ctx context.Context, executionID string) error {
+	if err := s.validateHost(); err != nil {
+		return err
+	}
 	return s.host.DeleteFlowContext(ctx, executionID)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/flow/flowexec/host_runtime_store.go` around lines 32 - 60,
NewContextStoreFromRuntime currently returns a hostContextStore that
dereferences s.host in methods (StoreFlowContext, GetFlowContext,
UpdateFlowContext, DeleteFlowContext) and will panic if the provided
RuntimeStore is nil; change the constructor to validate the host argument and
return an error or a no-op implementation when host is nil, or store an explicit
nil-check flag, and update each method to check s.host != nil and return a clear
error (e.g., fmt.Errorf("nil runtime store") or a package-level sentinel error)
instead of panicking; ensure GetFlowContext still returns (nil, nil) when
appropriate and keep conversions (toPublicFlowContext/fromPublicFlowContext)
unchanged.

Comment on lines +75 to +86
func resolveFlowContextStore(opts *InitOptions) (ContextStoreInterface, transaction.Transactioner, error) {
if opts != nil && opts.ContextStore != nil {
if config.GetServerRuntime().Config.Database.Runtime.Type == dbprovider.DataSourceTypeRedis {
return opts.ContextStore, transaction.NewNoOpTransactioner(), nil
}
dbProvider := dbprovider.GetDBProvider()
transactioner, err := dbProvider.GetRuntimeDBTransactioner()
if err != nil {
return nil, nil, err
}
return opts.ContextStore, transactioner, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Custom ContextStore path should not require runtime DB transactioner.

When opts.ContextStore is provided, this code still initializes a DB transactioner for non-Redis runtime types. That can fail startup even though persistence is delegated to the custom store.

Proposed fix
 func resolveFlowContextStore(opts *InitOptions) (ContextStoreInterface, transaction.Transactioner, error) {
 	if opts != nil && opts.ContextStore != nil {
-		if config.GetServerRuntime().Config.Database.Runtime.Type == dbprovider.DataSourceTypeRedis {
-			return opts.ContextStore, transaction.NewNoOpTransactioner(), nil
-		}
-		dbProvider := dbprovider.GetDBProvider()
-		transactioner, err := dbProvider.GetRuntimeDBTransactioner()
-		if err != nil {
-			return nil, nil, err
-		}
-		return opts.ContextStore, transactioner, nil
+		return opts.ContextStore, transaction.NewNoOpTransactioner(), nil
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func resolveFlowContextStore(opts *InitOptions) (ContextStoreInterface, transaction.Transactioner, error) {
if opts != nil && opts.ContextStore != nil {
if config.GetServerRuntime().Config.Database.Runtime.Type == dbprovider.DataSourceTypeRedis {
return opts.ContextStore, transaction.NewNoOpTransactioner(), nil
}
dbProvider := dbprovider.GetDBProvider()
transactioner, err := dbProvider.GetRuntimeDBTransactioner()
if err != nil {
return nil, nil, err
}
return opts.ContextStore, transactioner, nil
}
func resolveFlowContextStore(opts *InitOptions) (ContextStoreInterface, transaction.Transactioner, error) {
if opts != nil && opts.ContextStore != nil {
return opts.ContextStore, transaction.NewNoOpTransactioner(), nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/flow/flowexec/init.go` around lines 75 - 86, The custom
ContextStore path in resolveFlowContextStore currently still initializes a
runtime DB transactioner for non-Redis runtimes; change resolveFlowContextStore
so that whenever opts != nil && opts.ContextStore != nil it immediately returns
opts.ContextStore with transaction.NewNoOpTransactioner() (do not call
dbprovider.GetDBProvider() or GetRuntimeDBTransactioner()), so a provided
ContextStore does not force initialization of the runtime DB transactioner.

Comment thread backend/internal/oauth/oauth2/par/init.go Outdated
Comment thread backend/tests/integration/engine_test.go Outdated
Comment thread docs/content/guides/deployment-patterns/embed-thunderidengine.mdx Outdated
Comment thread docs/content/guides/deployment-patterns/embed-thunderidengine.mdx Outdated
Comment thread docs/sidebars.ts Outdated
@anushasunkada anushasunkada force-pushed the local_main branch 2 times, most recently from 8720e7e to 78dca70 Compare May 21, 2026 04:43
// Import this package for side effects when embedding the engine from another module:
//
// import _ "github.com/thunder-id/thunderid/pkg/thunderidengine/embed"
package embed
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the need of this package

* under the License.
*/

package thunderidengine
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use the gateway, to align with the ThunderID architecture.

Suggested change
package thunderidengine
package gateway

IDP IDPProvider
FlowDefinition FlowDefinitionProvider
Observability ObservabilityProvider
RuntimeStore RuntimeStore
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we take this out from providers and keep it out


// Package thunderidengine is the public embed surface for Thunder's OAuth authorization
// server and flow execution runtime.
package thunderidengine
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the doc.go to maintain the package level comments. We usually add it in the package service or main file


// RegisterBootstrap registers the module-internal bootstrap implementation.
// It is called from internal/enginebridge and must not be used by embedders.
func RegisterBootstrap(fn bootstrapFunc) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we make the internal implementation also to follow the same path? Having two entry points for the library one for internal and external might cause issues in long run. We should keep only one entry point and used internally too

Comment on lines +21 to +22
// NewExecutorRegistry creates an empty executor registry.
func NewExecutorRegistry() ExecutorRegistryInterface {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we have to make the NewExecutorRegistry public function? In ThunderID, we have not made the constructor public.

All the constructors will be private and each package will have the Initialize function which will internally create the needed instances and return the the instances needed for other packages to consume. So that the usage of those instances are controlled.

Comment on lines +26 to +27
// DefaultExecutorNames returns the full built-in executor name list.
func DefaultExecutorNames() []string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be private

Suggested change
// DefaultExecutorNames returns the full built-in executor name list.
func DefaultExecutorNames() []string {
// defaultExecutorNames returns the full built-in executor name list.
func defaultExecutorNames() []string {

Comment on lines +69 to +70
// RegisterDefaultExecutors registers built-in executors on reg. When names is empty, all defaults are registered.
func RegisterDefaultExecutors(reg ExecutorRegistryInterface, deps ExecutorDeps, names []string) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// RegisterDefaultExecutors registers built-in executors on reg. When names is empty, all defaults are registered.
func RegisterDefaultExecutors(reg ExecutorRegistryInterface, deps ExecutorDeps, names []string) {
// registerDefaultExecutors registers built-in executors on reg. When names is empty, all defaults are registered.
func registerDefaultExecutors(reg ExecutorRegistryInterface, deps ExecutorDeps, names []string) {

flowExecService, observabilitySvc, runtimeCryptoSvc, ouService, attributeCacheService, authZService,
entityProvider, resourceService, i18nService, idpService)
// Runtime routes (OAuth AS, flow execute, flow meta) via thunderidengine server wiring.
err = enginebridge.RegisterServerRuntime(mux, enginebridge.ServerRuntimeDeps{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should remove the bridge and refactor our internal implementations to use the interfaces defined in the pkg.

So the servermanager will become another embedder implementation. So there is no backdoor for our implementation to bypass the pkg layer.


// InitializeEngine initializes OAuth runtime services for the embeddable engine.
// DCR is omitted unless EnableDCR is true and ApplicationService is set.
func InitializeEngine(deps EngineDeps) error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not have two paths. As pointed out in earlier, let's have as single entry point

anushasunkada and others added 4 commits May 22, 2026 11:52
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants