@harperfast/template-react-ts-ssr-studio 1.13.4 → 1.13.5

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.
Files changed (48) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +687 -453
  2. package/.agents/skills/harper-best-practices/SKILL.md +24 -21
  3. package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  4. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  5. package/.agents/skills/harper-best-practices/rules/caching.md +2 -0
  6. package/.agents/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  7. package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
  8. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  9. package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  10. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  11. package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
  12. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  13. package/.agents/skills/harper-best-practices/rules/load-env.md +2 -0
  14. package/.agents/skills/harper-best-practices/rules/logging.md +2 -0
  15. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  16. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  17. package/.agents/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  18. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  19. package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  20. package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  21. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  22. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  23. package/.agents/skills/harper-best-practices/rules.manifest.yaml +44 -0
  24. package/agent/skills/harper-best-practices/AGENTS.md +687 -453
  25. package/agent/skills/harper-best-practices/SKILL.md +26 -21
  26. package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  27. package/agent/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  28. package/agent/skills/harper-best-practices/rules/caching.md +2 -0
  29. package/agent/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  30. package/agent/skills/harper-best-practices/rules/custom-resources.md +2 -0
  31. package/agent/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  32. package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  33. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  34. package/agent/skills/harper-best-practices/rules/extending-tables.md +2 -0
  35. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  36. package/agent/skills/harper-best-practices/rules/load-env.md +2 -0
  37. package/agent/skills/harper-best-practices/rules/logging.md +2 -0
  38. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  39. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  40. package/agent/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  41. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  42. package/agent/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  43. package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  44. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  45. package/agent/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  46. package/agent/skills/harper-best-practices/rules.manifest.yaml +44 -0
  47. package/package.json +1 -1
  48. package/skills-lock.json +1 -1
@@ -28,54 +28,62 @@ metadata:
28
28
  Actions
29
29
  - reference/v5/operations-api/operations.md#`list_oidc_trust`
30
30
  - reference/v5/operations-api/operations.md#`drop_oidc_trust`
31
- sourceCommit: 2dde0597443a13af0c753f2adbe528e8442d8ee4
32
- inputHash: b235eb6a7fb4d9a1
31
+ sourceCommit: e69702c99845f2dd810cb32652d1a9e6b58c538b
32
+ inputHash: 91e58cd0833af162
33
33
  ---
34
34
 
35
35
  # Deploying a Harper Application from GitHub Actions with OIDC
36
36
 
37
- 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.
37
+ Instructions for the agent to configure and use OIDC trusted publishing so a GitHub Actions workflow can deploy to Harper with no stored credential.
38
38
 
39
39
  ## When to Use
40
40
 
41
- 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.
41
+ Apply this rule when wiring up a GitHub Actions workflow to deploy a Harper application without storing a `HARPER_CLI_REFRESH_TOKEN` or any other durable credential in CI. Use it both for the one-command setup path (branch-triggered deploys) and for the manual policy path (tag-triggered releases). Also apply it when diagnosing a rejected OIDC exchange or revoking pipeline access.
42
42
 
43
43
  ## How It Works
44
44
 
45
- ### One-Command Setup (Branch-Based Deploys)
45
+ ### One-Command Setup (branch-triggered deploys)
46
46
 
47
- 1. **Run setup once** from the project checkout, signed in as a `super_user`:
47
+ 1. **Run setup once** from the project's checkout, signed in as a `super_user`:
48
48
 
49
49
  ```bash
50
- harper login https://my-cluster.example.com:9925
51
50
  harper deploy setup=true provider=github-actions project=my-app
52
51
  ```
53
52
 
54
- 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:
53
+ If your workflow file is not `.github/workflows/deploy.yaml`, pass `workflow=`:
55
54
 
56
55
  ```bash
57
56
  harper deploy setup=true provider=github-actions project=my-app workflow=.github/workflows/deploy.yml
58
57
  ```
59
58
 
