# Day-one verification Step 1 ran green against `api.typesafe.ai` on 2026-09-19: **16/16 assumptions hold**. Re-run it after any change to the transport, and on a new account or base URL. Two assumptions taken from the compiled SDK bundle turned out to be wrong, and both failed silently rather than loudly: | Assumed | Actual | What broke | | --- | --- | --- | | `GET /v1/models` returns a bare array | `{ "models": [...] }` | Model dropdown came back empty | | Errors expose `error.message` | `{ "detail": { "error_type", "message" } }` | API failures showed a status with no explanation | Both are fixed, pinned by tests, and asserted by the checker. Models available on this account: `jev-latest` and `jev-preview`. `jev-latest` currently resolves to `jev-1.13.0`. Work through the rest in order. --- ## 1. Check the API against our assumptions ```bash node scripts/verify-api.mjs ``` It loads a gitignored `.env` at the project root and accepts `TYPESAFE_API_KEY`, `JEV_API`, `JEV_API_KEY` or `TYPESAFE_KEY`, in that order. An explicit environment variable wins over the file. Costs two `systemOne` calls. Prints a PASS/FAIL line per assumption and, for any failure, the exact file that breaks. Green means the node's wire contract is correct and you can trust the rest. The assumptions it checks, in rough order of how much damage a mismatch would do: | # | Assumption | If wrong, fix | | --- | --- | --- | | 1 | `Authorization: Bearer ` is the accepted scheme | `credentials/TypeSafeApi.credentials.ts`, `authenticate` | | 2 | `POST /v1/systemone` takes `{ model, state, questions }` | `nodes/TypeSafeJev/transport.ts`, `systemOne` | | 3 | Response is `{ model, answers, usage }` at the top level, not wrapped | `TypeSafeJev.node.ts`, payload assembly | | 4 | `answers` is keyed by our question names | README examples | | 5 | Answer shapes per type (`choice`/`noul`/`score`) | `types.ts`, `simplifyAnswer` | | 6 | `usage` is snake_case (`input_tokens`) | README | | 7 | `GET /v1/models` returns `{ models: [...] }` or a bare array | `transport.ts`, `listModels` | | 8 | Errors expose a message under `detail`, `error` or `message` | `transport.ts`, `readErrorMessage` | | 9 | `x-typesafe-request-id` is present on success **and** failure | `transport.ts`, Include Request ID option | | 10 | A `noul` question is accepted with no `criteria` key | `questions.ts`, noul branch | Assumptions 7 and 8 were the ones that turned out wrong on first contact, and both were cheap to fix: a type declaration and one mapping function, not architecture. Treat 3 and 5 as the next most fragile if the API version changes. --- ## 2. Run it inside n8n Ran green on 2026-09-19 against n8n 2.39.8 with a real key. Both input modes returned correct answers on all three sample tickets, `score` matched the expected value of its probabilities in every case, and the request ID option populated. The JSON-mode node returning answers at all is the regression proof: that path could never work before. Note that n8n 2.x binds port 5678 well before its routes mount, so `http://localhost:5678` answers 404 for the first few minutes of a cold start while migrations run. It is not hung. Wait for a 200 rather than restarting. `n8n-node dev` pulls `n8n@latest`, and n8n 2.x requires **Node 24 or newer**. On an older Node the install fails rather than warning clearly. Check first: ```bash node -v ``` If it is below 24, switch before running dev (the node itself still builds and ships fine on Node 20.15+, this is only n8n's own requirement): ```bash nvm install 24 nvm use 24 ``` Then: ```bash pnpm install pnpm run dev ``` This starts a local n8n with the node symlinked in, using `~/.n8n-node-cli` as an isolated user folder so it does not touch existing n8n data. The first run downloads n8n in full, which takes several minutes. Let it finish. Killing it partway leaves a half-written package in the npm cache, and the next run then fails with `Cannot find module '../package.json'`; recover by deleting the matching folder under `~/AppData/Local/npm-cache/_npx/`. 1. Create a **TypeSafe API** credential, paste the key, press **Test**. It should go green, which exercises `authenticate` and `test` together. 2. Import **`examples/ticket-triage.dev.workflow.json`**, not the plain one. See the note below. 3. Set the credential on both Jev nodes. 4. Execute. ### Do not use the npm-named workflow in dev n8n registers a node under a different type name depending on how it was loaded: | Loaded via | Registered type | | --- | --- | | npm install, which is what real users do | `@n3ndor/n8n-nodes-typesafe-jev.typeSafeJev` | | `pnpm run dev`, from the `custom/` folder | `CUSTOM.typeSafeJev` | `CustomDirectoryLoader` hardcodes its package name to `CUSTOM`, so a workflow exported from one mode will not resolve in the other. It fails with `Unrecognized node type`, which looks exactly like the node failing to load even though it loaded fine. `examples/ticket-triage.workflow.json` is canonical and uses the npm name. Regenerate the dev copy after editing it: ```bash node scripts/dev-workflow.mjs ``` Also note that **Settings → Community Nodes → Install** cannot be used during development. It installs from npm and will fail with `Package version does not exist` until the package is published. Dev nodes load from disk instead, with no install step. Find it by pressing Tab on the canvas and searching `TypeSafe`. The workflow runs three sample tickets through **both** input modes at once: - **Jev (Question Builder)**: per-type fields, full response appended under `typesafeJev`, request ID included. - **Jev (JSON, Simplified)**: hand-typed JSON schema, answers only, simplified. The second node is the regression test that matters: a hand-typed JSON schema is exactly what the previous version could never accept. If it returns answers rather than *"Questions must be a non-empty JSON object"*, that bug is confirmed dead against a real instance. Expected shape from the simplified node: ```json { "intent": "refund", "urgent": 0.87, "frustration": 2 } ``` --- ### Confirming the package loads, without logging in n8n's REST and `/types/nodes.json` endpoints need an authenticated session, so checking registration normally means creating an owner account first. You can skip that by running the loader n8n itself uses at startup, from any directory that has n8n installed: ```js const { PackageDirectoryLoader } = require('n8n-core'); const loader = new PackageDirectoryLoader('D:/Programmierung/PROJECTS/n8n-nodes-typesafe-jev'); await loader.loadAll(); console.log(loader.loadedNodes, loader.types.credentials.map((c) => c.name)); ``` Last run against n8n 2.39.8 reported `typeSafeJev v1`, credential `typeSafeApi`, the codex categories, `usableAsTool: true`, and the per-type `displayOptions` intact. If the package were malformed, this throws where n8n would otherwise fail silently at startup. One Windows-only artifact: the loader builds `iconUrl` with `path.join`, so locally it comes out as `dist\nodes\...\typesafeJev.svg` with backslashes and the icon may not render in a local dev instance. That is n8n's path handling on Windows, not a fault in the package, and it does not occur on a Linux or Docker host. ## 3. Spot-check the UI These are the things unit tests cannot see: - [ ] Question **Type** switches the visible fields: Options for choice, Rubric Levels for score, Describe Yes/No for yes-no. If all fields show at once, `displayOptions` has been stripped from `properties.ts` (see the warning in that file). - [ ] **Model** dropdown populates from the account rather than erroring. - [ ] Node icon renders in both light and dark themes. - [ ] Node shows the model as its subtitle on the canvas. - [ ] Attach the node to an **AI Agent** as a tool. It should appear, because `usableAsTool` is set. - [ ] Break a question on purpose (one rubric level). The error should say *"needs a criteria array with at least two rubric levels"* and **no HTTP call should be made**. - [ ] Revoke or mistype the key. The error should name the status and carry a request ID. --- ## 4. Releasing - [ ] `pnpm run release` to cut `0.2.0`. Publishing happens in CI with provenance, never from this machine. - [ ] Submit for verification once it has run against a real key for a while. Verification is becoming load-bearing rather than cosmetic. n8n 2.39.8 emits this on startup: ``` N8N_UNVERIFIED_PACKAGES_ENABLED -> The default for this variable will change to `false` in a future version. Set it to `true` explicitly to keep installing unverified community packages. ``` Once that default flips, installing an unverified node stops being a click in the UI and becomes an environment-variable change on the n8n host. On someone else's instance that means involving whoever administers it. The three hard blockers for verification are already cleared: zero runtime dependencies, no environment reads, and provenance publishing from CI. ### The npm trusted publishing bootstrap A Trusted Publisher can only be attached to a package that **already exists**. This is true of the npmjs.com website and of `npm trust` alike: the CLI answers `404 Not Found` on `POST /-/package//trust` for a name that has never been published. Verified against npm 11.19.0 on 2026-09-23. `npm trust github ... --dry-run` reports success on that same name. The dry run validates arguments without calling the endpoint, so it cannot fail for this reason and proves nothing about whether the real call will work. The bootstrap is therefore one local publish, and no token at any point: | # | When | Action | | --- | --- | --- | | 1 | Once, by the account owner | `npm publish`. Local, under 2FA. Creates the name. This version carries no provenance, because provenance requires the CI OIDC token. | | 2 | Immediately after | `npm trust github "@n3ndor/n8n-nodes-typesafe-jev" --file publish.yml --repo n3ndor/n8n-nodes-typesafe-jev --allow-publish` | | 3 | Confirm | `npm trust list "@n3ndor/n8n-nodes-typesafe-jev"` | | 4 | Every release after | `pnpm run release`. CI exchanges its OIDC token for publish rights and attaches provenance. | No `NPM_TOKEN` secret exists and none is needed. Withdraw the grant with `npm trust revoke`. Submit a version from step 4 to the Creator Portal, not the bootstrap version. What this grants is worth stating plainly: anyone who can push a tag to this repository can publish the package. Repository write access is publish access, which is the same exposure a stored token would carry, without the stored token. Full details are in the header of `.github/workflows/publish.yml`. --- ## Known gap **Dynamic routing outputs**, sending a `choice` answer down its own branch the way the Switch node does, is not implemented, because it needs a live n8n to verify the `outputs` expression against. Until then, route with a Switch node reading the answer: ``` {{ $json.typesafeJev.answers.intent.choice }} ``` or, with **Answers Only** + **Simplify**: ``` {{ $json.intent }} ``` That is the 0.3.0 candidate and the main reason to install this over an HTTP Request node.