@harperfast/template-vue-ts-studio 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2320,24 +2320,25 @@ await fetch('http://localhost:9926/JokeCache/1', {
2320
2320
 
2321
2321
  ### 4.1 Deploying to Harper Fabric
2322
2322
 
2323
- Instructions for the agent to follow when deploying a Harper application to a remote Harper Fabric cloud cluster.
2323
+ Instructions for the agent to follow when deploying a Harper application to a Harper Fabric cluster, or any remote Harper instance, with the `harper` CLI, and when checking, staging, or going back to a release.
2324
2324
 
2325
2325
  #### When to Use
2326
2326
 
2327
- Apply this rule when deploying a Harper application to a remote Harper Fabric cluster, whether from a local machine or a CI/CD pipeline. Use it to configure authentication, select a package source, and run `harper deploy` with the correct parameters.
2328
-
2329
- See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md) to set up a cluster before deploying.
2327
+ Apply this rule when deploying to a remote cluster, choosing how it restarts, reading a deploy's `certification`, staging a release to make live later, or rolling back to a previous release. To deploy from CI with no stored credential, see [deploying-from-ci.md](deploying-from-ci.md). To set up the cluster first, see [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md).
2330
2328
 
2331
2329
  #### How It Works
2332
2330
 
2333
- 1. **Authenticate against the cluster**: Run `harper login` once, pointing at the cluster's Application URL (found on the cluster's **Config → Overview** page). The CLI stores the token so subsequent commands need no credentials. If the current directory already has a `.env` file that sets no target, it also appends `HARPER_CLI_TARGET` to it.
2331
+ 1. **Log in to the cluster once**: Pass the cluster's **Application URL**, from its **Config → Overview** page, to `harper login`. The CLI stores the token, so later commands don't repeat credentials. If the current directory already has a `.env` file that sets no target, it also appends `HARPER_CLI_TARGET` to it.
2334
2332
 
2335
2333
  ```bash
2336
2334
  harper login <Application URL>
2337
2335
  # Provide cluster username and password when prompted
2338
2336
  ```
2339
2337
 
2340
- 2. **Deploy the application**: After logging in, run `harper deploy` without repeating credentials. Omit `package` to package and deploy the current local directory.
2338
+ - For CI/CD on GitHub Actions, use workload identity (OIDC) instead, so the pipeline stores no Harper credential. From v5.4.0, `harper deploy setup=true provider=github-actions` sets it up in one command: see [deploying-from-ci.md](deploying-from-ci.md). Elsewhere, give the pipeline a refresh token from `harper login --for-ci` rather than a password.
2339
+ - For a one-off command, `auth_username=` and `auth_password=` also work, and take precedence over environment variables and saved login tokens. Don't use them in production.
2340
+
2341
+ 2. **Deploy with a restart**: Run `harper deploy`, with `restart=true` or `restart=rolling` so the release is certified before it serves (step 3).
2341
2342
 
2342
2343
  ```bash
2343
2344
  harper deploy \
@@ -2348,141 +2349,494 @@ See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-clu
2348
2349
  replicated=true
2349
2350
  ```
2350
2351
 
2351
- 3. **Choose a package source**: Set `package` to any valid npm dependency value.
2352
+ - On Harper Pro and Fabric, a deploy goes to every node in the cluster unless you pass `replicated=false`. Harper core on its own does not replicate.
2353
+ - `project` defaults to the current directory's name for a directory deploy, or is derived from the package for a package deploy.
2354
+ - A bare `target` host defaults to `https://<host>:9925`.
2355
+ - Pass `json=true` to print the result as JSON instead of YAML.
2356
+ - Every result carries a `deployment_id`, staged or not. It names this release when you go back to it later (step 4).
2357
+
2358
+ 3. **Choose the restart, and read `certification`**: `restart=true` restarts Harper after deploying. On a cluster, use `restart=rolling`, the staggered, zero-downtime restart: the command exits `0` once the node you called has taken the release and started the rolling restart's job, and returns its `restartJobId`.
2352
2359
 
2353
- | Value | Meaning |
2354
- | ------------------------------------------------------------ | ---------------------------------------------- |
2355
- | _(omit)_ | Package and deploy the current local directory |
2356
- | `package="@harperdb/status-check"` | npm package |
2357
- | `package="HarperFast/status-check"` | GitHub shorthand |
2358
- | `package="https://github.com/HarperFast/status-check"` | GitHub URL |
2359
- | `package="git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo (SSH) |
2360
- | `package="https://example.com/application.tar.gz"` | Tarball |
2360
+ Both certify the release in a canary worker before rolling it out. The canary replaces worker 0 and loads every component, but is held out of traffic until it reports whether the deployed component loaded. Until then, the workers already running keep serving the previous release. A deploy that succeeds, and is not staged, reports the result in `certification`:
2361
2361
 
2362
- When using git tags, use the `semver` directive:
2362
+ | `certification` | Meaning |
2363
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2364
+ | `certified` | The canary loaded the release, and the rollout went on. |
2365
+ | `uncertified` | The rollout went on unchecked: the component ran nothing as it loaded (`loadComponent: dev-only`, an absent `if-installed` component, or safe mode), or no worker was replaced. |
2366
+ | `unavailable` | The deploy restarted unchecked: no worker on this node loads the component, its `isolated` setting changed, it was already live, or it was deployed from a local directory (`package: file:<dir>`). |
2367
+ | `not-requested` | The deploy did not restart. |
2368
+ - **A rejected release fails the deploy.** If the canary throws while loading the component, exits, or doesn't report within a minute, the deploy fails with `400`. The release it replaced is made live again, and nothing is replicated to other nodes. The error's `certification` is an object whose `status` is `rejected`, or `interrupted` for a restart that stopped before the canary decided.
2369
+ - **A release with nothing to go back to fails closed.** A rejected first deploy, or one whose previous release was not kept, stays on disk, but no worker on that node loads it, even after Harper restarts. `get_status` reports the component as failed. Deploy a fix, or once the cause is fixed, activate the same `deployment_id` again with `restart`.
2370
+ - **A rolling deploy exits before every node has taken the release.** The other nodes stage it, and the job activates it on each in turn, each with a canary of its own. A node that rejects it keeps the previous release, or fails closed if it had none. Nothing is rolled back on the others, and the job fails naming each node that did not activate it. The deployment record reports `success` while the job runs and after it fails, so poll the job instead: `harper get_job id=<restartJobId>` ends `COMPLETE`, or `ERROR` with a `message` listing each node's outcome under `activated`.
2371
+ - **Poll the node that received the deploy.** A job is recorded only on the node that ran it, so any other node answers `get_job` with an empty array. A role with an `operations` allowlist, or a token with an `operations` scope, must list `get_job` to poll it.
2372
+ - Another deploy or `drop_component` of the component on that node is refused with `409` until the rollout has finished.
2373
+ - A node running a version before v5.4.0 activates the release without a canary.
2363
2374
 
2375
+ 4. **Go back to a previous release by its `deployment_id`**: When a deploy replaces the live release, Harper keeps the replaced one under the `deployment_id` that deployed it. Activating that id puts back the exact installed release with no fetch, rebuild, or reinstall, where deploying an older commit installs it again from source. Find the id with `list_deployments`, then deploy it. On a cluster, restart with `rolling` and poll its job (step 3):
2376
+
2377
+ ```bash
2378
+ harper list_deployments project=my-app status=success
2379
+ harper deploy project=my-app deployment_id=<id> restart=rolling
2364
2380
  ```
2365
- HarperFast/application-template#semver:v1.0.0
2381
+
2382
+ ```json
2383
+ {
2384
+ "operation": "deploy_component",
2385
+ "project": "my-app",
2386
+ "deployment_id": "<id of the deployment you want back>",
2387
+ "restart": "rolling"
2388
+ }
2366
2389
  ```
2367
2390
 
2368
- 4. **Deploy by reference** (pinned commit): Use `by_ref=true` to send a pinned git reference instead of uploading a snapshot. The cluster fetches and builds that exact commit.
2391
+ - `list_deployments` returns records newest first. Filter by `project` and `status`; omit `limit` to return every matching record. A deploy in progress reads `pending`, or `loading`, until its `completed_at` is set.
2392
+ - The newest `success` is not necessarily the release every node is serving: a rolling deploy's record says `success` before the other nodes have taken the release, and keeps saying it if one rejects it.
2393
+ - Redeploy an older commit by `ref` (step 7) only when the release is no longer kept.
2394
+ - The activation restores the root config entry that deployment published. The release it replaces is kept in turn, so you can go forward again the same way.
2395
+ - Kept releases and staged builds count against `deployment.stagingRetention.maxCount` (default `5`). Each kept release is a full installed copy of the component, `node_modules` included, so budget disk for it. `0` keeps no replaced release.
2396
+ - A release made live before v5.3.0, or deployed from a `file:` directory, is not kept.
2397
+ - Each node answers for itself. A node that never had the release, or no longer keeps it, answers `404`, which `deploy_component` reports as a failed peer. `409` means the build is there but can't be activated.
2369
2398
 
2370
- ```bash
2371
- harper deploy by_ref=true restart=true replicated=true
2399
+ 5. **Stage a release to make live later**: `activate=false` (`activate: false` in the operation) builds and installs the release on each node without making it live, so the slow, fallible part happens when you choose and the cut-over is quick. The result's `deployment_id` names the staged build:
2400
+
2401
+ ```json
2402
+ {
2403
+ "operation": "deploy_component",
2404
+ "project": "my-app",
2405
+ "package": "npm:@my-org/my-app@1.2.3",
2406
+ "activate": false
2407
+ }
2372
2408
  ```
