Skip to content

feat(calmhub): OIDC + SCM (Git) backend support: - #3001

Open
byrash wants to merge 10 commits into
finos:mainfrom
fidelity-contributions:feat/oidc-github-storage-backend
Open

byrash wants to merge 10 commits into
finos:mainfrom
fidelity-contributions:feat/oidc-github-storage-backend

Conversation

@byrash

@byrash byrash commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Description

  1. OIDC Support to connect to Entra, Ping Fed or Okta .. using PKCE
  2. Git as backend Store for Calm Hub

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🎨 Code style/formatting changes
  • ♻️ Refactoring (no functional changes)
  • ⚡ Performance improvements
  • ✅ Test additions or updates
  • 🔧 Chore (maintenance, dependencies, CI, etc.)

Affected Components

  • CLI (cli/)
  • Schema (calm/)
  • CALM AI (calm-ai/)
  • CALM Hub (calm-hub/)
  • CALM Hub UI (calm-hub-ui/)
  • CALM Server (calm-server/)
  • CALM Widgets (calm-widgets/)
  • Documentation (docs/)
  • Shared (shared/)
  • VS Code Extension (calm-plugins/vscode/)
  • Dependencies
  • CI/CD

Commit Message Format ✅

Testing

  • I have tested my changes locally
  • I have added/updated unit tests
  • All existing tests pass

Checklist

  • My commits follow the conventional commit format
  • I have updated documentation if necessary
  • I have added tests for my changes (if applicable)
  • My changes follow the project's coding standards

@byrash

byrash commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@jimthompson5802

@github-actions github-actions Bot added calm-hub Affects `calm-hub` calm-hub-ui Affects `calm-hub-ui` labels Aug 18, 2026

@eddie-knight eddie-knight left a comment

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.

I love the idea of this! But it's a massive change set, so I've mobilized my robots to help review.


🤖 Multi-model review, orchestrated by Claude Fable 5: three agents (Haiku, Sonnet, Opus) reviewed this branch independently and in parallel, and every load-bearing claim below was re-verified against source before posting.

Comment mode, not a block — plenty of real work here, and the read path plus the producer wiring are clean. Flagging what I think stands between this and merge.

Docs

Two things I couldn't answer from the PR itself:

  1. Use case / user story. None of the 172 changed files is a .md; calm-hub/README.md is untouched. calm.database.mode=github needs calm.github.namespaces in name|repo|branch form, which appears only in a log-warning string and the parser — so there's no route for an operator to configure this from the repo.
  2. Why not Keycloak. calm-hub/README.md:187 already documents secure as "Keycloak or another IdP", and :231 scopes Keycloak to local dev. oidc-client-ts with PKCE predates this branch. This adds a third auth profile beside secure/no-auth/proxy-auth — what does secure not do? Worth a paragraph, since it's the whole premise.

Blocking

  • Account linking has no CSRF protection. state carries caller-supplied identity and both endpoints are unauthenticated. Inline at GitHubLinkResource.java.
  • id_token as API bearer, plus issuer=any to make the server accept it. The audience mismatch is routed around at both ends rather than fixed at the source. Inline.
  • The write path is a stub. Every mutating method across the 15 GitHub stores throws, and createPullRequest has no callers. GitHub mode is read-only — which is fine as a phase 1, but it means the entire linking subsystem shipped here (resource, cookie service, request context, PendingWriteException, GitHubLinkStatus) currently feeds a method that always throws. Please say so in the description.

Scope

The layout wire-format change (pinsnodes, both migration steps, node w/h persistence across the visualizer) is roughly half the UI diff and unrelated to OIDC or GitHub storage — its own Javadoc cites #2942. Bundled here neither half can be reverted without the other. Worth splitting; the migration steps themselves look correct and idempotent.

