Skip to content

docs(cli): token credentials in the canonical auth precedence (v5.2.0) - #630

Draft
dawsontoth wants to merge 2 commits into
mainfrom
claude/cli-token-auth-docs
Draft

docs(cli): token credentials in the canonical auth precedence (v5.2.0)#630
dawsontoth wants to merge 2 commits into
mainfrom
claude/cli-token-auth-docs

Conversation

@dawsontoth

Copy link
Copy Markdown
Contributor

Documents the CI/CD token credentials that shipped in v5.2.0 via harper#1876 and never reached the reference docs.

Split out of #599, where this content was blocked behind four unmerged upstream PRs. It has no upstream dependency of its own — the behavior is live in v5.2.0 and has been since 2026-07-31 — so it can be reviewed and merged independently. That answers the first of @Ethan-Arrowood's two sequencing questions on #599.

Why this was worth separating

HARPER_CLI_REFRESH_TOKEN and HARPER_CLI_OPERATION_TOKEN are the credentials the CLI is meant to use in a pipeline, and the Authentication Precedence list — the one place that states which credential wins — stopped at the saved harper login token. A reader following the docs to set up CI had no way to learn the two variables exist, let alone where they rank.

What changed

reference/cli/authentication.md

  • Bearer tokens slotted into the existing numbered precedence list at position 5, above the saved login token and above the legacy username=/password= fallback. Folded into the canonical list rather than stated as a second, competing paragraph, so the order is given once.
  • Added to Method 2's supported-variable list, with the namespace rule: whichever prefix supplies a token owns both halves of it.
  • New Token credentials for CI/CD subsection covering harper login --for-ci, the GitHub Actions step shape, refresh behavior, lifetimes, and the one-refresh-token-per-user warning.

reference/cli/commands.md--for-ci on harper login, including the stdout/stderr split that makes | gh secret set --env-file - work without displaying the token.

Behavior documented that was only visible in the source

These are the parts a reader cannot infer, and the reason the section runs longer than a variable list:

  • Token variables are ignored for local operations. A local command goes over the domain socket, which the server already trusts via bypassLocalAuth — and that bypass is an else if on "no Authorization header present". Attaching a bearer token to a local request opts out of the trust and gets validated instead, 401ing on a token minted for another cluster. Since these variables are meant to persist across a whole CI job or a developer's shell, reading them unconditionally would break every local harper command in that environment.
  • A token namespace is selected as a unit. If HARPER_CLI_OPERATION_TOKEN could pair with CLI_TARGET_REFRESH_TOKEN, commands would run as the first identity until its operation token expired and then silently continue as the second.
  • A set-but-blank namespace reports and falls back to saved login credentials rather than silently running as whoever last logged in on that machine — the usual shape of a misconfigured CI secret.
  • An env-sourced refreshed token stays in memory. There is no credentials-file entry to persist it to, unlike a token refreshed from ~/.harperdb/credentials.json.
  • One refresh-token hash per user. createTokens overwrites hdb_user.refresh_token and refresh validation accepts only that hash, so minting a token for CI revokes whatever token that user already held — invisibly, until the other holder's next refresh 401s. This is why the section pushes a dedicated CI user rather than mentioning it as a nicety. Named, independently revocable credentials are harper#2018.

Verification

Checked against harper v5.2.2, not against the PR that introduced the feature:

Claim Source
Precedence order, local-target gate, namespace unit, blank-namespace fallback bin/cliOperations.ts
In-memory vs. persisted refresh refreshExpiredOperationToken, persistKey
--for-ci emits two lines on stdout; fails rather than emitting a half-block bin/login.ts
1d / 30d lifetime defaults config/configUtils.ts
One refresh-token hash per user security/tokenAuthentication.ts

npm run build and npm run format:check are clean. The build reports two broken anchors; both are pre-existing on main (verified by building main with these changes stashed) and neither is in a file this PR touches.