60
- 2. **Understand what setup does** — in order:
61
- - 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`).
62
- - **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.
63
- - 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>`.
64
- - Reads the policy back and compares it claim by claim with what it intended to write.
65
- - Sets the repository's `HARPER_CLI_TARGET` Actions variable via `gh` if one does not already exist.
59
+ 2. **Understand what setup does** — it executes these steps automatically:
60
+ - Resolves the GitHub repository's numeric `repository_id` from the `origin` remote (or `repo=<owner>/<name>`). Pass `repository_id=<id>` explicitly for private repos without `gh`.
61
+ - Validates the workflow file: it must be able to run on `main` (or `branch=`), run a job in the `production` environment (`environment=`), and grant `id-token: write`. Setup stops before writing anything if the workflow could never match or never request a token.
62
+ - Creates a role and a user named `<project>-ci-deploy` with permissions limited to `deploy_component`, `get_deployment`, `get_job`, and `registration_info`, plus the trust policy `github-actions-<project>`.
63
+ - Reads the policy back and compares it claim by claim.
64
+ - Sets the repository's `HARPER_CLI_TARGET` variable via `gh` if one does not already exist.
66
65
 
67
- 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:
66
+ 3. **Handle idempotency**: setup only creates — it never overwrites. A role, user, or policy that already exists but differs stops setup before writing. To replace a policy:
68
67
 
69
68
  ```bash
70
69
  harper drop_oidc_trust id=github-actions-<project>
71
- harper deploy setup=true provider=github-actions project=my-app
72
70
  ```
73
71
 
74
- ### Manual Setup (Tag-Based or Custom Deploys)
72
+ Then run setup again.
73
+
74
+ ---
75
+
76
+ ### Manual Setup (tag-triggered deploys)
75
77
 
76
- Use this path when deploying on version tags, because a branch-pinned policy cannot match a tag ref.
78
+ Use this path when the workflow deploys on version tags, because a branch pin cannot match a tag ref.
77
79
 
78
- 1. **Create a deploy-only role and user**:
80
+ 1. **Log in as `super_user`**:
81
+
82
+ ```bash
83
+ harper login https://my-cluster.example.com:9925
84
+ ```
85
+
86
+ 2. **Create a deploy-only role and user** named `<project>-ci-deploy`:
79
87
 
80
88
  ```bash
81
89
  harper add_role role=ci_deploy permission='{"operations":["deploy_component","get_job","get_deployment","registration_info"]}'
@@ -83,12 +91,12 @@ Use this path when deploying on version tags, because a branch-pinned policy can
83
91
  ```
84
92
 
85
93
  - `deploy_component` — the deploy itself.
86
- - `get_job` — required for the wait step; an `operations` allowlist refuses anything not listed, so without it the wait step fails with `403`.
87
- - `get_deployment` — reads the deployment record.
88
- - `registration_info` — version check `harper deploy` makes before uploading.
94
+ - `get_deployment` — required for `harper deploy restart=rolling` to wait on every node; without it that wait fails with `403`.
95
+ - `get_job` — required for the wait step; without it the wait step fails with `403`.
96
+ - `registration_info` — version check before upload; omitting it logs a refused operation on every run.
89
97
  - `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.
90
98
 
91
- 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):
99
+ 3. **Add a trust policy** pinning `repository_id`, `workflow_path`, and `environment` (use this shape for tag-triggered workflows where `workflow_ref` cannot be pinned):
92
100
 
93
101
  ```bash
94
102
  harper add_oidc_trust \
@@ -100,45 +108,40 @@ Use this path when deploying on version tags, because a branch-pinned policy can
100
108
  ```
101
109
 
102
110
  Key claim rules:
103
- - `repository_id` is immutable — survives renames. Look it up with `gh api repos/my-org/my-app --jq .id`.
104
- - `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>`.
111
+ - `repository_id` is immutable and survives renames. Look it up with `gh api repos/my-org/my-app --jq .id`.
112
+ - `workflow_path` is derived from `workflow_ref` by removing the `@<ref>` suffix; format is `<owner>/<repo>/<path>`.
105
113
  - `environment` is the ref gate. Without it, anyone who can push a branch can mint a token.
106
114
  - `audience` must be the exact target URL with its port and a **trailing slash**.
107
115
  - 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.
108
116
 
109
- 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.
117
+ 4. **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.
110
118
 
111
- 4. **Store the target as a repository variable** (not a secret — it is not sensitive):
119
+ 5. **Store the target as a repository variable** (not a secret):
112
120
 
113
121
  ```bash