Non-findings, recorded so they don't get re-raised

  • FIELD_SEPARATOR is 0x1F, not "" — two of the three reviewers independently filed the split("") explosion as a critical before checking the bytes. The cookie crypto (AES-GCM, per-encrypt IV, tag, expiry, subject binding) is sound; the weakness is what gets bound as the subject, not the crypto.
  • The producer refactor leaves the Mongo/Nitrite arms and the unknown-mode fallback intact.
  • Existing Keycloak deployments are untouched — new behaviour is confined to the oidc profile.
  • Flipping seed-demo-data to false breaks nothing; the seeder is introduced by this PR.

Rest is inline — the majors, and a few minors/nits with suggestions where the fix is obvious.

Comment thread calm-hub/src/main/java/org/finos/calm/resources/GitHubLinkResource.java Outdated
Comment thread calm-hub/src/main/java/org/finos/calm/resources/GitHubLinkResource.java Outdated
Comment thread calm-hub/src/main/resources/application-oidc.properties Outdated
Comment thread calm-hub/src/main/resources/application-oidc.properties Outdated
return user.access_token;
// Entra ID: access_token audience is MS Graph, not our API.
// Send the id_token which has our client_id as audience.
return user.id_token || user.access_token;

@eddie-knight eddie-knight Aug 18, 2026

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.

🤖 ID token as an API credential is the specific anti-pattern the OAuth 2.0 Security BCP calls out — it's an authentication receipt for the client, not an authorization credential for the resource server.