2373
2409
 
2374
- - `by_ref` — Build the package reference from the local repository.
2375
- - `ref` _(optional)_ — Deploy a specific commit, tag, or branch instead of `HEAD`. Implies `by_ref`. Resolved to a full commit SHA before being sent.
2376
- - `credential` _(optional)_ — Set to `true` to authenticate the clone with the stored credential for the repository's host. Omit for public repositories.
2410
+ ```json
2411
+ {
2412
+ "deployment_id": "a3f8c2d1-...",
2413
+ "message": "Staged: my-app. Deploy it with deploy_component deployment_id=a3f8c2d1-..."
2414
+ }
2415
+ ```
2416
+
2417
+ Make it live by deploying that `deployment_id`, as in step 4. Nothing changes until then, and `get_deployment` reports it as `staged`.
2418
+ - Pass `restart` on the activation, not the stage: `restart` is rejected alongside `activate: false`.
2419
+ - The activation takes no `package`, `payload`, `credentials`, install options, or `urlPath`/`host`. They are rejected, because the staged build already decided them.
2420
+ - Activating the release that is already live succeeds without a swap, so if an activation fails partway (a peer was unreachable, or the root config entry wasn't written), retry the same `deployment_id`. A node that already switched answers success, one still holding the build switches now, and one that holds neither answers `404`; the retry also finishes what the failed attempt left half done. With `restart`, a node where the release is already live still restarts every worker, isolated applications' included. Without it, the node marks the component as needing a restart.
2421
+ - A staged build outlives later deploys: stage v2, deploy v3, and activating v2's id returns the component to v2.
2422
+ - `file:` directory sources cannot be staged.
2423
+ - Upgrade every node before staging. A node before v5.3.0 doesn't recognize `activate: false`, and serves the release immediately. A peer that doesn't confirm staging fails the operation with the node names.
2424
+
2425
+ 6. **Choose a package source**: The `package` field accepts any valid npm dependency value:
2377
2426
 
2378
- Deploy a specific tag or commit:
2427
+ | Source | `package` value |
2428
+ | ----------------------- | --------------------------------------------------------------------------------------------- |
2429
+ | Current local directory | Omit `package` |
2430
+ | npm package | `package="@harperdb/status-check"` |
2431
+ | GitHub (public) | `package="HarperFast/status-check"` or `package="https://github.com/HarperFast/status-check"` |
2432
+ | Private repo (SSH) | `package="git+ssh://git@github.com:HarperDB/secret-app.git"` |
2433
+ | Tarball | `package="https://example.com/application.tar.gz"` |
2434
+
2435
+ For git tags, use the `semver` directive: `HarperFast/application-template#semver:v1.0.0`. For an SSH-based private repo, register the key with `add_ssh_key` first.
2436
+
2437
+ 7. **Deploy by reference for a reproducible source**: `by_ref=true` sends a pinned git reference instead of uploading the working directory, and the cluster fetches and builds that exact commit. It resolves the repository's `origin` remote and the current commit, then deploys `package=git+https://github.com/<owner>/<repo>.git#<full commit SHA>`. Pass `ref` to deploy a specific tag or commit instead of `HEAD`; it implies `by_ref`.
2379
2438
 
2380
2439
  ```bash
2440
+ harper deploy by_ref=true restart=true replicated=true
2381
2441
  harper deploy ref=v1.2.0 restart=true replicated=true
2382
2442
  harper deploy ref=9f8c2a1 restart=true replicated=true
2383
2443
  ```
2384
2444
 
2385
- Valid `ref` values must resolve to `refs/heads/*` or `refs/tags/*`, or a bare branch or tag name. Qualified refs outside those two namespaces (e.g. `refs/pull/123/head`) are rejected. A full commit SHA is always accepted directly with no resolution attempted. Run `git fetch` if a ref can't be resolved, or pass a full commit SHA.
2445
+ - A `ref` is resolved to a full commit SHA before the deploy is sent, from the local checkout or the remote. A full commit SHA is accepted directly. If a `ref` can't be resolved, the deploy stops: run `git fetch` and retry, or pass a full SHA.
2446
+ - Any other `ref` must name something a clone can fetch: `refs/heads/*` or `refs/tags/*`, or a bare branch or tag name. A qualified ref outside those, such as `refs/pull/123/head`, is rejected up front.
2447
+ - Commit and push first: the cluster clones from the remote. `by_ref` warns when the working tree is dirty, and when the commit isn't on any remote branch. The second check is skipped under GitHub Actions.
2448
+ - On a GitHub Actions `pull_request` run, `by_ref` deploys the pull request's head commit. If the event payload isn't readable, the deploy stops; pass the commit explicitly.
2449
+ - The cluster installs and builds from source on each node, so commit your lockfile if the build must be reproducible. If the build can't run on the node, deploy the built output as a payload instead.
2450
+
2451
+ 8. **Deploy a private repository by reference**: Pass `credential=true` with `by_ref=true`. The CLI attaches a `credentials` reference, and the cluster resolves the secret in memory at clone time, so no token travels in the operation body or lands on disk.
2452
+
2453
+ ```bash
2454
+ harper deploy by_ref=true credential=true restart=true replicated=true
2455
+ ```
2456
+
2457
+ Provision that credential once per component and source with `harper deploy setup=true`. (With `provider=github-actions`, it sets up GitHub Actions deploys instead: see [deploying-from-ci.md](deploying-from-ci.md).) It is interactive, and its operations require **super_user**, so run it with an administrative credential, not the CI identity it provisions for. It:
2458
+ 1. Fetches the cluster's public key with `get_secrets_public_key`.
2459
+ 2. Encrypts the token locally into an `enc:v1:` envelope, so the plaintext never leaves your machine.
2460
+ 3. Stores only the ciphertext with `set_secret`, scoped to the component, never in the global `processEnv` tier.
2461
+ 4. Grants the component permission to resolve it with `grant_secret`.
2462
+ 5. Prints the `credentials` reference for the deploy to use.
2463
+
2464
+ Use a **fine-grained** personal access token with **Contents: Read-only** on that one repository. Avoid the `gh` CLI's session token: it typically carries `repo`, `read:org`, `gist`, and `workflow` scopes across your whole account. The sealed token is durable, and later deploys reuse it.
2465
+
2466
+ #### Examples
2467
+
2468
+ **Deploy after logging in, and wait for a rolling restart:**
2469
+
2470
+ ```bash
2471
+ harper login https://my-cluster.harperdbcloud.com
2472
+ harper deploy \
2473
+ project=my-app \
2474
+ package="HarperFast/my-app" \
2475
+ target=https://my-cluster.harperdbcloud.com \
2476
+ restart=rolling \
2477
+ json=true > deploy.json
2478
+ # Poll the node that received the deploy until the job is COMPLETE or ERROR
2479
+ harper get_job id=<restartJobId from deploy.json> json=true
2480
+ ```
2481
+
2482
+ **Roll back to the release a deploy replaced, node by node:**
2483
+
2484
+ ```bash
2485
+ harper list_deployments project=my-app status=success
2486
+ harper deploy project=my-app deployment_id=<previous deployment_id> restart=rolling json=true
2487
+ # Then poll its restartJobId with get_job, as above
2488
+ ```
2489
+
2490
+ **Stage a release now, and make it live in a maintenance window:**
2491
+
2492
+ ```bash
2493
+ harper deploy project=my-app package=npm:@my-org/my-app@1.2.3 activate=false
2494
+ # Later, with the deployment_id the stage returned
2495
+ harper deploy project=my-app deployment_id=<staged deployment_id> restart=rolling
2496
+ ```
2497
+
2498
+ **Deploy a pull request's head commit by reference in GitHub Actions:**
2499
+
2500
+ ```bash
2501
+ harper deploy ref=${{ github.event.pull_request.head.sha }} restart=true replicated=true
2502
+ ```
2503
+
2504
+ **Deploy a private repository by reference:**
2505
+
2506
+ ```bash
2507
+ # Once, as super_user
2508
+ harper deploy setup=true
2509
+
2510
+ # Every deploy
2511
+ harper deploy by_ref=true credential=true restart=true replicated=true
2512
+ ```
2386
2513
 
2387
- **Commit and push before deploying by reference.** The cluster clones from the remote and only sees pushed commits.
2514
+ ### 4.2 Deploying a Harper Application from GitHub Actions with OIDC
2388
2515
 
2389
- 5. **Roll back to a previous release**: Activate a kept release by its `deployment_id` to restore the exact installed release without a fetch, rebuild, or reinstall.
2516
+ Instructions for the agent to configure and execute credential-free Harper deployments from GitHub Actions using OIDC trusted publishing, including one-command setup and complete workflow examples.
2517
+
2518
+ #### When to Use
2519
+
2520
+ Apply this rule when asked to deploy from CI, to set up or fix a deploy workflow (including the `.github/workflows/deploy.yaml` that create-harper projects ship), or to move a pipeline off a stored Harper credential such as `HARPER_CLI_REFRESH_TOKEN`. Use it when setting up OIDC trust for the first time, writing or debugging a deploy workflow, or revoking/rotating pipeline access.
2521
+
2522
+ #### How It Works
2523
+
2524
+ ##### One-Command Setup (Branch-Based Deploys)
2525
+
2526
+ 1. **Run setup once** from the project checkout, signed in as a `super_user`:
2390
2527
 
2391
2528
  ```bash
2392
- harper deploy project=<name> deployment_id=<id> restart=true
2529
+ harper login https://my-cluster.example.com:9925
2530
+ harper deploy setup=true provider=github-actions project=my-app
2393
2531
  ```
2394
2532
 
2395
- 6. **Provision a deploy credential for private repositories**: Run `harper deploy setup=true` once per component and source. This requires **super_user** privileges — run it with an administrative credential.
2533
+ If your workflow file is not `.github/workflows/deploy.yaml`, pass `workflow=` explicitly — otherwise the policy pins `deploy.yaml` and runs of your workflow cannot match it:
2396
2534
 
2397
2535
  ```bash
2398
- harper deploy setup=true
2536
+ harper deploy setup=true provider=github-actions project=my-app workflow=.github/workflows/deploy.yml
2399
2537
  ```
2400
2538
 
2401
- The setup flow:
2402
- 1. Fetches the cluster's public key with `get_secrets_public_key`.
2403
- 2. Encrypts the token locally into an `enc:v1:` envelope.
2404
- 3. Stores only the ciphertext with `set_secret`, scoped to the component.
2405
- 4. Grants the component permission to resolve it with `grant_secret`.
2406
- 5. Prints the `credentials` reference for the deploy to use.
2539
+ 2. **Understand what setup does** — in order:
2540
+ - Resolves the GitHub repository's numeric id and canonical name from the `origin` remote (or `repo=<owner>/<name>`). Pass `repository_id=<id>` when the API is unavailable (e.g., private repo without `gh`).
2541
+ - **The workflow is checked before anything is written.** It must be able to run on `main` (`branch=`) and run a job in the `production` environment (`environment=`) with `permissions: id-token: write`; one that never could stops setup. What it can't decide is reported as not verified.
2542
+ - Creates a role and a user named `<project>-ci-deploy` with permissions limited to `deploy_component`, `get_job`, and `registration_info`, and a trust policy named `github-actions-<project>`.
2543
+ - Reads the policy back and compares it claim by claim with what it intended to write.
2544
+ - Sets the repository's `HARPER_CLI_TARGET` Actions variable via `gh` if one does not already exist.
2407
2545
 
2408
- The plaintext never leaves your machine. Prefer a **fine-grained** personal access token with **Contents: Read-only** on the target repository. Avoid broad session tokens — they typically carry `read:org`, `gist`, and `workflow` scopes across your whole account.
2546
+ 3. **Handle idempotency**: setup only creates — it never updates. A role, user, or policy that already exists but differs stops setup and names what differs. To replace a policy:
2409
2547
 
2410
- 7. **Deploy from a private repository**: Pass `credential=true` after provisioning the deploy credential.
2548
+ ```bash
2549
+ harper drop_oidc_trust id=github-actions-<project>
2550
+ harper deploy setup=true provider=github-actions project=my-app
2551
+ ```
2552
+
2553
+ ##### Manual Setup (Tag-Based or Custom Deploys)
2554
+
2555
+ Use this path when deploying on version tags, because a branch-pinned policy cannot match a tag ref.
2556
+
2557
+ 1. **Create a deploy-only role and user**:
2411
2558
 
2412
2559
  ```bash
2413
- harper deploy by_ref=true credential=true restart=true replicated=true
2560
+ harper add_role role=ci_deploy permission='{"operations":["deploy_component","get_job","get_deployment","registration_info"]}'
2561
+ harper add_user username=ci-deploy role=ci_deploy active=true password="$(openssl rand -base64 32)"
2414
2562
  ```
2415
2563
 
2416
- 8. **Use one-off authentication parameters** (not recommended for production): Pass `auth_username` and `auth_password` directly. These take precedence over environment variables and saved login tokens.
2564
+ - `deploy_component` — the deploy itself.
2565
+ - `get_job` — required for the wait step; an `operations` allowlist refuses anything not listed, so without it the wait step fails with `403`.
2566
+ - `get_deployment` — reads the deployment record.
2567
+ - `registration_info` — version check `harper deploy` makes before uploading.
2568
+ - `restart_service` is not needed: Harper starts the rolling restart's job itself. The password is required to create the user but a GitHub Actions pipeline never uses it, so this command discards it.
2569
+
2570
+ 2. **Add a trust policy** pinned to `repository_id`, `workflow_path`, and `environment` (use this shape when the tag is unknown at policy-write time):
2417
2571
 
2418
2572
  ```bash
2419
- harper deploy \
2420
- project=<name> \
2421
- package=<package> \
2422
- auth_username=<username> \
2423
- auth_password=<password> \
2424
- target=<remote> \
2425
- restart=true \
2426
- replicated=true
2573
+ harper add_oidc_trust \
2574
+ id=my-app-release \
2575
+ issuer=https://token.actions.githubusercontent.com \
2576
+ audience=https://my-cluster.example.com:9925/ \
2577
+ user=ci-deploy \
2578
+ claims='{"repository_id":"67890","workflow_path":"my-org/my-app/.github/workflows/deploy.yml","environment":"production"}'
2427
2579
  ```
2428
2580
 
2429
- 9. **Use CI/CD pipelines**: For GitHub Actions, use workload identity (OIDC) so no Harper credential is stored in the pipeline. Alternatively, supply a refresh token from `harper login --for-ci`. On a `pull_request` run, pass the head SHA explicitly:
2581
+ Key claim rules:
2582
+ - `repository_id` is immutable — survives renames. Look it up with `gh api repos/my-org/my-app --jq .id`.
2583
+ - `workflow_path` pins the workflow file. Harper derives it from the token's `workflow_ref` by removing the `@<ref>` suffix, so it is `<owner>/<repo>/<path>`.
2584
+ - `environment` is the ref gate. Without it, anyone who can push a branch can mint a token.
2585
+ - `audience` must be the exact target URL with its port and a **trailing slash**.
2586
+ - A workflow that deploys on a push to `main` can pin `workflow_ref` (`my-org/my-app/.github/workflows/deploy.yml@refs/heads/main`) instead, which gates the ref by itself.
2587
+
2588
+ 3. **Configure the GitHub environment**: in **Settings → Environments → production**, set **Deployment branches and tags** to the `v*` tag pattern. Add required reviewers if a release needs approval.
2589
+
2590
+ 4. **Store the target as a repository variable** (not a secret — it is not sensitive):
2430
2591
 
2431
2592
  ```bash
2432
- harper deploy ref=${{ github.event.pull_request.head.sha }} restart=true replicated=true
2593
+ gh variable set HARPER_CLI_TARGET --repo my-org/my-app --body https://my-cluster.example.com:9925/
2433
2594
  ```
2434
2595
 
2596
+ ##### How OIDC Authentication Works in the Workflow
2597
+
2598
+ Once a trust policy exists, every `harper` command in the deploy job authenticates through it automatically when all four conditions hold:
2599
+
2600
+ 1. **The job may request an identity token.** On GitHub Actions that is `permissions: id-token: write` on the job.
2601
+ 2. **`HARPER_CLI_TARGET` is the policy's `audience`.** The CLI requests its token for the target it connects to, so the target must be the URL the policy names, with its port and trailing slash. Use `vars.HARPER_CLI_TARGET` — it is not sensitive, so a repository variable rather than a secret.
2602
+ 3. **The run matches the policy's claims**: its repository, its workflow file and branch, and its environment.
2603
+ 4. **Nothing else is configured to authenticate.** `auth_username=`/`auth_password=`, credentials in the target URL, `HARPER_CLI_USERNAME`/`HARPER_CLI_PASSWORD`, `HARPER_CLI_REFRESH_TOKEN` or `HARPER_CLI_OPERATION_TOKEN`, and a saved `harper login` all outrank the exchange.
2604
+
2605
+ What each command may then do is up to the role of the user the policy names: a role that lists only `deploy_component`, `get_job` and `registration_info`, like the one setup creates, refuses anything else with a `403`.
2606
+
2607
+ The exchange runs only when GitHub sets `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which it does for a job that declares `permissions: id-token: write`. Without them, the CLI falls through to its other credential sources rather than reporting a failure.
2608
+
2609
+ Nothing in the workflow names the policy. The CLI prints to stderr which policy authenticated it and as whom:
2610
+
2611
+ ```
2612
+ Requesting a GitHub Actions identity token for https://my-instance.harperdb.io:9925/...
2613
+ Authenticated as 'ci-deploy' via OIDC trust policy 'my-app-prod'.
2614
+ ```
2615
+
2616
+ Diagnose a rejected exchange with `list_oidc_trust` (check for `invalid_reason` on each entry) and the instance's `oidc-trust` log. The server deliberately does not report which check failed.
2617
+
2618
+ ##### Policy Specificity for GitHub Actions
2619
+
2620
+ For `https://token.actions.githubusercontent.com`, a policy must satisfy all three of these:
2621
+
2622
+ | Requirement | Satisfied by one of | Left open otherwise |
2623
+ | ---------------------- | ------------------------------------------------------------------------ | ----------------------------------------------- |
2624
+ | **Pin the repository** | `repository_id`, `repository` | Any repository |
2625
+ | **Pin the workflow** | `workflow_ref`, `workflow_path`, `job_workflow_ref`, `job_workflow_path` | Any workflow in that repository |
2626
+ | **Gate the ref** | `workflow_ref`, `ref`, `environment` | Any branch that can be pushed to the repository |
2627
+
2628
+ Additional constraints:
2629
+
2630
+ - `repository_id` is preferred over `repository` — it is immutable and survives renames.
2631
+ - A tag-triggered release cannot pin `workflow_ref` since the tag is unknown when the policy is written. Pin `workflow_path` and gate on `environment`.
2632
+ - `ref_type: tag` is not accepted as a ref gate, `sub` is not accepted as a pin, and `job_workflow_ref` pins the workflow without gating the ref. `pull_request_target` runs are denied unless the policy constrains `event_name`.
2633
+
2634
+ ##### Revoking Access
2635
+
2636
+ - **Stop future runs**: `harper drop_oidc_trust id=github-actions-<project>` (does not revoke already-issued tokens, which are valid up to one hour).
2637
+ - **Stop an already-issued token**: `harper alter_user username=<project>-ci-deploy active=false`.
2638
+ - **No CI secret to rotate** — there is none.
2639
+
2435
2640
  #### Examples
2436
2641
 
2437
- **Login and deploy the current directory to a remote cluster:**
2642
+ ##### Path A: Deploy a Tagged Release (`v1.2.3`) from the Repository
2438
2643
 
2439
- ```bash
2440
- harper login https://my-cluster.harperdbcloud.com
2441
- harper deploy \
2442
- project=my-app \
2443
- target=https://my-cluster.harperdbcloud.com \
2444
- restart=true \
2445
- replicated=true
2644
+ Push a semver tag (`v1.2.3`), and every node clones and installs that commit. `by_ref=true` deploys `git+https://github.com/<owner>/<repo>.git#<sha>`, where the SHA is the commit the tag points to (`GITHUB_SHA`), and every node clones that exact commit even if the tag moves later. For a private repository, add `credential=true` to attach the stored credential (`deploy.my-app.git.github.com`). If the repository needs a build step to be runnable, don't reach for `install_allow_scripts`: use Path B.
2645
+
2646
+ ```yaml
2647
+ # .github/workflows/deploy.yml
2648
+ name: Deploy to Harper
2649
+ on:
2650
+ push:
2651
+ tags: ['v*']
2652
+
2653
+ jobs:
2654
+ deploy:
2655
+ runs-on: ubuntu-latest
2656
+ environment: production
2657
+ permissions:
2658
+ contents: read
2659
+ id-token: write
2660
+ env:
2661
+ HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }}
2662
+ steps:
2663
+ - uses: actions/checkout@v4
2664
+ with:
2665
+ persist-credentials: false
2666
+ - uses: actions/setup-node@v4
2667
+ with:
2668
+ node-version: 22
2669
+ - run: npm install --global harper@^5.3
2670
+ - name: Deploy the tagged commit
2671
+ run: harper deploy project=my-app by_ref=true restart=rolling json=true > deploy.json
2672
+ - name: Wait for every node to take the release
2673
+ run: |
2674
+ JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
2675
+ [ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
2676
+ LAST="could not be read"
2677
+ for attempt in $(seq 60); do
2678
+ sleep 10
2679
+ harper get_job id="$JOB_ID" json=true > job.json || continue
2680
+ case $(jq -r '.[0].status // "MISSING"' job.json) in
2681
+ COMPLETE) exit 0 ;;
2682
+ ERROR) jq -r '.[0].message' job.json; exit 1 ;;
2683
+ MISSING) LAST="was not on the node that answered; its outcome is unknown" ;;
2684
+ *) LAST="was still running" ;;
2685
+ esac
2686
+ done
2687
+ echo "After 60 polls, job $JOB_ID $LAST"
2688
+ exit 1
2689
+ ```
2690
+
2691
+ Key details:
2692
+
2693
+ - **No secrets.** `HARPER_CLI_TARGET` is the only thing the workflow is given. `id-token: write` lets the CLI request an identity token, and `environment: production` puts the `environment` claim the trust policy requires into it.
2694
+ - **`persist-credentials: false`** keeps the job's `GITHUB_TOKEN` out of `.git/config`.
2695
+ - **`harper@^5.3`** pins the CLI's major version. 5.3 is the first CLI that authenticates with the identity token.
2696
+ - **`replicated` isn't needed.** On Harper Fabric and Harper Pro, a deploy goes to every node unless you pass `replicated=false`. Each node fetches and installs the package itself.
2697
+ - **`restart=rolling`** restarts nodes one at a time. Use `restart=true` for a single-node or dev instance.
2698
+ - **`json=true`** prints the deploy's result as JSON on stdout; progress and authentication messages go to stderr.
2699
+ - **The wait step** polls `restartJobId` via `get_job` until `COMPLETE` or `ERROR`, checking `.[0].status`. It retries a poll that fails (such as one that reaches a node while it restarts) and gives up after 60 polls (~10 minutes).
2700
+ - **`certification`**: on v5.4.0 and later, `restart=rolling` loads the release in a canary worker on each node before that node serves it, so a release that fails to load is rejected. The `certification` field in the deploy result reflects this. With `restart=rolling` on v5.4.0 and later, the deploy can report `success` before the rolling job has visited all nodes — wait on the job, not the record.
2701
+ - **The job lives on one node.** Through a **load-balanced** cluster URL, polls must eventually reach that node. Routing that always sends polls to a different node than the deploy causes the wait step to fail saying the job was not on the node that answered.
2702
+
2703
+ ##### Path B: Build in CI, Publish to GitHub Packages, Deploy the Artifact
2704
+
2705
+ Give `package.json` a scoped name and `"publishConfig": { "registry": "https://npm.pkg.github.com" }`:
2706
+
2707
+ ```json
2708
+ {
2709
+ "name": "@my-org/my-app",
2710
+ "version": "1.2.3",
2711
+ "publishConfig": { "registry": "https://npm.pkg.github.com" },
2712
+ "files": ["config.yaml", "schema.graphql", "dist/"]
2713
+ }
2446
2714
  ```
2447
2715
 
2448
- **Deploy a specific GitHub repository by tag:**
2716
+ The deploy installs the version the tag names, and `npm publish` publishes the version in `package.json`, so they must agree: `npm version 1.2.3` sets one and creates the other as `v1.2.3`.
2717
+
2718
+ ```yaml
2719
+ # .github/workflows/deploy.yml
2720
+ name: Build, publish, and deploy to Harper
2721
+ on:
2722
+ push:
2723
+ tags: ['v*']
2724
+
2725
+ jobs:
2726
+ publish:
2727
+ runs-on: ubuntu-latest
2728
+ permissions:
2729
+ contents: read
2730
+ packages: write
2731
+ steps:
2732
+ - uses: actions/checkout@v4
2733
+ with:
2734
+ persist-credentials: false
2735
+ - uses: actions/setup-node@v4
2736
+ with:
2737
+ node-version: 22
2738
+ registry-url: https://npm.pkg.github.com
2739
+ scope: '@my-org'
2740
+ - run: npm ci
2741
+ env:
2742
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2743
+ - run: npm run build
2744
+ - run: npm test
2745
+ - name: Publish to GitHub Packages
2746
+ run: npm publish
2747
+ env:
2748
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2749
+
2750
+ deploy:
2751
+ needs: publish
2752
+ runs-on: ubuntu-latest
2753
+ environment: production
2754
+ permissions:
2755
+ id-token: write
2756
+ env:
2757
+ HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }}
2758
+ steps:
2759
+ - uses: actions/setup-node@v4
2760
+ with:
2761
+ node-version: 22
2762
+ - run: npm install --global harper@^5.3
2763
+ - name: Deploy the published version
2764
+ run: |
2765
+ harper deploy project=my-app package="@my-org/my-app@${GITHUB_REF_NAME#v}" \
2766
+ credentials='[{"registry":"https://npm.pkg.github.com","scope":"@my-org","secret":"deploy.my-app.npm.pkg.github.com"}]' \
2767
+ restart=rolling json=true > deploy.json
2768
+ - name: Wait for every node to take the release
2769
+ run: |
2770
+ JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
2771
+ [ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
2772
+ LAST="could not be read"
2773
+ for attempt in $(seq 60); do
2774
+ sleep 10
2775
+ harper get_job id="$JOB_ID" json=true > job.json || continue
2776
+ case $(jq -r '.[0].status // "MISSING"' job.json) in
2777
+ COMPLETE) exit 0 ;;
2778
+ ERROR) jq -r '.[0].message' job.json; exit 1 ;;
2779
+ MISSING) LAST="was not on the node that answered; its outcome is unknown" ;;
2780
+ *) LAST="was still running" ;;
2781
+ esac
2782
+ done
2783
+ echo "After 60 polls, job $JOB_ID $LAST"
2784
+ exit 1
2785
+ ```
2786
+
2787
+ Two jobs keep the identity token isolated: `publish` installs, builds, tests and publishes with `contents: read` and `packages: write` only — it cannot request an identity token. `deploy` runs once `publish` finishes (`needs: publish`), holds `environment: production` and `id-token: write`, and installs only the Harper CLI before deploying.
2788
+
2789
+ Credential roles:
2790
+
2791
+ - **Publishing** and installing private `@my-org` dependencies use the workflow's ephemeral `GITHUB_TOKEN` (`packages: write` includes read). Both happen entirely inside `publish`.
2792
+ - **Installing on Harper nodes** uses the durable `read:packages` token stored as `deploy.my-app.npm.pkg.github.com` — because installs can happen long after this workflow ends.
2793
+
2794
+ For `my-app`, the credential names are `deploy.my-app.git.github.com` for the repository and `deploy.my-app.npm.pkg.github.com` for GitHub Packages. Store a durable credential once from your machine:
2449
2795
 
2450
2796
  ```bash
2451
- harper deploy \
2452
- project=my-app \
2453
- package="HarperFast/application-template#semver:v1.0.0" \
2454
- target=https://my-cluster.harperdbcloud.com \
2455
- restart=true \
2456
- replicated=true
2797
+ harper deploy setup=true provider=github project=my-app
2798
+ # or for GitHub Packages:
2799
+ harper deploy setup=true provider=npm project=my-app registry=https://npm.pkg.github.com scope=@my-org
2457
2800
  ```
2458
2801
 
2459
- **Deploy by reference from a private repository after provisioning credentials:**
2802
+ For a private repository, use a GitHub fine-grained personal access token with **Contents: read-only** scoped to the application repository. For GitHub Packages, use a token with the `read:packages` scope. Do not use the workflow's ephemeral `GITHUB_TOKEN` for the durable credential — it expires when the job ends.
2803
+
2804
+ ##### Check the Deployment Record
2460
2805
 
2461
2806
  ```bash
2462
- # Provision once (requires super_user)
2463
- harper deploy setup=true
2807
+ harper get_deployment deployment_id="$(jq -r .deployment_id deploy.json)" json=true
2808
+ ```
2464
2809
 
2465
- # Deploy by reference with credential
2466
- harper deploy by_ref=true credential=true restart=true replicated=true
2810
+ Only the node you called writes that record, so with `restart=rolling` on v5.4.0 and later it can report `success` before the job has visited the other nodes, and keeps reporting it if one of them rejects the release. Wait on the job, not the record, to confirm all nodes took the release. `list_deployments` gives the history of what was deployed and when.
2811
+
2812
+ ##### List and Inspect Trust Policies
2813
+
2814
+ ```bash
2815
+ harper list_oidc_trust
2467
2816
  ```
2468
2817
 
2469
- **Roll back to a previous deployment:**
2818
+ Returns `{ "policies": [ ... ] }`. Each entry includes `id`, `issuer`, `audience`, `claims`, `user`, `enabled`, and optionally `invalid_reason`. Check `invalid_reason` first when diagnosing a rejected exchange — it is set when the policy is malformed or its user was deleted or deactivated.
2819
+
2820
+ ##### Drop a Trust Policy
2470
2821
 
2471
2822
  ```bash
2472
- harper deploy project=my-app deployment_id=<id> restart=true
2823
+ harper drop_oidc_trust id=github-actions-<project>
2473
2824
  ```
2474
2825
 
2475
2826
  #### Notes
2476
2827
 
2477
- - The cluster's Application URL is found on the **Config → Overview** page.
2478
- - `harper deploy setup=true` requires **super_user** — run it with an administrative credential, not the CI identity it provisions for.
2479
- - Secrets are stored scoped to the component, never in the global `processEnv` tier. The `enc:v1:` envelope is the only form of the token that ever leaves your machine or travels over the operations API.
2480
- - For SSH-based private repos, use the `add_ssh_key` operation to register keys before deploying.
2481
- - A `ref` pointing to `refs/tags` is resolved to a full commit SHA before the deploy is sent. Tags that move mid-deploy could otherwise leave cluster nodes running different code.
2482
- - The unpushed-commit check is skipped under GitHub Actions; the dirty-tree warning still applies.
2483
- - Deploying by reference means the cluster installs and builds from source. If your application requires a build step that cannot run on the node, deploy a built payload instead.
2828
+ - `HARPER_CLI_REFRESH_TOKEN` and any other stored credential outrank the OIDC exchange. Remove them from the workflow if switching to OIDC.
2829
+ - **Go back to a previous release** by activating its `deployment_id`, with no rebuild or reinstall: `harper deploy project=my-app deployment_id=<id> restart=rolling`. To deploy a release that is no longer kept, re-run the deploy with its tag or version.
2830
+ - **Rotating the deploy credential**: run `harper deploy setup=true` again with the same name and a new value. The pipeline doesn't change.
2831
+ - **Revoking the pipeline's access**: `harper drop_oidc_trust` stops new runs; `harper alter_user` also stops a token already issued (valid up to one hour).
2832
+ - The `<project>-ci-deploy` user created by setup can deploy any component on the cluster, not only the named project. Access control comes down to who can merge to the branch.
2833
+ - The `audience` in `add_oidc_trust` must be the exact target URL with its port and a trailing slash.
2834
+ - For tag-triggered workflows, `provider=github-actions` setup is not sufficient — it pins a branch. Write the policy manually using `add_oidc_trust` with `workflow_path` and `environment` claims.
2835
+ - `token.actions.githubusercontent.com` is the required `issuer` value for GitHub Actions OIDC policies.
2836
+ - Git-host credentials require git ≥ 2.31 on the Harper nodes and are not supported on Windows nodes. Registry credentials have no such constraints.
2837
+ - See [deploying-to-harper-fabric.md](deploying-to-harper-fabric.md) for cluster-level deployment context.
2484
2838
 
2485
- ### 4.2 Creating a Harper Fabric Account and Cluster
2839
+ ### 4.3 Creating a Harper Fabric Account and Cluster
2486
2840
 
2487
2841
  Follow these steps to set up your Harper Fabric environment for deployment.
2488
2842
 
@@ -2493,7 +2847,7 @@ Follow these steps to set up your Harper Fabric environment for deployment.
2493
2847
  3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
2494
2848
  4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
2495
2849
  5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
2496
- 6. **Configure Environment**: Update your `.env` file or GitHub Actions secrets with cluster-specific credentials.
2850
+ 6. **Connect the CLI**: Run `harper login` with the Application URL to store a token for the cluster, and set the URL as `HARPER_CLI_TARGET` in your `.env` file. Prefer this to putting the cluster username and password in `.env`, and don't store them in GitHub Actions secrets: to deploy from GitHub Actions, set up OIDC trusted publishing instead (see [deploying-from-ci](deploying-from-ci.md)).
2497
2851
  7. **Next Steps**: See the [deploying-to-harper-fabric](deploying-to-harper-fabric.md) rule for detailed instructions on deploying your application successfully.
2498
2852
 
2499
2853
  #### Examples
@@ -2501,12 +2855,15 @@ Follow these steps to set up your Harper Fabric environment for deployment.
2501
2855
  ##### Environment Configuration
2502
2856
 
2503
2857
  ```bash
2504
- CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
2505
- CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
2506
- CLI_TARGET='YOUR_CLUSTER_URL'
2858
+ harper login YOUR_CLUSTER_URL
2859
+ ```
2860
+
2861
+ ```bash
2862
+ # .env
2863
+ HARPER_CLI_TARGET='YOUR_CLUSTER_URL'
2507
2864
  ```
2508
2865
 
2509
- ### 4.3 Creating Harper Applications
2866
+ ### 4.4 Creating Harper Applications
2510
2867
 
2511
2868
  The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command
2512
2869
  initializes a project with a standard folder structure, essential configuration files, and basic
@@ -2549,12 +2906,79 @@ npm create harper@latest my-app --template default
2549
2906
 
2550
2907
  #### Next Steps
2551
2908
 
2552
- 1. **Configure Environment**: Set up your `.env` file with local or cloud credentials.
2553
- 2. **Define Schema**: Modify `schema.graphql` to fit your application's data model.
2554
- 3. **Start Development**: Run `npm run dev` to start the local Harper instance.
2555
- 4. **Deploy**: Use `npm run deploy` to push your application to Harper Fabric.
2909
+ 1. **Define Schema**: Modify `schema.graphql` to fit your application's data model.
2910
+ 2. **Start Development**: Run `npm run dev` to start the local Harper instance.
2911
+ 3. **Connect the CLI to your cluster**: Create a cluster (see
2912
+ [creating-a-fabric-account-and-cluster](creating-a-fabric-account-and-cluster.md)), run `harper login` with its
2913
+ Application URL as a super user, and set that URL as `HARPER_CLI_TARGET` in the project's `.env` file. Don't put a
2914
+ cluster username or password in `.env` or in GitHub secrets.
2915
+ 4. **Deploy**: See [Deploying](#deploying).
2916
+
2917
+ #### Deploying
2918
+
2919
+ Every template except the legacy `early-hints` example ships a GitHub Actions workflow,
2920
+ `.github/workflows/deploy.yaml`. It tests every pull request and deploys to the cluster when a change merges to `main`.
2921
+ The deploy job holds no Harper credential: its `harper` commands authenticate with GitHub's OIDC identity token under a
2922
+ trust policy on the cluster. The Next.js templates' workflow also runs `next build` before it uploads.
2923
+
2924
+ 1. **Set up CI deploys once**: Push the project to a GitHub repository, then run `npm run deploy:setup-ci` from the
2925
+ project, logged in to the cluster as a super user. It runs `harper deploy setup=true provider=github-actions` for the
2926
+ project, which creates a deploy-only role and user and a trust policy matched to the workflow, and sets the
2927
+ repository's `HARPER_CLI_TARGET` variable when the `gh` CLI is signed in (otherwise it prints the command to run).
2928
+ Running it again changes nothing. See [deploying-from-ci](deploying-from-ci.md) for what it checks and how to
2929
+ customize it.
2930
+
2931
+ The command needs the Harper 5.4.0 CLI or later (`harper version`); an earlier CLI can't run it. Without 5.4.0, set
2932
+ up the same role, user, policy and variable by hand, as a super user. The policy must match the scaffolded workflow:
2933
+ its `deploy.yaml` file on `main`, in the `production` environment. The `audience` is the cluster URL with its port
2934
+ and a trailing slash, and `HARPER_CLI_TARGET` must be that same string:
2935
+
2936
+ ```bash
2937
+ gh api repos/<owner>/<repo> --jq .id # the repository_id
2938
+ harper add_role role=<name>-ci-deploy permission='{"operations":["deploy_component","get_job","registration_info"]}'
2939
+ harper add_user username=<name>-ci-deploy role=<name>-ci-deploy active=true password="$(openssl rand -base64 32)"
2940
+ harper add_oidc_trust \
2941
+ id=github-actions-<name> \
2942
+ issuer=https://token.actions.githubusercontent.com \
2943
+ audience=https://my-cluster.example.com:9925/ \
2944
+ user=<name>-ci-deploy \
2945
+ claims='{"repository_id":"<id>","workflow_ref":"<owner>/<repo>/.github/workflows/deploy.yaml@refs/heads/main","environment":"production"}'
2946
+ gh variable set HARPER_CLI_TARGET --repo <owner>/<repo> --body https://my-cluster.example.com:9925/
2947
+ ```
2948
+
2949
+ If you restrict the `production` environment's deployment branches, allow `main`.
2950
+
2951
+ 2. **Deploy by merging to `main`**, or by running the workflow from the **Actions** tab. The workflow deploys with
2952
+ `restart=rolling` and waits for every node to take the release. The run's summary names the `deployment_id` and its
2953
+ `certification`.
2954
+ 3. **Deploy by hand with `npm run deploy`**: It runs `harper deploy project=<name> restart=rolling` from your machine, as
2955
+ the user you logged in with.
2956
+ 4. **Go back to an earlier release** by activating its `deployment_id`, with nothing reinstalled:
2957
+
2958
+ ```bash
2959
+ harper list_deployments project=<name>
2960
+ harper deploy project=<name> deployment_id=<id> restart=rolling
2961
+ ```
2962
+
2963
+ See [deploying-to-harper-fabric](deploying-to-harper-fabric.md).
2964
+
2965
+ Use your package manager's equivalent of `npm run` (`pnpm run`, `bun run`, `yarn`) if the project uses another one.
2966
+
2967
+ Projects created before create-harper 1.13.0 deploy on version tags with a `HARPER_CLI_REFRESH_TOKEN` secret, and
2968
+ don't have `deploy:setup-ci`. To move one over:
2969
+
2970
+ 1. Scaffold a new project with the same name and template, and copy its `.github/workflows/deploy.yaml` over the old
2971
+ workflow.
2972
+ 2. Set up the cluster for it, with the `project=` value that workflow passes to `harper deploy` as `<name>`: with the
2973
+ 5.4.0 CLI, `harper deploy setup=true provider=github-actions project=<name>`; with an earlier one, by hand as in
2974
+ step 1.
2975
+ 3. Push the new workflow to `main`.
2976
+ 4. Once the workflow's run on `main` has succeeded, wait step included, delete the `HARPER_CLI_REFRESH_TOKEN` secret.
2977
+ Deleting it doesn't revoke the token, which stays valid until it expires. To cut it off now, mint a new one for that
2978
+ user with `harper login --for-ci`, which invalidates the old one, or deactivate the user with `alter_user` if
2979
+ nothing else uses it.
2556
2980
 
2557
- ### 4.4 Serving Web Content
2981
+ ### 4.5 Serving Web Content
2558
2982
 
2559
2983
  Instructions for the agent to follow when serving web content from Harper.
2560
2984
 
@@ -2661,7 +3085,7 @@ Because `@harperfast/vite` builds on the node and `static` serves the output, de
2661
3085
 
2662
3086
  On deploy the plugin runs `vite build` at startup (and rebuilds when `files` change) while `static` serves the result. If you prefer to build in CI, commit the build output, point `static` at it, and omit `files` so the plugin stays idle while `static` serves the prebuilt assets. Either way, `npm create harper@latest` scaffolds a working setup for you.
2663
3087
 
2664
- ### 4.5 Harper Logging
3088
+ ### 4.6 Harper Logging
2665
3089
 
2666
3090
  Instructions for the agent to follow when implementing logging in Harper applications, including direct logger usage, tagged loggers, and console capture behavior.
2667
3091
 
@@ -2817,61 +3241,31 @@ Tagged entries appear in `hdb.log` with the tag in the header:
2817
3241
  - When logging to standard streams, run Harper in the foreground (`harper`, not `harper start`).
2818
3242
  - `TaggedLogger` is bound to the configured log level at creation time — always use `?.` on its methods.
2819
3243
 
2820
- ### 4.6 Load Environment Variables with loadEnv
3244
+ ### 4.7 Load Environment Variables with loadEnv
2821
3245
 
2822
3246
  Instructions for the agent to follow when loading environment variables from `.env` files into a Harper application using the `loadEnv` plugin.
2823
3247
 
2824
3248
  #### When to Use
2825
3249
 
2826
- Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use `loadEnv` any time you need to load one or more `.env` files at application startup.
3250
+ Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use this whenever a `.env` file must be loaded at application startup.
2827
3251
 
2828
3252
  #### How It Works
2829
3253
 
2830
- 1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` as the first entry in `config.yaml`. It is built into Harper and requires no installation.
2831
-
2832
- ```yaml
2833
- loadEnv:
2834
- files: '.env'
2835
- ```
2836
-
2837
- 2. **Place `loadEnv` first**: Harper is a single-process application. List `loadEnv` before all other components so that environment variables are available on `process.env` before dependent components start.
3254
+ 1. **Declare `loadEnv` first in `config.yaml`**: Place `loadEnv` before all other components so environment variables are available when dependent components start.
2838
3255
 
2839
3256
  ```yaml
2840
3257
  # config.yaml — loadEnv must come first
2841
3258
  loadEnv:
2842
3259
  files: '.env'
2843
-
2844
- rest: true
2845
-
2846
- myApp:
2847
- files: './src/*.js'
2848
- ```
2849
-
2850
- 3. **Access loaded values in component code**: After `loadEnv` runs, all loaded values are available on `process.env` and shared across all components.
2851
-
2852
- 4. **Control override behavior**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to make loaded values win instead.
2853
-
2854
- ```yaml
2855
- loadEnv:
2856
- files: '.env'
2857
- override: true
2858
3260
  ```
2859
3261
 
2860
- 5. **Load multiple files**: Pass an array of paths or a glob pattern to `files`. Files are loaded in the order specified.
3262
+ 2. **Set the `files` option**: Provide one or more paths or glob patterns pointing to the env file(s) to load. This option is required.
2861
3263
 
2862
- ```yaml
2863
- loadEnv:
2864
- files:
2865
- - '.env'
2866
- - '.env.local'
2867
- ```
3264
+ 3. **Access variables in component code**: After loading, all values from the specified files are available on `process.env` and shared across all components in the single Harper process.
2868
3265
 
2869
- or with a glob:
3266
+ 4. **Control override behavior with `override`**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to reverse this — loaded values will overwrite variables already present in the shell or container environment.
2870
3267
 
2871
- ```yaml
2872
- loadEnv:
2873
- files: 'env-vars/*'
2874
- ```
3268
+ 5. **Load multiple files when needed**: Specify a list or a glob pattern under `files`. Files are loaded in the order specified.
2875
3269
 
2876
3270
  ##### Configuration Options
2877
3271
 
@@ -2882,10 +3276,17 @@ Apply this rule when a Harper application needs to supply secrets, API endpoints
2882
3276
 
2883
3277
  #### Examples
2884
3278
 
2885
- **Single file, default behavior:**
3279
+ **Basic usage — load a single `.env` file:**
2886
3280
 
2887
3281
  ```yaml
2888
- # config.yaml
3282
+ loadEnv:
3283
+ files: '.env'
3284
+ ```
3285
+
3286
+ **Full `config.yaml` with `loadEnv` declared first:**
3287
+
3288
+ ```yaml
3289
+ # config.yaml — loadEnv must come first
2889
3290
  loadEnv:
2890
3291
  files: '.env'
2891
3292
 
@@ -2895,29 +3296,40 @@ myApp:
2895
3296
  files: './src/*.js'
2896
3297
  ```
2897
3298
 
2898
- **Multiple files with override:**
3299
+ **Override existing environment variables:**
3300
+
3301
+ ```yaml
3302
+ loadEnv:
3303
+ files: '.env'
3304
+ override: true
3305
+ ```
3306
+
3307
+ **Load multiple files as a list:**
2899
3308
 
2900
3309
  ```yaml
2901
- # config.yaml
2902
3310
  loadEnv:
2903
3311
  files:
2904
3312
  - '.env'
2905
3313
  - '.env.local'
2906
- override: true
3314
+ ```
2907
3315
 
2908
- rest: true
3316
+ **Load multiple files using a glob pattern:**
2909
3317
 
2910
- myApp:
2911
- files: './src/*.js'
3318
+ ```yaml
3319
+ loadEnv:
3320
+ files: 'env-vars/*'
2912
3321
  ```
2913
3322
 
2914
3323
  #### Notes
2915
3324
 
2916
- - `loadEnv` loads values into `process.env` for **application** code only — it does not configure Harper itself.
2917
- - Harper's own instance-wide configuration is composed at startup **before** any component's `loadEnv` runs. Variables such as `HARPER_CONFIG`, `HARPER_SET_CONFIG`, and `HARPER_DEFAULT_CONFIG` delivered through a `.env` file are read too late and are ignored. Set Harper configuration directly in the configuration file or export variables in the real process/container environment before Harper starts.
2918
- - For production credentials, prefer the encrypted secrets store over a committed `.env` file. Secrets are also delivered to components via `process.env`.
3325
+ - `loadEnv` is built into Harper — it does not need to be installed, only declared in `config.yaml`.
3326
+ - Variables are loaded onto `process.env` and shared across all components. Declare `loadEnv` before any component that depends on those variables.
3327
+ - `loadEnv` supplies **application** environment variables only. It does **not** configure Harper itself. Harper's own configuration is composed at startup before any `loadEnv` runs, so variables such as `HARPER_CONFIG`, `HARPER_SET_CONFIG`, and `HARPER_DEFAULT_CONFIG` delivered through a `.env` file are read too late and are ignored.
3328
+ - To change Harper's own configuration, use the configuration file directly or export variables in the real process/container environment before Harper starts.
3329
+ - For production credentials, prefer the encrypted secrets store over a committed `.env` file. Secrets are delivered to components via `process.env` and never appear in operation logs or replication payloads.
3330
+ - Individual `.env` values can also be stored encrypted so plaintext never reaches the API, logs, or replication payload.
2919
3331
 
2920
- ### 4.7 v5 Upgrade: Breaking Changes and Migration Guide
3332
+ ### 4.8 v5 Upgrade: Breaking Changes and Migration Guide
2921
3333
 
2922
3334
  Instructions for the agent to follow when migrating a Harper application to v5, covering all breaking changes and required code updates.
2923
3335
 
@@ -3089,7 +3501,7 @@ record = { ...record, property: 'changed' };
3089
3501
  - Automatic context tracking simplifies code but requires explicit `commit()` or new `transaction()` calls when you need to observe data written within the same transaction.
3090
3502
  - Use `getContext` from `'harper'` rather than passing context manually through every call — this is the recommended pattern in v5.
3091
3503
 
3092
- ### 4.8 Delegating to the Built-in Agent
3504
+ ### 4.9 Delegating to the Built-in Agent
3093
3505
 
3094
3506
  Harper 5.2+ ships with a **built-in agent** that runs _inside_ the server, on the main thread
3095
3507
  adjacent to the operations API. Because it runs in-process, it can do things a remote client