114
122
  gh variable set HARPER_CLI_TARGET --repo my-org/my-app --body https://my-cluster.example.com:9925/
115
123
  ```
116
124
 
125
+ ---
126
+
117
127
  ### How OIDC Authentication Works in the Workflow
118
128
 
119
- Once a trust policy exists, every `harper` command in the deploy job authenticates through it automatically when all four conditions hold:
129
+ A job authenticates automatically when all four conditions hold:
120
130
 
121
- 1. **The job may request an identity token.** On GitHub Actions that is `permissions: id-token: write` on the job.
122
- 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.
123
- 3. **The run matches the policy's claims**: its repository, its workflow file and branch, and its environment.
131
+ 1. **The job declares `permissions: id-token: write`.**
132
+ 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. It is not sensitive, so use `vars.HARPER_CLI_TARGET` rather than a secret.
133
+ 3. **The run matches the policy's claims**: its `repository_id`, `workflow_path`/`workflow_ref`, and `environment`.
124
134
  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.
125
135
 
126
- 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`.
136
+ Nothing in the workflow names the policy. Every `harper` command in the job (`harper deploy`, `harper get_job`, etc.) authenticates through it automatically.
127
137
 
128
138
  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.
129
139
 
130
- Nothing in the workflow names the policy. The CLI prints to stderr which policy authenticated it and as whom:
131
-
132
- ```
133
- Requesting a GitHub Actions identity token for https://my-instance.harperdb.io:9925/...
134
- Authenticated as 'ci-deploy' via OIDC trust policy 'my-app-prod'.
135
- ```
136
-
137
- 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.
140
+ ---
138
141
 
139
142
  ### Policy Specificity for GitHub Actions
140
143
 
141
- For `https://token.actions.githubusercontent.com`, a policy must satisfy all three of these:
144
+ For `issuer=https://token.actions.githubusercontent.com`, every policy must satisfy all three requirements:
142
145
 
143
146
  | Requirement | Satisfied by one of | Left open otherwise |
144
147
  | ---------------------- | ------------------------------------------------------------------------ | ----------------------------------------------- |
@@ -148,21 +151,68 @@ For `https://token.actions.githubusercontent.com`, a policy must satisfy all thr
148
151
 
149
152
  Additional constraints:
150
153
 
