@taifoon/n8n-nodes-typesafe 0.1.0
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.
- package/LICENSE +18 -0
- package/README.md +183 -0
- package/dist/credentials/TaifoonGatewayApi.credentials.d.ts +13 -0
- package/dist/credentials/TaifoonGatewayApi.credentials.js +31 -0
- package/dist/credentials/TaifoonGatewayApi.credentials.js.map +1 -0
- package/dist/credentials/TypeSafeApi.credentials.d.ts +10 -0
- package/dist/credentials/TypeSafeApi.credentials.js +22 -0
- package/dist/credentials/TypeSafeApi.credentials.js.map +1 -0
- package/dist/credentials/taifoon.svg +1 -0
- package/dist/nodes/TaifoonTypeSafe/TaifoonTypeSafe.node.d.ts +5 -0
- package/dist/nodes/TaifoonTypeSafe/TaifoonTypeSafe.node.js +215 -0
- package/dist/nodes/TaifoonTypeSafe/TaifoonTypeSafe.node.js.map +1 -0
- package/dist/nodes/TaifoonTypeSafe/TaifoonTypeSafe.node.json +1 -0
- package/dist/nodes/TaifoonTypeSafe/taifoonTypeSafe.svg +1 -0
- package/dist/nodes/TaifoonTypeSafe/translate.d.ts +49 -0
- package/dist/nodes/TaifoonTypeSafe/translate.js +216 -0
- package/dist/nodes/TaifoonTypeSafe/translate.js.map +1 -0
- package/dist/package.json +69 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/KEY_POLICY.md +58 -0
- package/docs/SECURE_KEYS.md +81 -0
- package/docs/TRANSLATION.md +53 -0
- package/docs/WORKFLOWS.md +30 -0
- package/index.js +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Key policy and rotation
|
|
2
|
+
|
|
3
|
+
**Direct connection (the default): one secret.** Your TypeSafe key lives in n8n's encrypted credential store and is sent as a bearer token to `api.typesafe.ai` over TLS. Nobody else is in the path. Rotate it in TypeSafe's console and paste the new value into the credential; that is the whole procedure. Everything below concerns the OPTIONAL Taifoon gateway connection.
|
|
4
|
+
|
|
5
|
+
Three secrets exist in the gateway system. This page says where each one lives, who can see it, how it is
|
|
6
|
+
rotated, and what happens when one leaks. The node itself holds none of them outside n8n's
|
|
7
|
+
encrypted credential store.
|
|
8
|
+
|
|
9
|
+
| Secret | Owner | Lives | Never |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| **Taifoon principal key** (`tfn_live_…`) | you | n8n credential store (encrypted at rest with your instance's `N8N_ENCRYPTION_KEY`) | in a workflow JSON, an expression, a log, or git |
|
|
12
|
+
| **Your TypeSafe key** | you | n8n credential store; sent per request over TLS in `x-typesafe-key` | stored, logged or echoed by Taifoon. It exists on the deck for the duration of one call |
|
|
13
|
+
| **The house TypeSafe key** | Taifoon | the deck's 0600 env file on the production host | sent to any browser, node or client. It funds each principal's three trial calls and nothing else |
|
|
14
|
+
|
|
15
|
+
## What crosses the wire
|
|
16
|
+
|
|
17
|
+
`POST /api/login {key}` exchanges the principal key for an httpOnly, Secure, SameSite=Lax session
|
|
18
|
+
cookie valid for 12 hours. n8n caches that session as an expirable token and logs in again when it is
|
|
19
|
+
refused. Every later request carries the cookie and, only for `model: "jev"` after the trial, your
|
|
20
|
+
TypeSafe key in one header. Responses never contain a key; the deck's log lines (call and billing)
|
|
21
|
+
carry latency, tokens, USD and outcome, and never a key or your state.
|
|
22
|
+
|
|
23
|
+
## Rotation
|
|
24
|
+
|
|
25
|
+
**Your TypeSafe key.** Rotate it in TypeSafe's console, paste the new value into the n8n credential,
|
|
26
|
+
save. Nothing on Taifoon's side needs updating, because Taifoon never stored the old one. Rotate on a
|
|
27
|
+
schedule you would use for any vendor key (90 days is a reasonable default) and immediately on any of:
|
|
28
|
+
a teammate leaving, a workflow export shared outside your team, a credential test run on a machine
|
|
29
|
+
you do not control.
|
|
30
|
+
|
|
31
|
+
**Your Taifoon principal key.** Ask the operator to rotate it (`POST /agents/<id>/rotate-key` at the
|
|
32
|
+
identity door). The old key stops working at once; existing 12-hour sessions made with it expire on
|
|
33
|
+
their own and can be cut short by the operator. Update the n8n credential. Your licence, trial count
|
|
34
|
+
and spend ledger are attached to your principal, not to the key string, so they survive rotation.
|
|
35
|
+
|
|
36
|
+
**The house key.** Taifoon's to rotate: replace it in the deck's env file and restart the deck. Trial
|
|
37
|
+
calls pause for the length of the restart. No user action is needed.
|
|
38
|
+
|
|
39
|
+
## If a key leaks
|
|
40
|
+
|
|
41
|
+
| Leaked | Blast radius | Do this |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| your TypeSafe key | spend on YOUR TypeSafe quota, by anyone, until rotated | rotate in TypeSafe's console now; review their usage page |
|
|
44
|
+
| your Taifoon key | calls billed to your Taifoon key up to its DAILY token ceiling; your three trial calls; read access to the deck as you | ask the operator to rotate; the ceiling bounds the damage per day |
|
|
45
|
+
| the house key | Taifoon's TypeSafe quota | Taifoon rotates; trial calls resume after |
|
|
46
|
+
|
|
47
|
+
A Taifoon key cannot be used to read anyone's TypeSafe key: the deck has none to read.
|
|
48
|
+
|
|
49
|
+
## Policy the deck enforces, so you do not have to trust the node
|
|
50
|
+
|
|
51
|
+
- The first **three** `jev` calls a principal ever makes are funded by Taifoon. The count is per
|
|
52
|
+
principal, persisted, and a call that fails upstream is handed back.
|
|
53
|
+
- After that, a `jev` call needs **both** the `jev` licence on your principal's grant and your own
|
|
54
|
+
TypeSafe key. A missing piece is a `402` that names it. There is no silent fallback to the house key.
|
|
55
|
+
- Every call is metered per key **and** per model, persisted across restarts, against a daily ceiling.
|
|
56
|
+
- Login throttles: ten refused keys in five minutes from one address rests the door for that window.
|
|
57
|
+
- An answer that does not validate is never repaired or defaulted; with **Fail Closed** on (the
|
|
58
|
+
default) the node stops the item instead of passing a null downstream.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Supplying keys securely
|
|
2
|
+
|
|
3
|
+
Three ways to give this node a TypeSafe key, from simplest to most locked-down. All three keep the
|
|
4
|
+
key encrypted at rest and none of them needs a fork of this package.
|
|
5
|
+
|
|
6
|
+
## 1 · The credential form (most users)
|
|
7
|
+
|
|
8
|
+
**Credentials → New → TypeSafe API**, paste the key, save. n8n encrypts every credential at rest with
|
|
9
|
+
your instance's `N8N_ENCRYPTION_KEY`. The key goes from your browser to your n8n over TLS, and from
|
|
10
|
+
your n8n to `api.typesafe.ai` over TLS. Nobody else is in the path, including us.
|
|
11
|
+
|
|
12
|
+
## 2 · Operator-provisioned, users never see the key (teams)
|
|
13
|
+
|
|
14
|
+
n8n can pre-fill a credential type for the whole instance from a **secrets file**. Users pick
|
|
15
|
+
"TypeSafe API" and it works; the key is never typed, displayed or exported by them.
|
|
16
|
+
|
|
17
|
+
`credentials-overwrite.json`, **minified** (n8n requires no spaces or newlines), keyed by the
|
|
18
|
+
credential TYPE name this package registers:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{"typeSafeApi":{"apiKey":"<your TypeSafe key>","baseUrl":"https://api.typesafe.ai"}}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# docker-compose.yaml
|
|
26
|
+
services:
|
|
27
|
+
n8n:
|
|
28
|
+
image: docker.n8n.io/n8nio/n8n:<pin a version>
|
|
29
|
+
environment:
|
|
30
|
+
CREDENTIALS_OVERWRITE_DATA_FILE: /run/secrets/n8n_credentials_overwrite
|
|
31
|
+
N8N_ENCRYPTION_KEY_FILE: /run/secrets/n8n_encryption_key
|
|
32
|
+
secrets: [n8n_credentials_overwrite, n8n_encryption_key]
|
|
33
|
+
secrets:
|
|
34
|
+
n8n_credentials_overwrite: { file: ./secrets/credentials-overwrite.json } # chmod 600, never in git
|
|
35
|
+
n8n_encryption_key: { file: ./secrets/encryption_key } # chmod 600, BACK IT UP off the host
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `_FILE` suffix works on any n8n setting and exists precisely so secrets stay out of environment
|
|
39
|
+
variables, which leak into `docker inspect`, process listings, crash dumps and CI logs. On Kubernetes,
|
|
40
|
+
mount a `Secret` at the same paths.
|
|
41
|
+
|
|
42
|
+
For the optional Taifoon gateway the type name is `taifoonGatewayApi`
|
|
43
|
+
(`{"taifoonGatewayApi":{"deckUrl":"https://deck.taifoon.dev","principalKey":"…","typesafeKey":"…"}}`).
|
|
44
|
+
|
|
45
|
+
## 3 · A vault (n8n Enterprise)
|
|
46
|
+
|
|
47
|
+
n8n's External Secrets feature reads credentials from HashiCorp Vault, AWS, GCP, Azure or Infisical at
|
|
48
|
+
run time. It is an Enterprise feature; if you have it, reference the secret in the credential field
|
|
49
|
+
and this node needs no change.
|
|
50
|
+
|
|
51
|
+
## Why there is no `.env` support inside the node
|
|
52
|
+
|
|
53
|
+
A node that reads `process.env` or the file system cannot be verified by n8n, and for good reason: a
|
|
54
|
+
community package would then be able to read every other secret on your instance. Option 2 gives you
|
|
55
|
+
the same operational result (configure once, in a file, outside the UI) through n8n's own mechanism,
|
|
56
|
+
with the published package. If you fork the node to read a `.env`, you lose updates and verification
|
|
57
|
+
and gain nothing.
|
|
58
|
+
|
|
59
|
+
## Never send us a key
|
|
60
|
+
|
|
61
|
+
You do not need to give Taifoon a TypeSafe key, ever. On the direct connection we are not in the path.
|
|
62
|
+
On the optional gateway connection your key travels per request over TLS in one header, is used for
|
|
63
|
+
that call, and is never stored, logged or echoed. There is deliberately no form, email address or chat
|
|
64
|
+
where we accept keys. If anyone asks you for one in our name, it is not us.
|
|
65
|
+
|
|
66
|
+
If two organisations ever must hand a secret to each other, do it with public-key encryption to the
|
|
67
|
+
recipient (for example [age](https://age-encryption.org): `age -r <recipient public key>`), never in
|
|
68
|
+
chat, email, a ticket or a repository, and rotate it afterwards. Prefer not needing to: each side
|
|
69
|
+
holding its own vendor key is the design here.
|
|
70
|
+
|
|
71
|
+
## Rotation and loss
|
|
72
|
+
|
|
73
|
+
| Event | Do this |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Routine | Rotate the TypeSafe key in TypeSafe's console every 90 days; update the credential or the secrets file; restart n8n if you use the file. |
|
|
76
|
+
| A workflow export, screenshot or log may have shown it | Rotate now. n8n never exports credential values in workflow JSON, but Code nodes and error messages can. |
|
|
77
|
+
| Someone with instance access leaves | Rotate the key, and consider rotating `N8N_ENCRYPTION_KEY` (re-save credentials after). |
|
|
78
|
+
| `N8N_ENCRYPTION_KEY` is lost | Every stored credential is unreadable. Re-enter them. This is why the key needs an off-host backup. |
|
|
79
|
+
|
|
80
|
+
This node never writes a key into an execution record: HTTP errors are reduced to a status and the
|
|
81
|
+
service's own message, with key-shaped strings redacted, before n8n stores them.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# What the translation layer can and cannot do
|
|
2
|
+
|
|
3
|
+
**Languages:** English, Spanish, German, French, Portuguese, Italian, Polish, Dutch, Russian, Japanese, Arabic. The tables below show the English words; every language has the equivalent pack in `translate.ts`. European packs match whole words (Unicode-aware, so accents are safe). Japanese and Arabic match anywhere, because Japanese has no spaces between words and Arabic attaches particles; for the same reason a one-letter particle is never used as a marker. A colon, in any language, always starts the option list.
|
|
4
|
+
|
|
5
|
+
The node sits between a workflow, which speaks JSON items and branches, and a System One model, which
|
|
6
|
+
speaks typed questions and probabilities. It translates in both directions, in plain code.
|
|
7
|
+
|
|
8
|
+
## Forward: a task becomes typed questions (the Translate operation)
|
|
9
|
+
|
|
10
|
+
Rules, applied per clause, in this order:
|
|
11
|
+
|
|
12
|
+
| The clause contains | Becomes | Details |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| a selection verb: classify, categorise, route, pick, choose, select, label, tag, assign, triage, sort, "which of", "one of", "decide which …" | **choice** | Options are taken from the task itself: after a colon or after *into / as / between / among / one of*, split on commas, slashes, pipes and "or". 2 to 50. |
|
|
15
|
+
| a rating verb: rate, score, rank, grade, "how severe / likely / urgent / relevant …", severity, priority, quality, "on a scale", "out of N" | **score** | A named numeric scale ("from 1 to 5", "out of 10", up to ten levels) becomes that many levels; otherwise a four-level rubric *None / Low / Medium / High* that you should replace. |
|
|
16
|
+
| anything else | **noul** | Rewritten as a question; lead-ins such as "check if" and "determine whether" are removed. Returns the probability the statement is true. |
|
|
17
|
+
|
|
18
|
+
Compound tasks are split on new lines, list markers at the start of a line, semicolons, and sentence
|
|
19
|
+
boundaries (never inside a decimal). Up to 12 clauses; the default battery is 8.
|
|
20
|
+
|
|
21
|
+
What it refuses to do, deliberately:
|
|
22
|
+
|
|
23
|
+
- **It never invents options.** "Classify this ticket" with no categories comes back as a choice
|
|
24
|
+
marked `needs_input`, and is left out of the runnable battery. `ready` is `false`.
|
|
25
|
+
- **It asks no model.** The same task always compiles to the same battery, it costs nothing, and
|
|
26
|
+
every question carries `explain`: the rule that produced it.
|
|
27
|
+
- **It does not write your rubric.** A score without a named scale gets a generic one and says so.
|
|
28
|
+
|
|
29
|
+
## Backward: answers become branches (the Routing map on Ask)
|
|
30
|
+
|
|
31
|
+
| Question kind | Threshold keys | Outcome |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| noul | `gte`, `lte` on the probability | pass / fail |
|
|
34
|
+
| choice | `minConfidence`; `in` (accepted options) | below `minConfidence` → **review**; outside `in` → fail |
|
|
35
|
+
| score | `min`, `max` on the level | pass / fail |
|
|
36
|
+
|
|
37
|
+
`branch` is **review** if any decision is review, **pass** only if every routed question passed,
|
|
38
|
+
otherwise **fail**. An answer that did not validate is always **review**: nothing fails open. The
|
|
39
|
+
node exposes the three as real outputs, so they are wires on the canvas.
|
|
40
|
+
|
|
41
|
+
Translate returns thresholds only as `suggestedRouting` and leaves `routing` EMPTY: the branch is the AND of every routed question, so auto-filled thresholds made a calm refund ticket "fail" for not being urgent (measured in n8n). Copy in only what you mean to gate on. A score is a zero-based level index.
|
|
42
|
+
|
|
43
|
+
Suggested thresholds are starting points, not fits. A System One model's probabilities
|
|
44
|
+
are calibrated on its vendor's distribution, not on your items. Measured on our own benchmark, Jev
|
|
45
|
+
matched or beat Claude on every thresholded decision while its raw calibration was worse: **fit each
|
|
46
|
+
threshold on your own labelled items, and leave a question unrouted until you have.**
|
|
47
|
+
|
|
48
|
+
## State
|
|
49
|
+
|
|
50
|
+
With `jev`, state is any JSON up to 96,000 characters: `{{ JSON.stringify($json) }}` sends the whole
|
|
51
|
+
incoming item. Compute first, judge second: do arithmetic in a Code node and send the result; ask the
|
|
52
|
+
model for judgment, never for a sum. House models (`algotrada`, `auditor`) need a flat object of
|
|
53
|
+
numbers and short symbols, answer noul and choice only, and return an uncalibrated boolean.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Workflows
|
|
2
|
+
|
|
3
|
+
Each pattern below is one Taifoon TypeSafe node plus ordinary n8n nodes. All of them follow the same
|
|
4
|
+
three moves: **compute** the state upstream, **ask** every question that might matter in one call
|
|
5
|
+
(they are answered in parallel and in isolation, so ten cost about what one does), **route** on
|
|
6
|
+
thresholds that live on the canvas.
|
|
7
|
+
|
|
8
|
+
| Workflow | Trigger → state | Questions | Routing |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| **Support triage** | Helpdesk webhook → the ticket JSON | noul *is a refund requested?* · choice *team: billing, technical, sales, abuse* · score *urgency 1 to 5* | Pass → assign to team queue · Review (low confidence) → human inbox · Fail → auto-reply |
|
|
11
|
+
| **Lead qualification** | CRM new lead → lead + enrichment | score *fit against your ICP rubric* · noul *is the budget stated?* · choice *segment* | score ≥ 3 → sales · else nurture |
|
|
12
|
+
| **Invoice guard** | Inbox attachment → extracted fields + last 5 invoices | noul *duplicate of a previous one?* · noul *amount out of pattern?* | any pass → hold for approval |
|
|
13
|
+
| **Content moderation** | Form submit → text | choice *ok, spam, abusive, off-topic* · score *severity* | confidence < 0.6 → Review, never auto-ban |
|
|
14
|
+
| **LLM guardrail** | Before and after an LLM node → the prompt or the reply | noul *contains personal data?* · noul *instruction injection?* | fail closed: stop the item |
|
|
15
|
+
| **Alert de-noising** | Monitoring webhook → alert + last hour of context | noul *is this a repeat of a known flap?* · score *customer impact* | page only when impact ≥ High |
|
|
16
|
+
| **Strategy gate** (trading) | Schedule → indicators computed in a Code node | one noul per gate of your state machine | every gate must pass; Review → stand down |
|
|
17
|
+
|
|
18
|
+
The importable example is `examples/typed-gate.workflow.json`.
|
|
19
|
+
|
|
20
|
+
## Operating notes
|
|
21
|
+
|
|
22
|
+
- **Latency.** Jev answers a 4 to 8 question battery in 0.3 to 0.8 s. `auditor` takes about a minute.
|
|
23
|
+
The edge cuts any request at 100 s.
|
|
24
|
+
- **Cost.** About 450 to 550 input tokens per battery, 0.00002 USD on Jev. Output is free.
|
|
25
|
+
- **Retries.** A `503` means the lane is unavailable and nothing was charged; n8n's *Retry On Fail*
|
|
26
|
+
with backoff is the right response. A `402` is not retryable: it names what is missing.
|
|
27
|
+
- **Observability.** Use **List Lanes** on a schedule to alert on trial exhaustion, spend against the
|
|
28
|
+
daily ceiling, and lane health. The deck logs every call and every charge on its own side.
|
|
29
|
+
- **Batching.** One item is one request. For many items, keep n8n's batching on and the deck's daily
|
|
30
|
+
ceiling in mind; TypeSafe allows 1,200 requests a minute.
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// n8n loads nodes and credentials from the n8n attribute in package.json
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taifoon/n8n-nodes-typesafe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeSafe for n8n: ask Jev (a System One model) yes/no, pick-one and rate-it questions about any workflow item and route on calibrated answers. Works directly with your own TypeSafe key; includes a task-to-questions translator and Pass / Fail / Review outputs.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"typesafe",
|
|
8
|
+
"jev",
|
|
9
|
+
"system-one",
|
|
10
|
+
"taifoon",
|
|
11
|
+
"decision",
|
|
12
|
+
"classification",
|
|
13
|
+
"routing",
|
|
14
|
+
"ai"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"homepage": "https://github.com/taifoon-io/n8n-nodes-typesafe#readme",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Taifoon",
|
|
20
|
+
"url": "https://github.com/taifoon-io"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/taifoon-io/n8n-nodes-typesafe.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.15"
|
|
28
|
+
},
|
|
29
|
+
"main": "index.js",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "n8n-node build",
|
|
32
|
+
"dev": "n8n-node dev",
|
|
33
|
+
"lint": "n8n-node lint",
|
|
34
|
+
"lint:fix": "n8n-node lint --fix",
|
|
35
|
+
"release": "n8n-node release",
|
|
36
|
+
"prepublishOnly": "n8n-node prerelease"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"docs",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"n8n": {
|
|
45
|
+
"n8nNodesApiVersion": 1,
|
|
46
|
+
"strict": true,
|
|
47
|
+
"credentials": [
|
|
48
|
+
"dist/credentials/TypeSafeApi.credentials.js",
|
|
49
|
+
"dist/credentials/TaifoonGatewayApi.credentials.js"
|
|
50
|
+
],
|
|
51
|
+
"nodes": [
|
|
52
|
+
"dist/nodes/TaifoonTypeSafe/TaifoonTypeSafe.node.js"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@n8n/node-cli": "^0.23.0",
|
|
57
|
+
"eslint": "^9.39.5",
|
|
58
|
+
"typescript": "5.9.2"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"n8n-workflow": "*"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/taifoon-io/n8n-nodes-typesafe/issues"
|
|
68
|
+
}
|
|
69
|
+
}
|