docs: fixes from skills#81 - #662
Conversation
Each of these is a real error in v5 reference content that a `mode: generate` skill rule reproduced faithfully, so agents were being taught the broken pattern. - `database/api.md` — the `transaction()` timer example did not await the transaction, so a failed write became an unhandled rejection the callback never observed. Awaited, wrapped in try/catch/finally, and given an overlap guard for jobs that can outlast their interval. Also added the missing `transaction` and `isMainThread` imports. - `security/jwt-authentication.md` — the "no `Authorization` header is required" claim is true only for the username/password flow; it is now scoped to that shape. The scoped-token example showed no authentication even though the mint requires an authenticated super_user, so it now shows Basic Auth and names the `server.operation(..., true)` alternative. - `rest/overview.md` — the `rest: true` requirement contradicted its own no-configuration-file carve-out. `@export` is always required; `rest: true` is required whenever a `config.yaml` exists. - `components/applications.md` — the fetchable-ref constraint read as an absolute rule and so implied that valid SHA-based rollbacks are rejected. The full-commit-SHA exception is now stated in that paragraph. Refs HarperFast/skills#81 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the documentation across several files, clarifying commit SHA deployments, improving the transaction basic usage example with error handling and concurrency guards, and detailing REST configuration and JWT authentication flows. The review feedback highlights a missing logger import in the database API example and suggests splitting combined sentences to make critical security requirements and fallback behaviors more prominent and readable.
| import { isMainThread } from 'node:worker_threads'; | ||
| import { tables, transaction } from 'harper'; |
There was a problem hiding this comment.
The code example uses logger.error in the catch block, but logger is not imported from 'harper'. Since logger is a package export of 'harper', omitting it will result in a ReferenceError at runtime when an error is caught. Please add logger to the imported members.
| import { isMainThread } from 'node:worker_threads'; | |
| import { tables, transaction } from 'harper'; | |
| import { isMainThread } from 'node:worker_threads'; | |
| import { logger, tables, transaction } from 'harper'; |
There was a problem hiding this comment.
logger is available in the globals, though
| ## Create Authentication Tokens | ||
|
|
||
| Call `create_authentication_tokens` with your Harper credentials. No `Authorization` header is required for this operation. | ||
| Call `create_authentication_tokens` with your Harper credentials. When the request carries a `username` and `password` in the body, no `Authorization` header is required — the operation authenticates from the body. Other shapes of this operation do need an authenticated caller: sending no credentials at all mints tokens for the user the request is already authenticated as, and [minting a scoped token](#scoped-tokens-inline-role) requires an authenticated `super_user`. |
There was a problem hiding this comment.
The fallback behavior regarding sending no credentials is combined with other concepts in a single sentence. To ensure readers scanning the documentation can easily find it, please present this fallback behavior in separate, distinct sentences.
For example:
Call `create_authentication_tokens` with your Harper credentials. When the request carries a `username` and `password` in the body, no `Authorization` header is required — the operation authenticates from the body. Other shapes of this operation do need an authenticated caller. Sending no credentials at all mints tokens for the user the request is already authenticated as. Minting a scoped token (#scoped-tokens-inline-role) requires an authenticated `super_user`.References
- Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.
| A super user can mint a **scoped token**: a single JWT whose permissions are embedded in the token itself, so the bearer needs no pre-existing user or role record. This is useful for handing a limited credential (for example, read-only access) to an external service or script without provisioning it in `hdb_user`. | ||
|
|
||
| Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). The request must be authenticated as a `super_user`; no `password` may be included: | ||
| Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). Unlike the username/password flow above, this shape carries no credentials of its own — the **minter** must be authenticated as a `super_user`, and no `password` may be included in the body: |
There was a problem hiding this comment.
The critical security requirement that the minter must be authenticated as a super_user is combined with other concepts in a single sentence. Please present this security requirement in separate, distinct sentences to improve readability and scannability.
For example:
Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). Unlike the username/password flow above, this shape carries no credentials of its own. The **minter** must be authenticated as a `super_user`. No `password` may be included in the body:References
- Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.
| 1. The table is exported in a schema definition with [`@export`](../database/schema.md#export). | ||
| 2. REST is enabled for the application — normally `rest: true` in `config.yaml` (see [Configuration](#configuration)); a component directory with **no configuration file at all** gets it from Harper's built-in default instead, as described below. | ||
| 1. The table is exported in a schema definition with [`@export`](../database/schema.md#export). This is always required. | ||
| 2. REST is enabled for the application. `rest: true` must be present in `config.yaml` **whenever a configuration file exists** (see [Configuration](#configuration)); a component directory with no configuration file at all inherits `rest` from Harper's built-in default instead, as described below. |
There was a problem hiding this comment.
The fallback behavior of inheriting rest from Harper's built-in default is buried behind a semicolon. Please present this fallback behavior in a separate, distinct sentence to make it more prominent and easier to scan.
For example:
2. REST is enabled for the application. `rest: true` must be present in `config.yaml` **whenever a configuration file exists** (see [Configuration](#configuration)). A component directory with no configuration file at all inherits `rest` from Harper's built-in default instead, as described below.References
- Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-662 This preview will update automatically when you push new commits. |
🧹 Preview CleanupThe preview deployment for this PR has been removed. |
Five items were raised against v5 reference content while reviewing
HarperFast/skills#81. Four were real docs-source errors and are fixed here; one was a reviewer mistake and is left alone with the evidence below.Every behavior claim was traced in a read-only
HarperFast/harpercheckout atorigin/main(d1eae3d7f).1.
reference/database/api.md—transaction()timer example did not awaitWrong: the background-job example under
transaction(context?, callback)-> Basic Usage calledtransaction(...)bare.transaction()returns a promise that resolves after commit, so the timer callback finished before observing the result: a rejected write became an unhandled rejection, and a later tick could overlap a still-running transaction.Two further defects in the same snippet: neither
transactionnorisMainThreadwas imported, so the example throwsReferenceErroras written.Verified against:
resources/transaction.ts:87(_assignPackageExport('transaction', transaction)— it is aharperexport, matchingreference/resources/resource-api.md:1216);isMainThreadhas noharperexport (the full export list isResource,contentTypes,createBlob,databases,logger,models,operation,secrets,server,tables,threads,transaction), so it comes fromnode:worker_threadsas it does inresources/replayLogs.ts:7.Changed: the call is awaited, both imports added, and the body wrapped in
try/catch/finallywith arunningflag. Thecatchis load-bearing, not decoration:setIntervalignores the promise its async callback returns, so awaiting alone relocates the unhandled rejection rather than removing it. Two short bullets below the snippet state both rules.2.
reference/security/jwt-authentication.md— scoped-token example showed no authenticationWrong: two connected problems.
## Create Authentication Tokenssaid flatly "NoAuthorizationheader is required for this operation", which holds only for the username/password shape.## Scoped Tokens (Inline Role)then asserted the request "must be authenticated as asuper_user" while showing a bare JSON body with no authentication anywhere — so a request copied from the page fails with a 403.Verified against:
security/impersonation.ts:119-121—if (!trusted && !minter?.role?.permission?.super_user) throw new ClientError('Only super_user can create a token with an inline role', 403);. The super_user requirement is real, and the error string in the doc is quoted from here.security/tokenAuthentication.ts:288-290—'password' cannot be combined with an inline 'role' object, confirming the payload cannot carry its own credentials.security/tokenAuthentication.ts:152-153(comment) — "create_authentication_tokens is NO_AUTH, so verifyPerms ... never runs here", andutility/operation_authorization.ts:470lists it as NO_AUTH. So the minter identity comes from whatever the auth handler resolved, not from an operation permission check.integrationTests/apiTests/token-auth.test.mjs:198— the passing test is named "scoped token: super_user mints an inline-role token for a non-existent username" and issues the mint throughclient.req(), which setsAuthorization: Basic <base64 user:pass>for the admin super_user (integrationTests/apiTests/utils/client.mjs:9). Basic Auth is the mechanism the test itself uses.unitTests/security/impersonation.test.js:659— "untrusted mint without a super_user minter is rejected with 403".server/serverHelpers/serverUtilities.ts:371-378—server.operation(op, context, authorize)assignsoperation.hdb_user = context?.userand setsbypassAuth = !authorize, soauthorize: trueis what makes the mint run as (and be checked against) the calling user.Changed: (a) the no-header claim is scoped to the body-credential shape, with the other two shapes named; (b) a cURL example with an
Authorization: Basicheader was added, plus the exact 403 text and theserver.operation(..., context, true)in-component alternative.3.
reference/security/jwt-authentication.md—IssueTokensstatic-verb signature: no change, the reviewer is wrongThe automated reviewer claimed Harper statics receive
(target, data)and that the request context must come fromgetContext()rather than a second parameter, makingstatic async get(_target, context)wrong.That is not how
getis dispatched. Verb signatures differ by whether the verb has a body:server/REST.ts:298—resource.get(target, request)server/REST.ts:304—resource.delete(target, request)server/REST.ts:300,302,306—resource.post/put/patch(target, request.data, request)Resource.tsis explicit that the two-argument form for a body-less verb is(id, context):(
resources/Resource.ts:651-653, reached becausestatic getis declared withhasContent: false—resources/Resource.ts:113and:138;static deletelikewise at:186,190.)So
static async get(_target, context)is correct — in fact it is the formResource.tscalls preferred. The siblingstatic async post(_target, data)examples are correct too, and alreadyawait data, consistent withresources/DESIGN.md-> Conventions ("datais aMaybePromise—awaitit").RefreshJWTshares no flaw. Nothing was changed in either example.For completeness, the reviewer's alternative is not itself imaginary:
getContextis exported fromharper(index.ts:12). It is the right tool for a static that must also work when called directly from server-side code with no context argument. It is not a correction to the REST-dispatched example on this page.4.
reference/rest/overview.md—rest: truerequirement contradicted its own carve-outWrong: the numbered requirement and the "Neither half is sufficient on its own" paragraph in
## Tables and Their Automatic Endpointspresented@exportandrest: trueas symmetric hard requirements, while the:::notea few lines down correctly documents that a component directory with no configuration file gets REST from Harper's built-in default.Verified against:
components/DEFAULT_CONFIG.ts:1-2setsrest: truein the built-in default;components/componentLoader.ts:638-650picksharper-config.yaml, thenharperdb-config.yaml, then (non-root)config.yaml, and only falls back toDEFAULT_CONFIGwhen none exists — the file is parsed and used verbatim with no merge against the default. That disjointness is exactly why the carve-out is all-or-nothing and why an existingconfig.yamlomittingrestturns REST off.Changed:
@exportis now stated as always required;rest: trueis stated as required whenever a configuration file exists. The "Neither half is sufficient" paragraph now ends by pointing at the no-configuration-file case as the sole exception, so the requirement and the note agree.5.
reference/components/applications.md— ref constraint contradicted the commit-SHA bulletWrong: under
## Remote Management-> Deploying by Reference, the fetchable-ref paragraph read as an absolute rule ("Arefmust also name something a clone can fetch:refs/heads/*andrefs/tags/*, or a bare branch or tag name. Anything else ... is rejected"), which tells a reader that a SHA-based rollback is invalid — contradicting the paragraph above it.Verified against
bin/cliOperations.ts, which shows the constraint is narrower than the docs implied:assertCloneableRefNamespace(:382) returns early unless the ref starts withrefs/:if (!ref.startsWith('refs/') || ref.startsWith('refs/heads/') || ref.startsWith('refs/tags/')) return;. The namespace rejection only ever applies to arefs/-qualified ref. Its own comment says as much: "It can't catch a bare SHA that happens to be unreachable: an object ID carries no namespace to inspect."resolveExplicitRef(:428-441) then triesgit rev-parse --verify <ref>^{commit}locally, and on failureif (FULL_OBJECT_ID.test(ref)) return ref; // already immutable; nothing to pin it to, whereFULL_OBJECT_ID = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i(:323) covers SHA-1 and SHA-256 object IDs. Only after that does it fall back tols-remoteforrefs/tags/<ref>/refs/heads/<ref>.Changed: the paragraph now leads with the exception — a full commit SHA is accepted directly with no resolution attempted — then states the constraint as applying to every other form, with the namespace rejection correctly scoped to qualified refs.
Exclusions honored
harperdb-config.yaml->harper-config.yamlrename (docs(reference): use harper-config.yaml consistently in v5 #661). Untouched everywhere, includingreference/resources/resource-api.md.componentLoader.tswas read for fix 4 and does showharper-config.yamlfirst, but nothing in this PR renames anything.reference/database/api.md, which docs(resources): fix four static-verb examples that fail silently on Harper 5 #658 also touches — but a different section. docs(resources): fix four static-verb examples that fail silently on Harper 5 #658's change there is confined to Accepting Binary in JSON Requests (static async postonPhoto); this PR changes only the Basic Usage snippet undertransaction(context?, callback). Expect a small rebase. No other docs(resources): fix four static-verb examples that fail silently on Harper 5 #658 file is touched. Fix 3's non-change is consistent with docs(resources): fix four static-verb examples that fail silently on Harper 5 #658's root cause rather than in tension with it: that root cause is the unresolveddatapromise on body-carrying verbs, which says nothing aboutget's second parameter.AGENTS.mdsanctions the pattern and uses it.Verification
npm run format:writethennpm run format:check— clean.npm run build— succeeds, 406 documents processed, zero broken-anchor warnings (the only[WARNING]line is the pre-existingdocusaurus-plugin-llms-txtroute-exclusion notice, present onmain).id="scoped-tokens-inline-role"inbuild/reference/v5/security/jwt-authentication.htmlandid="serveroperationoperation-context-authorize"inbuild/reference/v5/http/api.html, each matching the emittedhref.reference_versioned_docs/untouched; no heading renamed, so no skills-manifestsection:selector moves.Refs
HarperFast/skills#81🤖 Generated with Claude Code