151
- - `repository_id` is preferred over `repository` — it is immutable and survives renames.
152
- - 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`.
153
154
  - `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`.
154
155
 
155
- ### Revoking Access
156
+ ---
157
+
158
+ ### Diagnosing a Rejected Exchange
159
+
160
+ Run `list_oidc_trust` (requires `super_user`) to list all policies including disabled ones:
161
+
162
+ ```json
163
+ { "operation": "list_oidc_trust" }
164
+ ```
165
+
166
+ `list_oidc_trust` 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 after the policy was written. The exchange itself will not report what failed.
167
+
168
+ Check the instance's `oidc-trust` log for server-side detail.
169
+
170
+ ---
171
+
172
+ ### Revoking Pipeline Access
173
+
174
+ - **Stop future runs** — drop the policy:
175
+
176
+ ```bash
177
+ harper drop_oidc_trust id=github-actions-<project>
178
+ ```
179
+
180
+ This does not revoke tokens already issued (they are stateless JWTs valid for one hour).
181
+
182
+ - **Stop a token already in circulation** — deactivate the user:
183
+
184
+ ```bash
185
+ harper alter_user username=<project>-ci-deploy active=false
186
+ ```
187
+
188
+ Use both together when responding to a suspected compromise.
156
189
 
157
- - **Stop future runs**: `harper drop_oidc_trust id=github-actions-<project>` (does not revoke already-issued tokens, which are valid up to one hour).
158
- - **Stop an already-issued token**: `harper alter_user username=<project>-ci-deploy active=false`.
159
- - **No CI secret to rotate** — there is none.
190
+ ---
191
+
192
+ ### Providing a Durable Credential for a Private Source
193
+
194
+ Do not use the workflow's ephemeral `GITHUB_TOKEN` for Harper's stored credential — it expires when the job ends, and later installs (a new node joining, a reinstall after restore) would fail. Instead use a durable token:
195
+
196
+ - For a private repository: a fine-grained personal access token with **Contents: read-only** scoped to the application repository.
197
+ - For GitHub Packages: a token with the `read:packages` scope.
198
+
199
+ Store it in Harper once from your machine:
200
+
201
+ ```bash
202
+ # Private git repository
203
+ harper deploy setup=true provider=github project=my-app
204
+
205
+ # GitHub Packages
206
+ harper deploy setup=true provider=npm project=my-app registry=https://npm.pkg.github.com scope=@my-org
207
+ ```
208
+
209
+ The name is derived from the component and the host: `deploy.my-app.git.github.com` for the repository, and `deploy.my-app.npm.pkg.github.com` for GitHub Packages. Reference it by that name in the workflow — the token never appears in your pipeline.
160
210
 
161
211
  ## Examples
162
212
 
163
213
  ### Path A: Deploy a Tagged Release (`v1.2.3`) from the Repository
164
214
 
165
- 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.
215
+ 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 is moved later. If this repository needs a build step to be runnable, do not reach for `install_allow_scripts` — use Path B instead.
166
216
 
167
217
  ```yaml
168
218
  # .github/workflows/deploy.yml
@@ -192,6 +242,8 @@ jobs:
192
242
  run: harper deploy project=my-app by_ref=true restart=rolling json=true > deploy.json
193
243
  - name: Wait for every node to take the release
194
244
  run: |
245
+ # From v5.4.0 a certified rolling deploy waited for every node itself.
246
+ [ "$(jq -r '.rollout.status // empty' deploy.json)" != success ] || exit 0
195
247
  JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