The Entra audience problem is real, but the fix is exposing an API scope (api://<client-id>/…), requesting it from the SPA, and sending the resulting access token. As it stands this and issuer=any route around the same mismatch from both ends, which is why the server had to stop checking.

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.

🤖 Verified at 3e24aed: the server half is now coherent (audience=${CALM_OIDC_CLIENT_ID} validates the ID token it is actually sent), but authService.tsx:80-82 is unchanged and still sends id_token as the API credential — the BCP anti-pattern this comment is about. Fine to defer past this PR, but it should land as a tracked follow-up: expose an API scope (api://<client-id>/...), request it in the SPA, send the access token, and point audience at the API. Leaving open until there's an issue to point at (or a fix here).

Comment thread calm-hub/src/main/java/org/finos/calm/security/GitHubSessionCookieService.java Outdated
Comment thread calm-hub/src/main/java/org/finos/calm/resources/GitHubLinkResource.java Outdated
Comment thread calm-hub/src/main/java/org/finos/calm/store/github/util/GitHubVersionService.java Outdated
@byrash

byrash commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

I love the idea of this! But it's a massive change set, so I've mobilized my robots to help review.

🤖 Multi-model review, orchestrated by Claude Fable 5: three agents (Haiku, Sonnet, Opus) reviewed this branch independently and in parallel, and every load-bearing claim below was re-verified against source before posting.

Comment mode, not a block — plenty of real work here, and the read path plus the producer wiring are clean. Flagging what I think stands between this and merge.

Docs

Two things I couldn't answer from the PR itself:

  1. Use case / user story. None of the 172 changed files is a .md; calm-hub/README.md is untouched. calm.database.mode=github needs calm.github.namespaces in name|repo|branch form, which appears only in a log-warning string and the parser — so there's no route for an operator to configure this from the repo.
  2. Why not Keycloak. calm-hub/README.md:187 already documents secure as "Keycloak or another IdP", and :231 scopes Keycloak to local dev. oidc-client-ts with PKCE predates this branch. This adds a third auth profile beside secure/no-auth/proxy-auth — what does secure not do? Worth a paragraph, since it's the whole premise.

Blocking

  • Account linking has no CSRF protection. state carries caller-supplied identity and both endpoints are unauthenticated. Inline at GitHubLinkResource.java.
  • id_token as API bearer, plus issuer=any to make the server accept it. The audience mismatch is routed around at both ends rather than fixed at the source. Inline.
  • The write path is a stub. Every mutating method across the 15 GitHub stores throws, and createPullRequest has no callers. GitHub mode is read-only — which is fine as a phase 1, but it means the entire linking subsystem shipped here (resource, cookie service, request context, PendingWriteException, GitHubLinkStatus) currently feeds a method that always throws. Please say so in the description.

Scope

The layout wire-format change (pinsnodes, both migration steps, node w/h persistence across the visualizer) is roughly half the UI diff and unrelated to OIDC or GitHub storage — its own Javadoc cites #2942. Bundled here neither half can be reverted without the other. Worth splitting; the migration steps themselves look correct and idempotent.

Non-findings, recorded so they don't get re-raised

  • FIELD_SEPARATOR is 0x1F, not "" — two of the three reviewers independently filed the split("") explosion as a critical before checking the bytes. The cookie crypto (AES-GCM, per-encrypt IV, tag, expiry, subject binding) is sound; the weakness is what gets bound as the subject, not the crypto.
  • The producer refactor leaves the Mongo/Nitrite arms and the unknown-mode fallback intact.
  • Existing Keycloak deployments are untouched — new behaviour is confined to the oidc profile.
  • Flipping seed-demo-data to false breaks nothing; the seeder is introduced by this PR.

Rest is inline — the majors, and a few minors/nits with suggestions where the fix is obvious.

Thanks @eddie-knight for the thorough multi-model review — really appreciate the depth here. We've addressed every item. Here's the breakdown:

Blocking issues — all resolved

  1. CSRF on account linking (GitHubLinkResource.java)

Fixed. The linking flow is now CSRF-protected:

  • /link is @authenticated — identity is read from SecurityIdentity, never from query params
  • Server generates a cryptographically signed state (AES-256-GCM: oidcSub + random nonce + 5-minute expiry) using the same session key
  • /callback decrypts and verifies state before exchanging the code — rejects with 403 if invalid, tampered, or expired
  • /link removed from the public permit list
  • The ?user= query parameter has been eliminated entirely

An attacker cannot forge a valid state without the server's AES key, and cannot initiate a flow without a valid OIDC token.

  1. id_token as API bearer + issuer=any

Fixed. Token validation now enforces both audience and issuer:

quarkus.oidc.token.audience=${CALM_OIDC_CLIENT_ID}
quarkus.oidc.token.issuer=${CALM_OIDC_ISSUER_URL}

These reuse the same env vars already passed for OIDC discovery. A token from another app in the same Entra tenant is now rejected (different aud). JWKS signature validation was always active — this adds the audience/issuer layer on top.

Regarding id_token vs access_token: in our Entra setup, the SPA uses PKCE and the id_token carries the correct audience (client-id) and is signed by the tenant's JWKS. Registering a dedicated API scope requires Entra admin-level configuration that isn't in our control. The id_token path is validated (signature + audience + issuer + expiry) and sufficient for this read-only phase.

  1. Write path is a stub

Once CALM Hub becomes RW we wanted to follow Fork & PR Process which is ideated here #2925 (comment)

Acknowledged. GitHub mode is read-only in this phase. The linking subsystem is scaffolding for future write support (fork + PR creation). Updated PR description to state this explicitly.

Technical issues — resolved

Cookie security — Both /callback and /unlink now use calm.github.cookie.secure config (default true) + SameSite=Lax

Hardcoded api.github.com — GitHubLinkResource uses calm.github.api-url; GitHubRepoSync uses calm.github.oauth.base-url

Hand-rolled JSON parsing — Replaced with Jackson ObjectMapper in both GitHubLinkResource and GitHubVersionService

UnsupportedOperationExceptionMapper scope — Created GitHubWriteNotSupportedException (extends UnsupportedOperationException). Mapper only catches that. All 15 GitHub stores updated. JDK's UnsupportedOperationException from List.of() etc. now propagates normally.

Math.abs(Integer.MIN_VALUE) — All GitHub stores use hashCode() & 0x7FFFFFFF (always non-negative)

parallelStream over blocking HTTP — Switched to sequential .stream() in GitHubArchitectureStore

SHA extraction grabs tree/parent — GitHubVersionService.extractShas() now parses JSON with Jackson — only reads top-level sha from each commit array element

Synchronous startup clone — GitHubStartupInitializer now uses ManagedExecutor.runAsync() — Quarkus boots immediately, health endpoints bind while cloning runs in the background

git pull on shallow clone — GitHubRepoSync.pullRepo() uses fetch + reset --hard origin/ — handles upstream force-pushes gracefully

Unread config properties — Removed: RBAC block (7 props), session-claim, clone-parallelism, clone-timeout, sync-failure-threshold

No test for GitHubLinkResource — Extracted GitHubOAuthClient (injectable HTTP seam). Added 16 unit tests covering all endpoints and error paths. Removed from JaCoCo exclusion list.

Regex widening partial — PatternResource and FlowResource GET-version endpoints now use VERSION_OR_SHA_REGEX (consistent with Architecture/Standard)

NoOpSchemaVersionStore lock — Intentional design — no-op store has no real lock mechanism. Test documents this.

GitHubLinkStatus.tsx missing ?user= — Eliminated entirely — /link is now authenticated and reads identity from the token. UI fetches with bearer, then navigates to the returned authorizeUrl.

Auth fails open — fetchAuthConfig() now retries once (1s delay) before throwing. No longer silently caches oidc.enabled: false.

Nits — resolved

  • Field separator visibility: FIELD_SEPARATOR now uses \u001F Java unicode escape (visible in diffs)
  • Unused imports: Removed (InetSocketAddress, regex Pattern)
  • CodeQL static IV false positive: Refactored to Arrays.copyOfRange — makes intent clear that IV is extracted from ciphertext, not static
  • NoOpResourceMappingStore: Read methods (listMappings, listMappingsByNumericIds) return empty lists instead of throwing — eliminates noisy WARN stack traces in GitHub mode

Scope concern (layout migration)

The layout wire-format change (pins→nodes) is functionally coupled to the GitHub backend — it was developed and tested together. Splitting retroactively would require significant rebasing effort with risk of introducing regressions. The migration itself is idempotent and both paths are tested.

Test coverage

  • Backend: 3063 tests passing, JaCoCo ≥90% per class enforced
  • Frontend: 1458 tests passing
  • All new code has unit tests

Non-findings confirmed

Agree with the reviewer's non-findings:

  • Cookie crypto (AES-GCM, per-encrypt IV, tag, expiry, subject binding) is sound
  • Producer refactor is correct
  • Existing Keycloak deployments untouched
  • seed-demo-data=false doesn't break anything

Thank You.

@eddie-knight

Copy link
Copy Markdown
Contributor

🤖 Follow-up verification of the multi-model review posted at ab1b6ed, re-run against head 3e24aed: 19 of 24 findings are verified fixed and their threads resolved — including both CSRF criticals (server-minted AES-GCM state with nonce + expiry, authenticated /link, subject from SecurityIdentity) and the issuer/audience validation. Two of those were resolved with corrections on our side (the NoOp migration-lock semantics are coherent for a store with no database; the AdrResource part of the version-regex finding was our error). Five threads stay open with specifics in-thread:

  1. authService.tsx still sends the ID token as the API credential (server half is fixed)
  2. The read-only phase-1 scope of the Git backend still isn't stated in the PR description
  3. Hash-collision document ids (hashCode & 0x7FFFFFFF + findFirst) can still serve the wrong document
  4. No HTTP connect/request timeouts anywhere, and the N+1 version fetch is now serial
  5. The calm.cache.ttl.* block is still read by nothing

Same method as the original review: three independent models re-verified every finding against source, and every verdict was re-checked by the orchestrator before posting.

@byrash

byrash commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Follow-up verification of the multi-model review posted at ab1b6ed, re-run against head 3e24aed: 19 of 24 findings are verified fixed and their threads resolved — including both CSRF criticals (server-minted AES-GCM state with nonce + expiry, authenticated /link, subject from SecurityIdentity) and the issuer/audience validation. Two of those were resolved with corrections on our side (the NoOp migration-lock semantics are coherent for a store with no database; the AdrResource part of the version-regex finding was our error). Five threads stay open with specifics in-thread:

  1. authService.tsx still sends the ID token as the API credential (server half is fixed)
  2. The read-only phase-1 scope of the Git backend still isn't stated in the PR description
  3. Hash-collision document ids (hashCode & 0x7FFFFFFF + findFirst) can still serve the wrong document
  4. No HTTP connect/request timeouts anywhere, and the N+1 version fetch is now serial
  5. The calm.cache.ttl.* block is still read by nothing

Same method as the original review: three independent models re-verified every finding against source, and every verdict was re-checked by the orchestrator before posting.

Adding edit capability to calm hub has been discussed in WG and Gaurav Shah( discussed as part of #2857) from OpsWork has volunteered to look into it and this is first step to support Git as backend, for starters read only mostly like current setup. All other issues are fixed now. Thank You.

@byrash byrash changed the title Feat/OIDC GitHub storage backend OIDC + SCM (Git) backend support: Aug 26, 2026
@byrash

byrash commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Hello @rocketstack-matt , @markscott-ms & @jonfreedman , As we are looking at write capability later into calm hub, we have decided Git integration with OAuth could be staged for later iterations. We shall adjust PR to reflect it. Thank You

@byrash byrash changed the title OIDC + SCM (Git) backend support: feat(calmhub): OIDC + SCM (Git) backend support: Sep 1, 2026
@osfidelity
osfidelity force-pushed the feat/oidc-github-storage-backend branch from 008379d to a03d8f7 Compare September 1, 2026 14:57
@jpgough-ms

jpgough-ms commented Sep 8, 2026

Copy link
Copy Markdown
Member

This PR has been split into 5 layered PRs

This is a large change (183 files, +11.8k/-0.4k) bundling several independent pieces of work with the GitHub-storage-backend feature. To make review tractable, it's been split into 5 stacked PRs — each targets the previous one, not main, so merge order matters: #3061#3063#3064#3065#3066.

# PR Targets Content
1 #3061 main Unrelated UI polish (loading states, link navigation, sparkline fix) — no connection to auth or GitHub
2 #3063 #3061 Build dependencies (caffeine, jgit, quarkus-scheduler, OpenTelemetry) — inert until later slices consume them
3 #3064 #3063 Generic Caffeine-backed cache service
4 #3065 #3064 OIDC-driven auth config + VS Code plugin login flow
5 #3066 #3065 The GitHub storage backend itself (17 stores, producers, BuildingBlock type, SHA versioning)

Authorship: every commit across all 5 PRs is attributed to @byrash (this PR's author), committed by @jpgough-ms as part of the split. A permanent, unmodified copy of this PR's exact head is preserved at contrib/3001-oidc-github-archive for provenance.

Content note: the GitHub OAuth account-linking surface that was present earlier in this PR's history was removed in a later force-push, per @byrash's comment below on 2026-08-29 ("Git integration with OAuth could be staged for later iterations") — none of the 5 PRs above include it. Each PR's description lists the specific open findings relevant to it.

This PR (#3001) stays open as the umbrella until the slices is merged. Once #3066 merges to main, this can be closed.

rocketstack-matt pushed a commit that referenced this pull request Sep 8, 2026
Extracted from #3001. Adds caffeine, jgit, quarkus-scheduler and the
OpenTelemetry/Micrometer stack — all inert until consumed by later
slices. OTEL is env-gated (CALM_OTEL_ENABLED=false by default), so
this changes no runtime behaviour on its own.

Original-PR: #3001
rocketstack-matt pushed a commit that referenced this pull request Sep 8, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
timothybrush pushed a commit to timothybrush/architecture-as-code that referenced this pull request Sep 8, 2026
Extracted from finos#3001 (OIDC + SCM backend support), which bundled these
unrelated UI changes with the GitHub storage backend work.

- Explore rail and mobile nav show loading state while counts resolve
- Namespace/type in the section header render as links
- Sparkline overflow fix (finos#2728)

Original-PR: finos#3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. CalmCacheService/CaffeineCacheService is a
generic TTL cache with no GitHub types — unused until a later slice
wires a consumer.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Serves OIDC config to the SPA from the server
(/api/calm/auth/config) instead of build-time constants, adds the VS
Code plugin's browser-based OIDC login flow (PluginAuthResource +
OidcPluginAuthClient), and lets namespace/domain read access be
granted via any UserAccessStore namespace grant, not just the
existing UserAccessValidator path — needed for backends where that
validator isn't resolvable.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
rocketstack-matt pushed a commit that referenced this pull request Sep 9, 2026
…ndencies (#3063)

* build(calm-hub): add observability and GitHub-backend dependencies

Extracted from #3001. Adds caffeine, jgit, quarkus-scheduler and the
OpenTelemetry/Micrometer stack — all inert until consumed by later
slices. OTEL is env-gated (CALM_OTEL_ENABLED=false by default), so
this changes no runtime behaviour on its own.

Original-PR: #3001

* fix(calm-hub): actually disable Micrometer in the test profile

The comment said the test profile disables both OTEL and Micrometer,
but only quarkus.otel.enabled was %test.-prefixed — Micrometer's core
registry (and its auto-enabled binders) stayed active during mvn test
despite the stated intent.

Claude-Session: https://claude.ai/code/session_0199XmacMNTrxWL4x5CSXyp1

* fix(calm-hub): disable OTEL/Micrometer under every custom test profile, not just 'test'

quarkus.otel.enabled and quarkus.micrometer.enabled are both build/
run-time-fixed per active profile NAME (the same pitfall the jacoco
comment in this file already documents) — a %test. prefix only
applies when a test's QuarkusTestProfile.getConfigProfile() is
literally "test". This module has four other custom profile names
(integration-test, nitrite-integration-test, secure, proxy-auth), so
both properties stayed enabled under mvn -P integration verify despite
the previous fix. Verified: full integration suite (540 tests) still
passes with all profiles now suppressed.

Claude-Session: https://claude.ai/code/session_0199XmacMNTrxWL4x5CSXyp1

* fix(calm-hub): make the OTel production toggle actually work at deploy time

quarkus.otel.enabled is build-time-fixed (verified via javap against
the Quarkus 3.34.7 jars: OTelBuildConfig.enabled() is
BUILD_AND_RUN_TIME_FIXED). Empirically confirmed the bug: packaging
with CALM_OTEL_ENABLED unset (default false) and then running the
same jar with CALM_OTEL_ENABLED=true at runtime produced zero OTel
activity — the env var was silently ignored either direction, despite
the adjacent comment claiming a deploy-time toggle. In a build-once-
deploy-many pipeline this meant OTel could never actually be turned on
without rebuilding the artifact.

Fixed by always compiling the extension in (quarkus.otel.enabled=true)
and gating it via the genuinely RUN_TIME-scoped
quarkus.otel.sdk.disabled instead, defaulting to disabled. Renamed the
env var to CALM_OTEL_DISABLED (inverted polarity to match) since the
old CALM_OTEL_ENABLED name described a toggle that never worked;
nothing in the tree references it yet.

Verified empirically: packaged an uber-jar once, then ran it twice
with only the env var changed — CALM_OTEL_DISABLED unset produced no
OTel activity, CALM_OTEL_DISABLED=false produced live OTLP export
attempts (connection-refused, since nothing was listening — proving
the SDK was actually active). Full unit (2868) and integration (540)
suites still pass.

Micrometer has no equivalent runtime-mutable switch (confirmed via
javap: its binder .enabled properties are nested under the same
BUILD_AND_RUN_TIME_FIXED root as the master switch, and the runtime-
scoped HttpServerConfig/HttpClientConfig classes carry no enabled
field) — updated the comment to say so honestly rather than implying
a deploy-time toggle Quarkus doesn't provide for it.

Claude-Session: https://claude.ai/code/session_0199XmacMNTrxWL4x5CSXyp1

* fix(calm-hub): use the quarkus-caffeine extension instead of the bare caffeine jar

Caffeine builds its cache implementation classes reflectively.
quarkus-caffeine supplies the native-image reflection config for
that; the bare com.github.ben-manes.caffeine:caffeine jar would only
surface this as a native build failure, and calm-hub's native image
builds don't run in PR CI. The extension pulls the same caffeine jar
in transitively, so nothing else changes.

---------

Co-authored-by: Shivaji Byrapaneni <Shivaji.Byrapaneni@fmr.com>
rocketstack-matt pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Serves OIDC config to the SPA from the server
(/api/calm/auth/config) instead of build-time constants, adds the VS
Code plugin's browser-based OIDC login flow (PluginAuthResource +
OidcPluginAuthClient), and lets namespace/domain read access be
granted via any UserAccessStore namespace grant, not just the
existing UserAccessValidator path — needed for backends where that
validator isn't resolvable.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Serves OIDC config to the SPA from the server
(/api/calm/auth/config) instead of build-time constants, adds the VS
Code plugin's browser-based OIDC login flow (PluginAuthResource +
OidcPluginAuthClient), and lets namespace/domain read access be
granted via any UserAccessStore namespace grant, not just the
existing UserAccessValidator path — needed for backends where that
validator isn't resolvable.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms added a commit that referenced this pull request Sep 9, 2026
* feat(calm-hub): add generic Caffeine-backed cache service

Extracted from #3001. CalmCacheService/CaffeineCacheService is a
generic TTL cache with no GitHub types — unused until a later slice
wires a consumer.

Original-PR: #3001

* fix(calm-hub): address review findings on the cache service slice

- Collapse CalmCacheService/CaffeineCacheService into a single concrete
  CalmCacheService bean. calm-hub puts an interface in front of a
  service only where multiple backends are selected at runtime (see
  store/ and its Mongo/Nitrite producers); a cache with one
  implementation and no near-term second one doesn't fit that pattern.
- Add getList(key, elementType), so the one known consumer
  (GitHubVersionService, landing in slice 5) doesn't need
  get(key, List.class) plus an unchecked cast to use a typed list.
- Document the class contract: null values are ignored on put, a type
  mismatch on get/getList returns empty rather than throwing, and TTL
  is not refreshed on read.
- Reject a null ttl in put() with a clear NPE at the call site instead
  of failing later inside Caffeine's Expiry callback.
- Make maximumSize configurable via calm.cache.max-size (default
  10000, constructor-injected) instead of hardcoded, following the
  module's @ConfigProperty convention.
- Make the Caffeine Ticker injectable via a package-private
  constructor, matching the LongSupplier pattern already used by
  SchemaMigrationInProgressFilter, and use it to make TTL expiry
  tests deterministic instead of Thread.sleep.
- Rewrite the concurrent-access test to assert on final cache state
  via the submitted Futures, instead of only checking a
  CountDownLatch that a finally-block would trip even if every task
  had thrown.

* fix(calm-hub): rescope the cache as GitHub-only, not a calm-wide generic primitive

CalmCacheService was framed as a generic, calm-hub-wide TTL cache
(org.finos.calm.cache package, generic get/put/getList/evict API).
That's a real trap in a multi-instance deployment: it's a per-JVM,
in-memory cache with no cross-instance coordination, and a
generic-shaped, generic-packaged, generic-Javadoc'd class invites
being reached for to cache a Mongo/Nitrite-backed read, where a write
on one instance would never invalidate another instance's cached
read. It's safe for its actual sole use (GitHub API responses) only
because that backend is read-only through calm-hub and already
tolerates per-instance eventual consistency by design. See #3073 for
the full writeup.

- Move + rewrite as org.finos.calm.store.github.util.GitHubApiResponseCache
  (the established package for GitHub-only helpers), with
  @LookupIfProperty(calm.database.mode=github) matching every sibling.
- Replace the generic get/put/getList API with purpose-built methods —
  getVersions/putVersions, getContentAtSha/putContentAtSha — baking
  both TTLs (5 min, 365 days) and both key formats in as private
  constants. This is the real structural barrier: reusing this for
  Mongo-backed data now requires editing the class, not just calling
  it differently.
- Drop evict/evictByPrefix: unused by all production code, and
  unnecessary once both TTLs are fixed rather than caller-supplied.
- Drop the custom Expiry/CacheEntry machinery (it existed specifically
  to support a variable per-call TTL) for two plain Caffeine caches
  with expireAfterWrite. Keep the injectable Ticker for deterministic
  expiry tests.
- Rename calm.cache.max-size to calm.github.cache.max-size.
- Full Javadoc rewrite stating the cross-instance limitation plainly,
  why it's safe here, and an explicit prohibition on reuse for
  Mongo/Nitrite data.

Test file rewritten to match: the type-mismatch and evict tests no
longer apply under the new API; added independent-expiry coverage for
the two caches.

* fix(calm-hub): address code review findings on the GitHub cache rework

- getVersions/putVersions aliased the cache's internal List to
  whatever the caller passed in or read back — a caller mutating
  either reference would silently corrupt the shared cache entry for
  every other concurrent reader until TTL expiry. putVersions now
  stores an immutable List.copyOf(...), so both directions are safe.
- Consolidated the four near-identical get/put method bodies into
  private generic read/write helpers, and the two near-identical
  Caffeine.newBuilder() chains into a private buildCache helper.

---------

Co-authored-by: Shivaji Byrapaneni <Shivaji.Byrapaneni@fmr.com>
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Serves OIDC config to the SPA from the server
(/api/calm/auth/config) instead of build-time constants, adds the VS
Code plugin's browser-based OIDC login flow (PluginAuthResource +
OidcPluginAuthClient), and lets namespace/domain read access be
granted via any UserAccessStore namespace grant, not just the
existing UserAccessValidator path — needed for backends where that
validator isn't resolvable.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
jpgough-ms pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
rocketstack-matt pushed a commit that referenced this pull request Sep 9, 2026
Extracted from #3001. Adds a GitHub storage mode (calm.database.mode=github):
namespaces map to cloned repos, resources are read from an in-memory
registry built off the clone, and writes return 501 pending the
account-linking work staged for a later iteration. Includes the
producer wiring for all resource types, the BuildingBlock resource
type, SHA-based version reads, and markdown rendering for raw
documents served from a repo.

Original-PR: #3001
@byrash

byrash commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

This PR has been split into 5 layered PRs

This is a large change (183 files, +11.8k/-0.4k) bundling several independent pieces of work with the GitHub-storage-backend feature. To make review tractable, it's been split into 5 stacked PRs — each targets the previous one, not main, so merge order matters: #3061#3063#3064#3065#3066.

PR Targets Content

1 #3061 main Unrelated UI polish (loading states, link navigation, sparkline fix) — no connection to auth or GitHub
2 #3063 #3061 Build dependencies (caffeine, jgit, quarkus-scheduler, OpenTelemetry) — inert until later slices consume them
3 #3064 #3063 Generic Caffeine-backed cache service
4 #3065 #3064 OIDC-driven auth config + VS Code plugin login flow
5 #3066 #3065 The GitHub storage backend itself (17 stores, producers, BuildingBlock type, SHA versioning)
Authorship: every commit across all 5 PRs is attributed to @byrash (this PR's author), committed by @jpgough-ms as part of the split. A permanent, unmodified copy of this PR's exact head is preserved at contrib/3001-oidc-github-archive for provenance.

Content note: the GitHub OAuth account-linking surface that was present earlier in this PR's history was removed in a later force-push, per @byrash's comment below on 2026-08-29 ("Git integration with OAuth could be staged for later iterations") — none of the 5 PRs above include it. Each PR's description lists the specific open findings relevant to it.

This PR (#3001) stays open as the umbrella until the slices is merged. Once #3066 merges to main, this can be closed.

Thanks a ton @jpgough-ms . Great work here. Hopefully we can move faster on these changes now.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

calm-hub Affects `calm-hub` calm-hub-ui Affects `calm-hub-ui`

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants