Skip to content

feat(secrets): teach secrets.subscribe() and link docs in Secrets config guidance#1501

Merged
dawsontoth merged 3 commits into
stagefrom
claude/cluster-secrets-config-guidance-a35de1
Jul 17, 2026
Merged

feat(secrets): teach secrets.subscribe() and link docs in Secrets config guidance#1501
dawsontoth merged 3 commits into
stagefrom
claude/cluster-secrets-config-guidance-a35de1

Conversation

@dawsontoth

Copy link
Copy Markdown
Contributor

Enhances the inline "How to read it in your component" guidance on the cluster Secrets config page (the add/edit dialogs) in two ways.

1. Practical secrets.subscribe() usage (harper#1787)

The scoped-tier example now demonstrates the live-change API: read the value immediately, then subscribe in a fire-and-forget background task so module load isn't blocked — with a guard so the first (current-value) yield doesn't trigger a redundant rebuild:

import { secrets } from 'harper';

// Use the current value right away — read at your module's top level.
let value = secrets.STRIPE_KEY;

// Then react to rotations without blocking startup. Subscribing in a background
// task lets module load finish, so the rest of your app keeps initializing.
(async () => {
	for await (const next of secrets.subscribe('STRIPE_KEY')) {
		if (next === value) continue; // the first yield is the current value you already have
		value = next; // rotated — rebuild anything bound to the secret (e.g. an API client) here
	}
})();

The global / process.env tier example is unchanged — per harper#1787's tier semantics, subscribe there is reload-only (not live), so a hot-swap example would mislead. Name-aware bracket notation is preserved (secrets['deploy.my-app']).

2. Outward documentation links

  • A contextual "Learn more about secrets in the Harper docs" link right under the code example (covers the config page and any delivery-enabled surface).
  • A page-level "Secrets Docs" link in the Secrets config toolbar, matching the existing SSH-keys / certificates convention.

Both point to https://docs.harperdb.io/reference/v5/security/secrets (the current canonical docs structure). Plain <a target="_blank" rel="noreferrer"> — the repo's dominant external-link pattern — rather than TanStack Link, so the isolated dialog test stays router-free.

Docs

The docs page that backs the subscribe() example is HarperFast/documentation#584 (stacked on the secrets-store docs PR #581). It predated harper#1787, so #584 adds the secrets.subscribe() section, the live-accessor note, and the per-tier change table.

Verification

  • accessExample.test.ts and SecretsManager.delivery.test.tsx updated; the delivery test renders the actual dialog DOM and asserts the subscribe line, the change-guard, and the doc-link href. 14/14 secrets tests pass; tsc --noEmit, dprint check, and oxlint all clean.

Note: committed unsigned — this environment has no TTY for gpg pinentry, so signing failed. Happy to amend with a signed commit if the branch protection needs it.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 50.92% 5421 / 10645
🔵 Statements 51.49% 5805 / 11272
🔵 Functions 43.11% 1327 / 3078
🔵 Branches 44.42% 3660 / 8239
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/instance/config/secrets/index.tsx 0% 0% 0% 0% 27-183
src/features/instance/secrets/SecretDeliveryPicker.tsx 100% 100% 100% 100%
src/features/instance/secrets/SecretsManager.tsx 96.55% 91.42% 94.11% 100% 111
src/features/instance/secrets/accessExample.ts 100% 100% 100% 100%
Generated in workflow #1537 for commit 75207c2 by the Vitest Coverage Report Action

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request updates the scoped secrets access example to read the live accessor and subscribe to secret rotations asynchronously using secrets.subscribe(), replacing the previous static destructuring approach. It also adds links to the Harper secrets documentation in the UI and updates the corresponding tests. The reviewer suggested wrapping the asynchronous subscription loop in a try-catch block to prevent unhandled promise rejections from crashing the application, as this code is intended to be copied and pasted by users.

Comment thread src/features/instance/secrets/accessExample.ts
dawsontoth added a commit that referenced this pull request Jul 14, 2026
Address review on #1501: the fire-and-forget subscribe loop is copy-paste
code, so wrap it in try/catch and console.error the failure (surfacing it
rather than swallowing it) so a subscription error can't become an unhandled
promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dawsontoth added a commit to HarperFast/documentation that referenced this pull request Jul 14, 2026
Mirror the studio review fix (HarperFast/studio#1501): the fire-and-forget
subscribe loop is copy-paste code, so wrap it in try/catch and console.error
the failure so a stream error can't become an unhandled promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kriszyp pushed a commit to HarperFast/documentation that referenced this pull request Jul 14, 2026
…584)

* style(operations-api): apply prettier table alignment

Pre-existing format:check violation on the kris/secrets-docs branch; the
repo-wide prettier gate must be clean for CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(security): document secrets change subscription and live scoped accessor

Documents harper#1787 (live secret change detection) on the new secrets
reference page:

- secrets.subscribe(name) — async iterable yielding the current value then
  every change; shown as read-now + fire-and-forget background subscribe so
  module load isn't blocked (the practical hot-swap-on-rotation pattern).
- Live scoped-tier accessor: a fresh secrets.NAME read reflects the latest
  value; a destructure is now called out as a point-in-time copy.
- Reworked 'Healing after changes' into a per-tier 'How each tier sees a
  change' table (scoped = live; global/process.env = reload-only) and
  corrected the 'frozen' accessor description to the read-only live view,
  with subscribe as a non-enumerable member.

Ships in v5.2.0 alongside the rest of the secrets store, so no differential
VersionBadge (the page is uniformly 5.2.0 surface).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(security): dedupe the subscribe example's startup rebuild

Address review on #584: secrets.subscribe() replays the current value
first, so guard on key change to skip the redundant client rebuild on
startup, and note that semantic inline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(security): guard the subscribe example against unhandled rejections

Mirror the studio review fix (HarperFast/studio#1501): the fire-and-forget
subscribe loop is copy-paste code, so wrap it in try/catch and console.error
the failure so a stream error can't become an unhandled promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(security): lead with the live accessor; subscription is the exception

Per feedback: reading secrets.NAME (undestructured) returns the live value
on every read, so most components just use the secret where they need it —
e.g. per request inside a resource — and get the current value with no
subscription. Only a destructure takes a snapshot. Reframe subscribe() as
the case for long-lived objects built from a secret (client/pool/signer)
that must be rebuilt on rotation, and make the loader note's per-loader
boundary explicit (vm/compartment read live anywhere; native resolves only
during load).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth
dawsontoth marked this pull request as ready for review July 14, 2026 18:57
@dawsontoth
dawsontoth requested a review from a team as a code owner July 14, 2026 18:57
Comment thread src/features/instance/config/secrets/index.tsx Outdated
dawsontoth added a commit that referenced this pull request Jul 14, 2026
Address review on #1501: passing the 'Secrets Docs' link as SecretsManager
children put it after Refresh/Add (far right), unlike the sshKeys/certificates
pages where the docs link is first. Add a dedicated `docsLink` slot rendered
before Refresh/Add and use it from the Secrets config page, matching the
convention. The `children` slot (used by the .env editor for its raw-editor
button) still renders after Add. Regression test asserts the ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth
dawsontoth requested a review from cb1kenobi July 15, 2026 14:07
dawsontoth and others added 3 commits July 17, 2026 10:07
…ance

The inline 'How to read it in your component' guidance on the cluster
Secrets config page now shows the live-change API from harper#1787:

- Scoped-tier example reads the value immediately, then subscribes in a
  fire-and-forget background task so module load isn't blocked, guarding on
  change so the first (current-value) yield doesn't trigger a redundant
  rebuild. The global/process.env tier stays reload-only (subscribe there is
  not live), so its example is unchanged.
- Outward links to the Harper secrets docs (reference/v5/security/secrets):
  one contextual link by the code example, one in the config page toolbar.

Docs backing the example: HarperFast/documentation#584.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review on #1501: the fire-and-forget subscribe loop is copy-paste
code, so wrap it in try/catch and console.error the failure (surfacing it
rather than swallowing it) so a subscription error can't become an unhandled
promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review on #1501: passing the 'Secrets Docs' link as SecretsManager
children put it after Refresh/Add (far right), unlike the sshKeys/certificates
pages where the docs link is first. Add a dedicated `docsLink` slot rendered
before Refresh/Add and use it from the Secrets config page, matching the
convention. The `children` slot (used by the .env editor for its raw-editor
button) still renders after Add. Regression test asserts the ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the claude/cluster-secrets-config-guidance-a35de1 branch from 7f304a5 to 75207c2 Compare July 17, 2026 14:08
@dawsontoth
dawsontoth merged commit edd32de into stage Jul 17, 2026
2 checks passed
dawsontoth added a commit that referenced this pull request Jul 17, 2026
Address review on #1501: the fire-and-forget subscribe loop is copy-paste
code, so wrap it in try/catch and console.error the failure (surfacing it
rather than swallowing it) so a subscription error can't become an unhandled
promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth
dawsontoth deleted the claude/cluster-secrets-config-guidance-a35de1 branch July 17, 2026 14:46
dawsontoth added a commit that referenced this pull request Jul 22, 2026
Address review on #1501: the fire-and-forget subscribe loop is copy-paste
code, so wrap it in try/catch and console.error the failure (surfacing it
rather than swallowing it) so a subscription error can't become an unhandled
promise rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dawsontoth added a commit that referenced this pull request Jul 22, 2026
Address review on #1501: passing the 'Secrets Docs' link as SecretsManager
children put it after Refresh/Add (far right), unlike the sshKeys/certificates
pages where the docs link is first. Add a dedicated `docsLink` slot rendered
before Refresh/Add and use it from the Secrets config page, matching the
convention. The `children` slot (used by the .env editor for its raw-editor
button) still renders after Add. Regression test asserts the ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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