196
248
  [ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
197
249
  LAST="could not be read"
@@ -212,14 +264,17 @@ jobs:
212
264
  Key details:
213
265
 
214
266
  - **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.
215
- - **`persist-credentials: false`** keeps the job's `GITHUB_TOKEN` out of `.git/config`.
267
+ - **`persist-credentials: false`** keeps the job's `GITHUB_TOKEN` out of `.git/config`, where `actions/checkout` otherwise leaves it for every later step.
216
268
  - **`harper@^5.3`** pins the CLI's major version. 5.3 is the first CLI that authenticates with the identity token.
217
269
  - **`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.
218
- - **`restart=rolling`** restarts nodes one at a time. Use `restart=true` for a single-node or dev instance.
219
- - **`json=true`** prints the deploy's result as JSON on stdout; progress and authentication messages go to stderr.
220
- - **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).
221
- - **`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.
222
- - **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.
270
+ - **`restart=rolling`** restarts nodes one at a time so the cluster keeps serving throughout. Use `restart=true` for a single-node or dev instance.
271
+ - **`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 whether this ran.
272
+ - **`restartJobId`**: present in the deploy JSON when a rolling job was started but the deploy did not wait for it itself. The wait step polls `.[0].status` from `get_job` until `COMPLETE` or `ERROR`.
273
+ - **Load-balanced clusters**: the deployment record wait works through a load-balanced URL (the record replicates), but `get_job` polls do not — Harper does not replicate jobs. If your load balancer never routes back to the node that received the deploy, use one node's direct URL for both the deploy and the wait, and as the trust policy's `audience`.
274
+
275
+ For a private repository, add `credential=true` to the deploy command.
276
+
277
+ ---
223
278
 
224
279
  ### Path B: Build in CI, Publish to GitHub Packages, Deploy the Artifact
225
280
 
@@ -288,6 +343,7 @@ jobs:
288
343
  restart=rolling json=true > deploy.json
289
344
  - name: Wait for every node to take the release
290
345
  run: |
346
+ [ "$(jq -r '.rollout.status // empty' deploy.json)" != success ] || exit 0
291
347
  JOB_ID=$(jq -r '.restartJobId // empty' deploy.json)
292
348
  [ -n "$JOB_ID" ] || { echo "The rolling deploy returned no restartJobId"; exit 1; }
293
349
  LAST="could not be read"
@@ -305,54 +361,26 @@ jobs:
305
361
  exit 1
306
362
  ```
307
363
 
308
- 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.
309
-
310
- Credential roles:
311
-
312
- - **Publishing** and installing private `@my-org` dependencies use the workflow's ephemeral `GITHUB_TOKEN` (`packages: write` includes read). Both happen entirely inside `publish`.
313
- - **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.
364
+ Split into two jobs so the job with `id-token: write` runs none of your build code. Only the `deploy` job names `environment: production`, so only its token carries the `environment` claim the trust policy requires.
314
365
 
315
- 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:
316
-
317
- ```bash
318
- harper deploy setup=true provider=github project=my-app
319
- # or for GitHub Packages:
320
- harper deploy setup=true provider=npm project=my-app registry=https://npm.pkg.github.com scope=@my-org
321
- ```
322
-
323
- 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.
366
+ ---
324
367
 
325
- ### Check the Deployment Record
368
+ ### Checking the Deployment Record
326
369
 
327
370
  ```bash
328
371
  harper get_deployment deployment_id="$(jq -r .deployment_id deploy.json)" json=true
329
372
  ```
330
373
 
331
- 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.
374
+ Use `list_deployments` to see the history of what was deployed and when, and filter by commit with `sha`.
332
375
 
333
- ### List and Inspect Trust Policies
334
-
335
- ```bash
336
- harper list_oidc_trust
337
- ```
338
-
339
- 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.
340
-
341
- ### Drop a Trust Policy
342
-
343
- ```bash
344
- harper drop_oidc_trust id=github-actions-<project>
345
- ```
376
+ - **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.
346
377
 
347
378
  ## Notes
348
379
 
349
- - `HARPER_CLI_REFRESH_TOKEN` and any other stored credential outrank the OIDC exchange. Remove them from the workflow if switching to OIDC.
350
- - **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.
351
- - **Rotating the deploy credential**: run `harper deploy setup=true` again with the same name and a new value. The pipeline doesn't change.
352
- - **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).
353
- - 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.
354
- - The `audience` in `add_oidc_trust` must be the exact target URL with its port and a trailing slash.
355
- - 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.
356
- - `token.actions.githubusercontent.com` is the required `issuer` value for GitHub Actions OIDC policies.
357
- - Git-host credentials require git ≥ 2.31 on the Harper nodes and are not supported on Windows nodes. Registry credentials have no such constraints.
380
+ - `audience` in `add_oidc_trust` must include the port and a **trailing slash** — e.g., `https://my-cluster.example.com:9925/`. A URL without the trailing slash will not match.
381
+ - `HARPER_CLI_REFRESH_TOKEN` and any other configured credential (`auth_username=`/`auth_password=`, `HARPER_CLI_USERNAME`/`HARPER_CLI_PASSWORD`, `HARPER_CLI_OPERATION_TOKEN`, a saved `harper login`) outrank the OIDC exchange. Remove stored credentials from the runner before enabling OIDC.
382
+ - `list_oidc_trust`, `drop_oidc_trust`, and `alter_user` all require `super_user`.
383
+ - `drop_oidc_trust` does not revoke already-issued operation tokens (one-hour JWTs). Pair it with `alter_user username=<project>-ci-deploy active=false` to stop tokens already in circulation.
384
+ - `ref_type: tag` is not accepted as a ref gate. Gate tag-triggered workflows with `environment` instead, and set **Deployment branches and tags** to `v*` in the environment's settings.
385
+ - Every step in a job with `id-token: write` can request an identity token — GitHub gives each one `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`. Run none of the repository's code in that job.
358
386
  - See [deploying-to-harper-fabric.md](deploying-to-harper-fabric.md) for cluster-level deployment context.