Skip to content

refactor(credentials): derive API key handling from one table - #537

Merged
chhoumann merged 3 commits into
masterfrom
refactor/credentials-table
Nov 14, 2026
Merged

chhoumann merged 3 commits into
masterfrom
refactor/credentials-table

Conversation

@chhoumann

@chhoumann chhoumann commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

PodNotes is moving transcription to more providers (OpenAI, ElevenLabs, OpenRouter, Deepgram), and adding a provider's API key should take one table row. This is step 1 of that plan, with no change in behavior.

  • CREDENTIALS in src/types/Credentials.ts holds each kind's settings field, export key, and label. CredentialKind is the table's keys.
  • The credential repository, the settings decoder, settings import and export, the settings tab, and the key migration notice loop over the table. Before, each one held its own hand-written OpenAI and Deepgram pair.
  • IPodNotesSettings takes its key fields from the table, so a kind with no default in DEFAULT_SETTINGS fails typecheck.
  • SecretStorage IDs keep the released rule podnotes-<kind>-api-key, with an optional -<n> suffix. The validator still accepts -1.
  • The plaintext key map from before 2.19.0 (openAIApiKey, diarizationApiKey) stays fixed.
  • No new kinds, settings, or transcription changes. The transcription service and the diarization code are untouched.

The first commit adds five tests that pass on the old code. They cover derivations that no existing test checked.

Verification

Commands, with Node 22 (fnm exec --using 22.23.2):

  • npm run lint, npm run format:check, npm run typecheck, npm run build: pass.
  • npm run test: svelte-check 0 errors and 0 warnings, 2831 tests pass. The tests-only commit also passes all 2831 on the old source.
  • npm run test:tooling: 47 tests pass.
  • npm run build && npm run size: gzip (level 9) 103,371 bytes before, 103,332 after. Raw 318,892 before, 318,544 after.

Plants, each run with npm run plant. Every plant fails its named test and passes once restored:

  • The table swaps the two export keys. Caught by exports only values whose referenced secrets still exist (CredentialRepository.test.ts).
  • The table swaps the two settings fields. Caught by stores credentials under fixed PodNotes IDs and verifies them (CredentialRepository.test.ts).
  • The SecretStorage ID uses the export key instead of the kind. Caught by the same test.
  • The codec checks every settings field against the OpenAI ID pattern. Caught by preserves provider-owned SecretStorage IDs and removes tampered v2 references (podNotesData.test.ts).
  • Settings import validates the settings field column instead of the export key. Caught by rejects a malformed v2 secrets payload (settingsTransfer.test.ts).
  • Settings import keeps untrimmed keys. Caught by parses the v2 secrets payload separately from settings (settingsTransfer.test.ts).
  • The OpenAI key row is built for the Deepgram kind. Caught by an API key's button keeps its own words and adds its row's name (PodNotesSettingsTab.test.ts).
  • The ID validator rejects -1, as the design sketch proposed. Caught by new keeps every numbered SecretStorage ID that released versions accept (podNotesData.test.ts).
  • The ID validator accepts any digits, such as -0 and -01. Caught by the same new test.
  • The export error names the kind (openai) instead of the label. Caught by new names the provider whose configured key is missing from an explicit export (CredentialRepository.test.ts).
  • The settings tab maps every settings field to the OpenAI kind. Caught by new copies a secret selected in each key row into that row's provider ID (PodNotesSettingsTab.import.test.ts).
  • The migration notice names every kind, or names the export key instead of the label. Caught by new names exactly the legacy keys it moved into SecretStorage (main.persistence.test.ts).
  • The migration failure notice counts only an OpenAI key as a credential. Caught by new says its API keys could not move when only a Deepgram key fails to migrate (main.persistence.test.ts).

No test that existed before this PR catches any of the plants that the new tests catch. With each one planted, the full suite shows only the new test failing. The one exception is the notice that names every kind, which three feed migration tests also catch.

These wrong derivations do not compile, so typecheck rejects them before any test runs:

  • One kind's settings field points at the other's: TS2353 in src/constants.ts.
  • The settings tab's kind map is built uninverted: TS2352 in PodNotesSettingsTab.ts.
  • A third kind with no default in DEFAULT_SETTINGS: TS2741, "Property 'elevenLabsSecretId' is missing", in src/constants.ts.

Obsidian 1.14.4 on Linux, in this worktree's isolated instance, from a fresh vault each run. I seeded one foreign secret, verify-shared-key. Then in Settings > PodNotes I linked it to the OpenAI API key row through the real Select secret picker. I turned on Speaker diarization, chose Deepgram, and linked it to the Deepgram API key row. The script picks Deepgram with the dropdown's change event, because CDP cannot open a native select menu. I ran the same script on a bundle built from master and on this build.

  • data.json and SecretStorage match exactly on both bundles. openAISecretId is podnotes-openai-api-key and deepgramSecretId is podnotes-deepgram-api-key. Each holds the selected value.
  • Each row's name, description, button text, and disabled state match exactly on both bundles.
  • Screenshots match pixel for pixel, except for 19 pixels in the settings scrollbar strip that vary from run to run. No runtime errors.