Notes for review

  • No companion marker: nothing here is gated on an unmerged PR, so it gets an immediate pass once #629's check becomes required.
  • Opened as a draft only to match the rest of this stack. Unlike the others there is nothing to wait for — promote it whenever you want it reviewed.
  • The v5.2.0 badge on the --for-ci heading is a real release, not a placeholder. The rest of the deploy stack has moved to v5.3.0 (see docs(deploy): two-phase deploy, revert, by-reference deploys, and OIDC trusted publishing #599).

Description drafted by Claude Code (Opus 5).

`HARPER_CLI_REFRESH_TOKEN` / `HARPER_CLI_OPERATION_TOKEN` shipped in v5.2.0
(harper#1876) but never reached the reference docs. The Authentication
Precedence list stopped at the saved `harper login` token, so the two variables
a CI pipeline is supposed to use were absent from the one place that states
which credential wins.

Folded into main's existing numbered list rather than stated separately, so the
order is given once: bearer tokens sit at position 5, above the saved login
token and above the legacy `username=`/`password=` fallback, which is what
cliOperations.ts does.

Also documents the parts that are only discoverable by reading the source:

- Token variables apply to remote targets only. A local operation goes over the
  domain socket and is already trusted, so attaching a bearer token there would
  opt out of that trust and 401 — hence the deliberate ignore.
- A token namespace owns both halves. `HARPER_CLI_OPERATION_TOKEN` cannot pair
  with `CLI_TARGET_REFRESH_TOKEN`, which would otherwise run as one identity
  until expiry and then silently continue as another.
- A set-but-blank namespace reports and falls back rather than silently running
  as whoever last logged in locally.
- An env-sourced refreshed token stays in memory; there is no file entry to
  persist it to.
- One refresh-token hash per user, so `--for-ci` as your own account revokes
  your own token. Hence the dedicated-CI-user warning.

`harper login --for-ci` is documented in commands.md with the stdout/stderr
split that makes `| gh secret set --env-file -` work without displaying the
token.

Verified against harper v5.2.2: bin/cliOperations.ts (precedence and refresh),
bin/login.ts (--for-ci output), config/configUtils.ts (1d / 30d defaults).

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

@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 CLI documentation to cover the new token-based authentication features for CI/CD pipelines introduced in v5.2.0, including the HARPER_CLI_OPERATION_TOKEN and HARPER_CLI_REFRESH_TOKEN environment variables and the harper login --for-ci command. Feedback is provided regarding a contradiction in the documentation text where a blank token variable is described as an error but is immediately explained as falling back to saved credentials.


**Refresh behavior.** The CLI mints an operation token from the refresh token when none is supplied, and again whenever the supplied one has expired. A token refreshed from an environment variable is held in memory for that invocation only — nothing is written to `~/.harperdb/credentials.json`, because there is no file entry for an environment-supplied credential. If the refresh token itself is rejected, the command reports that and exits non-zero rather than falling back to another identity.

**A blank token variable is an error, not a fallback.** If a namespace is set but empty — the usual shape of a misconfigured CI secret — the CLI says so and falls back to saved login credentials. It does not silently run as whoever last logged in on that machine.

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.

medium

The heading 'A blank token variable is an error, not a fallback.' contradicts the description immediately following it, which states that the CLI 'falls back to saved login credentials'. Since the CLI does indeed fall back (albeit with a warning/report), it is more accurate to describe this behavior as a warning before falling back, rather than stating it is not a fallback.

Suggested change
**A blank token variable is an error, not a fallback.** If a namespace is set but empty — the usual shape of a misconfigured CI secret — the CLI says so and falls back to saved login credentials. It does not silently run as whoever last logged in on that machine.
**A blank token variable warns before falling back.** If a namespace is set but empty — the usual shape of a misconfigured CI secret — the CLI warns you of the empty variable and falls back to saved login credentials. It does not silently run as whoever last logged in on that machine.

@github-actions
github-actions Bot temporarily deployed to pr-630 August 17, 2026 20:01 Inactive
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-630

This preview will update automatically when you push new commits.

The other half of @kriszyp's discoverability finding, for the piece that
actually shipped in v5.2.0. A user who only reads release notes had no way to
discover that a pipeline can authenticate with a token instead of an admin
password.

States the two properties that change how someone sets this up: the token
variables outrank a saved login (so a runner with both uses the CI identity),
and one refresh-token hash per user means issuing a token for CI revokes your
own if you run it as yourself.

The 5.3 surface goes in 5.3.md, added on #599.
dawsontoth added a commit that referenced this pull request Aug 18, 2026
Restacks this branch on #630, which carries the `harper login --for-ci` /
`HARPER_CLI_REFRESH_TOKEN` content that used to live here. That content
documents behavior shipped in v5.2.0, so it no longer belongs behind this
branch's four unmerged upstream dependencies.

Three conflicts, resolved as follows.

`reference/cli/authentication.md` and `reference/cli/commands.md` — took #630's
side wholesale. Everything this branch had added to those two files was the
token content that moved, and #630 states it against main's current structure
rather than the structure this branch forked from. That also settles the
duplicate-precedence hazard @Ethan-Arrowood raised: main grew a canonical
numbered Authentication Precedence section after this branch forked, and the
token rules are now folded into that list instead of restated beside it. Merging
this branch no longer gives the precedence order twice, differently.

`reference/cli/operations-api-commands.md` — hand-merged, because neither side
was correct alone. Taking ours would have silently dropped the seven backup rows
main added since the fork (`create_backup`, `list_backups`, `verify_backup`,
`delete_backup`, `purge_backups`, `restore_backup`, `get_backup`); taking theirs
would have dropped this branch's four deploy rows and its recategorization. The
merged table has all three sets:

- main's seven backup rows, unchanged.
- This branch's `stage` / `activate` / `revert_component` / `revert` rows.
- This branch's recategorization: the six user/role rows point at
  `#users--roles` and the seven SSH rows at `#components`, instead of the
  `#certificate-management` anchor that documented neither. The five genuine
  certificate rows still point there.

Compared both sides row by row before resolving: no description text differed,
so the column-width churn is only Prettier re-padding.

`operations.md` and `applications.md` auto-merged; main's new `host` badge
survives.

Build clean. The two broken anchors it reports are pre-existing on main
(confirmed by building main with these changes stashed) and are in files this
branch does not touch.
dawsontoth added a commit that referenced this pull request Aug 18, 2026
The v5.2.0 placeholder this branch has carried since July is wrong, and now
demonstrably so rather than provisionally. harper `main` is at **v5.2.2**
(tagged 2026-08-13) and by_ref merged 2026-08-14, after that tag and in no
release. The v5.3.0 upgrade directive on harper#2173 confirms which release is
next. So none of this surface can ship in a 5.2.x patch:

| Surface | Upstream | Ships in |
| --- | --- | --- |
| two-phase deploy, `revert_component`, staging retention | harper#1849 | v5.3.0 |
| `deploy by_ref=true` | harper#1850 (merged, untagged) | v5.3.0 |
| `deploy setup=true` | harper#1851 | v5.3.0 |
| `add_ssh_key generate: true` | harper-pro#594 | v5.3.0 |

The one piece that genuinely shipped in v5.2.0 — `login --for-ci` and the token
env vars (harper#1876) — left this branch for #630, so nothing here is v5.2.0
any more.

Addresses @kriszyp's two versioning findings. Both plain-text `Available since`
lines are now `<VersionBadge>`, standalone under their headings per
CONTRIBUTING.md, and the API reference no longer reads as though these
behaviors exist throughout v5:

- `deploy_component` — changed badge; it is existing surface whose cluster
  behavior changed.
- `revert_component` — added badge, and the row it was missing from the
  Components summary table.
- `add_ssh_key`'s `generate` subsection — added badge.

Release-notes coverage, the other half of those findings, follows in a separate
commit once the OIDC surface is in.
dawsontoth added a commit that referenced this pull request Aug 18, 2026
@kriszyp's eight findings, re-checked against the current state of harper#1849
and harper-pro#594 rather than the state they were filed against. Four had been
fixed at the source in the meantime; four needed the docs changed.

**"All-or-nothing at go-live" was wrong, and self-contradictory.** The page
claimed it in the opening paragraph and then correctly described a version split
twenty lines later. Reframed around what the two phases actually buy: an
all-nodes staging barrier, which eliminates the common failure (a node that
can't fetch or install) but not a per-node activation failure. The
activation-failure paragraph is now its own `#### Activation failures` section
instead of sitting mid-parameter-list, and the opening paragraph links to it.

**The deploy modes are now documented as a matrix**, which core made possible by
adding the validation @kriszyp asked for. `activate: false` and `deployment_id`
are rejected outright when combined with `two_phase: false`, `replicated:
false`, or a cluster whose `system` database isn't replicated, so a request that
says stage-only can no longer activate. `two_phase: true` is likewise rejected
without the replication to support it. `revert_on_failure` is refused rather
than accepted and ignored.

**The `system`-replication precondition is stated** — @Ethan-Arrowood's second
question. A plain deploy on a cluster with `system` excluded from replication
still silently takes the one-shot path, and that is the case worth warning
about, because it also means no rollback target.

**`revert_component`'s guarantees are scoped to the two-phase path.** The
retained `.deploy-previous` copy is created by activation, so a component
deployed one-shot has nothing to revert to however many times it has been
deployed — the docs implied repeated deploys build up a rollback target. Also
documents that `restart` is optional (a reverted component keeps serving the
rolled-away-from code until something restarts it) and adds the `force`
parameter the validator accepts.

**The SSH-key encryption guarantee is qualified.** `sealSSHKey` in harper-pro
still falls back to storing and replicating the private key in plaintext with
only a WARN when no secret custody is registered, and that fallback is
deliberate — SSH keys predate custody. So encryption at rest is a property of
the deployment's configuration, not of the operation, and `generate: true` in
particular reads as though the key could never be exposed. Custody is present by
default, so the warning says that too rather than implying the risk is likely.

Fixed at the source, no doc change needed:

- **Explicit refs now resolve to a SHA** (harper 5c85714b8, 26cce986d,
  21043f994) — the guarantee the page claimed is now real. The fuller treatment,
  including remote resolution and failing closed, is in #617.
- **`auth_*` vs. ordinary `username=` precedence** — `main` grew a canonical
  numbered precedence section covering exactly this, and #630 folds the token
  rules into it.
- **Version badges** — previous commit.
dawsontoth added a commit that referenced this pull request Aug 18, 2026
…rface

Completes @kriszyp's discoverability findings. The reference pages carry version
badges now, but a user who only reads release notes had no way to find any of
this — and there was no 5.3 page at all.

The directory is autogenerated into the sidebar, so the new page needs no
registration.

Covers, in the house style of 5.2.md: two-phase cluster deploys,
stage-now/activate-later, component rollback, deploying by git reference, sealed
deploy credentials, OIDC trusted publishing, and server-side SSH key generation.

Each entry states the limits alongside the capability rather than leaving them to
the reference page, because these are the ones that change whether a reader
should adopt the feature: the two-phase barrier is not an all-or-nothing
go-live, it silently degrades on a cluster whose `system` database isn't
replicated, rollback reaches back exactly one two-phase activation, and the
GitHub Actions ref-gate rule will refuse policy shapes that npm accepts.

`login --for-ci` and the token env vars are not here — they shipped in v5.2.0
and belong in 5.2.md, which is #630's.
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-630

This preview will update automatically when you push new commits.

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