Release impact

None.

Note

Derive API key handling from a single credential metadata table

Replaces the hard-coded OpenAI and Deepgram credential handling with one shared metadata record in Credentials.ts. Each entry defines the settings key, export key, and provider label, and the credential types derive from it.

  • CredentialRepository.ts, settingsCodec.ts, settingsTransfer.ts, and PodNotesSettingsTab.ts now iterate over the shared metadata instead of enumerating the two providers.
  • SecretStorage ID prefixes come from a shared secretBaseId helper, and isPodNotesSecretId builds its regex from it. IDs keep the positive, non-zero numeric suffix format.
  • The legacy plaintext-key mapping in settings transfer stays explicitly limited to the existing legacy fields.
  • Behavioral Change: the settings decoder now warns and clears foreign or wrong-provider secret IDs that it previously kept; a zero or leading-zero suffix ID is rejected.

Macroscope summarized 1a2b833.


Note

Low Risk
Refactor-only for existing OpenAI/Deepgram keys; no new providers or SecretStorage format changes beyond shared validation helpers and added test coverage.

Overview
Centralizes OpenAI and Deepgram API key handling in a single CREDENTIALS table (settingsKey, exportKey, label, and podnotes-<kind>-api-key IDs). CredentialKind and related types are derived from that table so adding a provider later is meant to be one row, not edits everywhere.

Replaces hand-coded provider pairs with loops over CREDENTIAL_KINDS in CredentialRepository, settings decode/transfer, schema migration notices in main.ts, and the settings tab secret rows. IPodNotesSettings now picks up secret reference fields via CredentialReferenceSettings.

SecretStorage ID rules are built from shared secretBaseId / isPodNotesSecretId (still accepts positive numeric suffixes like -1; rejects -0 / leading-zero forms).

Adds tests for migration/user-facing copy, numbered ID acceptance, provider-specific export errors, and per-row secret adoption in the settings UI. No new transcription providers in this PR—behavior for the two existing keys should match prior releases.

Reviewed by Cursor Bugbot for commit 1a2b833. Bugbot is set up for automated code reviews on this repo. Configure here.

Each test passes on the hand-written code. Together they catch wrong
derivations that no existing test sees: the -1 SecretStorage ID suffix
that released versions accept, the provider an export error names, the
kind a settings key row adopts a secret for, and the key migration
notices.
CREDENTIALS in src/types/Credentials.ts holds each kind's settings
field, export key and label. The credential repository, settings
decoder, settings transfer, settings tab and key migration notice loop
over it. IPodNotesSettings takes its key fields from it, so a kind with
no default in DEFAULT_SETTINGS fails typecheck. The plaintext key map
from before 2.19.0 stays fixed.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1a2b833
Status: ✅  Deploy successful!
Preview URL: https://ed3f59f0.podnotes.pages.dev
Branch Preview URL: https://refactor-credentials-table.podnotes.pages.dev

View logs

@chhoumann

Copy link
Copy Markdown
Owner Author

PASS+NOTES
Verified head 1a2b833 by an independent verifier (GPT 6 Astra).

  • Compared origin/master at 46e62183f1a512c8b4375f10ce9685470e513bc5 against this head in fresh isolated vaults, using Obsidian 1.14.4 on Linux and distinct fake keys only.
  • Through Settings > PodNotes, linked OpenAI, enabled speaker diarization, selected Deepgram with keyboard input, and linked Deepgram. Saved data.json, SecretStorage IDs/values, and row labels/control states match. The captured Deepgram settings screenshots are byte-identical.
  • Exported through the UI with “Include API keys” off and on. Files match except exportedAt; default exports omit secrets, explicit exports retain secrets.openAI and secrets.deepgram with the correct values.
  • Imported each export into a fresh vault through the Import button, real file input, and confirmation dialog. Used CDP to supply the file while suppressing the native chooser. Both builds produce identical persisted settings, secrets, rows, and confirmation text. Key-free imports create no secrets; explicit imports restore both provider-owned IDs. No runtime errors captured.
  • npm run lint, npm run format:check, npm run typecheck, npm run build, npm run size, and npm run test all pass. All 2,831 tests pass; bundle size is 318,544 raw bytes and 103,332 gzip bytes, within both limits.
  • Full diff review found no unintended behavior change. IDs, export names, and migration notice wording are preserved. The autogenerated summary's stricter-validation claim is incorrect: base already rejects foreign/wrong-provider IDs and suffixes -0/-01, and accepts -1.
  • Environment note: commands used fnm exec --using 22.23.2, but the binary installed under that version's path reports v22.23.4. The isolated Obsidian instance is stopped (pids: none), and tracked files remain unchanged.

@chhoumann
chhoumann merged commit d53bd5c into master Nov 14, 2026
18 checks passed
@chhoumann
chhoumann deleted the refactor/credentials-table branch November 14, 2026 15:13
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.

1 participant