@tpsdev-ai/flair 0.30.0 → 0.31.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/README.md +194 -377
- package/dist/cli.js +1355 -281
- package/dist/deploy.js +212 -24
- package/dist/fabric-upgrade.js +16 -1
- package/dist/federation/scheduler.js +500 -0
- package/dist/install/clients.js +111 -53
- package/dist/lib/mcp-spec.js +128 -0
- package/dist/lib/safe-snapshot-extract.js +231 -0
- package/dist/lib/scheduler-platform.js +128 -0
- package/dist/lib/xml-escape.js +54 -0
- package/dist/rem/scheduler.js +35 -87
- package/dist/rem/snapshot.js +13 -0
- package/dist/replication-convergence.js +505 -0
- package/dist/resources/MemoryBootstrap.js +7 -8
- package/dist/resources/SemanticSearch.js +17 -45
- package/dist/resources/abstention.js +1 -1
- package/dist/resources/embeddings-boot.js +10 -12
- package/dist/resources/embeddings-provider.js +10 -7
- package/dist/resources/health.js +24 -19
- package/dist/resources/in-process.js +225 -0
- package/dist/resources/mcp-tools.js +23 -17
- package/dist/resources/migration-boot.js +80 -10
- package/dist/resources/migrations/data-dir.js +205 -0
- package/dist/resources/migrations/progress.js +33 -0
- package/dist/resources/migrations/runner.js +29 -2
- package/dist/resources/migrations/state.js +13 -2
- package/dist/resources/models-dir.js +18 -9
- package/dist/resources/semantic-retrieval-core.js +5 -4
- package/dist/src/lib/scheduler-platform.js +128 -0
- package/dist/src/lib/xml-escape.js +54 -0
- package/dist/src/rem/scheduler.js +35 -87
- package/docs/deploying-on-fabric.md +267 -0
- package/docs/deployment.md +5 -0
- package/docs/embedding-in-a-harper-app.md +299 -0
- package/docs/federation.md +61 -4
- package/docs/integrations.md +3 -0
- package/docs/mcp-clients.md +16 -7
- package/docs/quickstart.md +80 -54
- package/docs/releasing.md +72 -38
- package/docs/supply-chain-policy.md +36 -0
- package/docs/troubleshooting.md +24 -0
- package/docs/upgrade.md +98 -3
- package/package.json +1 -11
- package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
- package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
- package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
- package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
- package/dist/resources/rerank-provider.js +0 -569
- package/docs/rerank-provisioning.md +0 -101
package/docs/quickstart.md
CHANGED
|
@@ -2,37 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
From zero to a persistent agent memory in five minutes.
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0. Prerequisites
|
|
6
6
|
|
|
7
|
-
**Node.js 22 or newer.**
|
|
7
|
+
**Node.js 22 or newer.** No Docker, no database to install, no API keys — Flair runs in a single process and computes embeddings locally.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
node --version # v22.x.x or
|
|
10
|
+
node --version # v22.x.x or newer
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Need it? [nodejs.org](https://nodejs.org/), `nvm install 22`, or `brew install node`.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**A user-writable npm global prefix.** Do not install Flair with `sudo`. A root-owned install cannot write the embedding model into its own package directory, and semantic search silently falls back to keyword-only matching. `nvm` gives you a user-owned prefix already; otherwise:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
mkdir -p ~/.npm-global
|
|
19
|
+
npm config set prefix ~/.npm-global
|
|
20
|
+
export PATH="$HOME/.npm-global/bin:$PATH" # add this to ~/.zshrc or ~/.bashrc
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 1. Install
|
|
16
24
|
|
|
17
25
|
```bash
|
|
18
26
|
npm install -g @tpsdev-ai/flair
|
|
19
27
|
```
|
|
20
28
|
|
|
21
|
-
`flair`, `flair-mcp
|
|
29
|
+
One install gives you `flair`, `flair-mcp` and the client library.
|
|
22
30
|
|
|
23
|
-
## 2. Bootstrap Flair
|
|
31
|
+
## 2. Bootstrap Flair and register an agent
|
|
24
32
|
|
|
25
33
|
```bash
|
|
26
|
-
flair init
|
|
34
|
+
flair init --agent local
|
|
27
35
|
```
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
> **Pass `--agent`.** A bare `flair init` bootstraps the instance and stops there — it prints `✅ Flair initialized (no agent registered)` and creates no keypair, no soul, and no MCP wiring.
|
|
38
|
+
|
|
39
|
+
First run does six things:
|
|
30
40
|
|
|
31
|
-
1. Installs the embedded Harper
|
|
32
|
-
2. Downloads the local embedding model
|
|
41
|
+
1. Installs the embedded Harper memory store into `~/.flair/data/`.
|
|
42
|
+
2. Downloads the local embedding model — about 80 MB, first run only.
|
|
33
43
|
3. Starts Flair as a launchd / systemd service on port 19926.
|
|
34
|
-
4.
|
|
35
|
-
5.
|
|
44
|
+
4. Generates the agent's Ed25519 keypair in `~/.flair/keys/` and registers it.
|
|
45
|
+
5. Wires every MCP client it detects — Claude Code, Cursor, Codex CLI, Gemini CLI — to `npx -y @tpsdev-ai/flair-mcp`, then smoke-tests it. `--no-mcp` skips this.
|
|
46
|
+
6. Opens a short **soul wizard** so your agent knows who it is.
|
|
47
|
+
|
|
48
|
+
It also generates a Harper admin password and writes it to `~/.flair/admin-pass` (mode 0600). The CLI prints that **path**, never the value — read the file when a command needs the password, and prefer `--admin-pass-file ~/.flair/admin-pass` over pasting it into a command line.
|
|
36
49
|
|
|
37
50
|
The soul wizard offers a few shapes:
|
|
38
51
|
|
|
@@ -49,7 +62,7 @@ The soul wizard offers a few shapes:
|
|
|
49
62
|
(s) Skip — set up later; `flair doctor` will nudge
|
|
50
63
|
```
|
|
51
64
|
|
|
52
|
-
Pick the template that matches how you'll use
|
|
65
|
+
Pick the template that matches how you'll use the agent. Edit or replace any entry later with `flair soul set`. The wizard is skipped automatically in a non-interactive shell, and by `--skip-soul`.
|
|
53
66
|
|
|
54
67
|
## 3. Confirm it's running
|
|
55
68
|
|
|
@@ -57,61 +70,68 @@ Pick the template that matches how you'll use this agent. You can edit or replac
|
|
|
57
70
|
flair status
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
A bare `flair status` (no agent, no admin credentials) only shows the public health
|
|
61
|
-
summary:
|
|
62
|
-
|
|
63
73
|
```
|
|
64
|
-
Flair vX.Y.Z —
|
|
65
|
-
URL
|
|
74
|
+
Flair vX.Y.Z — ✓ running PID 12345 · uptime 0m
|
|
75
|
+
URL http://127.0.0.1:19926
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
Memory
|
|
78
|
+
Total 0
|
|
79
|
+
Durability permanent:0 · persistent:0 · standard:0 · ephemeral:0
|
|
80
|
+
Archived 0
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
Agents
|
|
83
|
+
Total 1 — local
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
REM
|
|
86
|
+
Nightly disabled
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
flair status --agent local
|
|
76
|
-
```
|
|
88
|
+
Federation not configured
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
Flair vX.Y.Z — 🟢 running (PID 12345, uptime 1m)
|
|
80
|
-
URL: http://127.0.0.1:19926
|
|
90
|
+
Bridges none installed
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Agents:
|
|
87
|
-
1 total — local
|
|
88
|
-
|
|
89
|
-
Soul:
|
|
90
|
-
3 entries — 0 critical / 0 high / 3 standard / 0 low
|
|
92
|
+
Disk
|
|
93
|
+
Data ~/.flair/data — 0 B
|
|
94
|
+
Snapshots ~/.flair/snapshots — 0 B
|
|
91
95
|
|
|
92
96
|
✓ all checks passing
|
|
93
97
|
```
|
|
94
98
|
|
|
99
|
+
You will also see a **Soul** section if you completed the wizard in step 2.
|
|
100
|
+
|
|
101
|
+
`✓` means healthy. `⚠` means something is worth looking at, and the warning names the command that fixes it. For `✗ unreachable`, see [troubleshooting.md](troubleshooting.md).
|
|
102
|
+
|
|
95
103
|
## 4. Write your first memory
|
|
96
104
|
|
|
97
105
|
```bash
|
|
98
|
-
flair memory add --agent local
|
|
106
|
+
flair memory add --agent local "Harper v5 sandbox blocks node:module but process.dlopen works"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"id": "local-1785277247486",
|
|
112
|
+
"written": true,
|
|
113
|
+
"deduplicated": false
|
|
114
|
+
}
|
|
99
115
|
```
|
|
100
116
|
|
|
101
|
-
Flair
|
|
117
|
+
Flair embedded the text locally on write. No network calls.
|
|
102
118
|
|
|
103
119
|
## 5. Find it back by meaning
|
|
104
120
|
|
|
105
121
|
```bash
|
|
106
|
-
flair
|
|
122
|
+
flair search --agent local "native addon loading in sandboxed runtimes"
|
|
107
123
|
```
|
|
108
124
|
|
|
109
125
|
```
|
|
110
126
|
Harper v5 sandbox blocks node:module but process.dlopen works
|
|
111
|
-
(2026-
|
|
127
|
+
( 2026-07-28 · standard · 100% )
|
|
112
128
|
```
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
You searched for a concept, not the keywords. The line under each hit is its creation date, durability tier, and rank score.
|
|
131
|
+
|
|
132
|
+
> The percentage is a **rank-fusion score, not a similarity**. It is normalized so the top result is always near 100%. Read it as ordering within these results, never as confidence that the match is good.
|
|
133
|
+
|
|
134
|
+
Add `--explain` to see the ranking inputs, or `--limit`, `--tag`, `--since 7d` to narrow the search. `flair memory search` runs the same query but always prints raw JSON — use it when piping to a script.
|
|
115
135
|
|
|
116
136
|
## 6. Give your agent context on boot
|
|
117
137
|
|
|
@@ -119,35 +139,41 @@ flair memory search --agent local --q "native addon loading in sandboxed runtime
|
|
|
119
139
|
flair bootstrap --agent local --max-tokens 2000
|
|
120
140
|
```
|
|
121
141
|
|
|
122
|
-
|
|
142
|
+
```
|
|
143
|
+
## Recent Context
|
|
144
|
+
📝 Harper v5 sandbox blocks node:module but process.dlopen works (2026-07-28)
|
|
145
|
+
✓ budget 20/2000 tokens (1%) · ✓ 1 included · ✓ 0 truncated
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Soul entries and relevant memories, in one block sized to a token budget. Paste it into any LLM session — Claude Code, Codex, Cursor, an API call — to hand the agent its identity and memory in one shot.
|
|
123
149
|
|
|
124
|
-
|
|
150
|
+
Using Claude Code? Add this to your `CLAUDE.md`:
|
|
125
151
|
|
|
126
152
|
```
|
|
127
153
|
At the start of every session, run mcp__flair__bootstrap before responding.
|
|
128
154
|
```
|
|
129
155
|
|
|
130
|
-
With the MCP server wired up
|
|
156
|
+
With the MCP server wired up — `flair init` does this automatically for every client it detects — Claude Code runs bootstrap on every new session. See the [integration section in README.md](../README.md#integration).
|
|
131
157
|
|
|
132
158
|
## What's next
|
|
133
159
|
|
|
134
160
|
| You want to... | Go to |
|
|
135
161
|
|----------------|-------|
|
|
136
162
|
| Add more agents to the same instance | `flair agent add <id>` |
|
|
137
|
-
|
|
|
138
|
-
| Import memories from agentic-stack / Mem0 / etc. | [
|
|
139
|
-
| Sync memories across machines | [
|
|
163
|
+
| Keep a memory owner-only | `flair memory add --visibility private` — reads are otherwise open to every agent on the instance ([auth.md](auth.md)) |
|
|
164
|
+
| Import memories from agentic-stack / Mem0 / etc. | [bridges.md](bridges.md) |
|
|
165
|
+
| Sync memories across machines | [federation.md](federation.md) |
|
|
140
166
|
| Integrate with OpenClaw, Claude Code, Cursor | [README.md#integration](../README.md#integration) |
|
|
141
|
-
| Fix something that isn't working | [
|
|
142
|
-
| Upgrade to a new version | `flair upgrade
|
|
167
|
+
| Fix something that isn't working | `flair doctor`, then [troubleshooting.md](troubleshooting.md) |
|
|
168
|
+
| Upgrade to a new version | `flair upgrade` (restarts automatically) or [upgrade.md](upgrade.md) |
|
|
143
169
|
|
|
144
170
|
## If you change your mind
|
|
145
171
|
|
|
146
172
|
```bash
|
|
147
|
-
flair stop # stop the service
|
|
173
|
+
flair stop # stop the service, keep data
|
|
148
174
|
flair restart # restart
|
|
149
|
-
flair uninstall # remove the service
|
|
150
|
-
flair uninstall --purge # remove everything including data and keys
|
|
175
|
+
flair uninstall # remove the service, keep data and keys
|
|
176
|
+
flair uninstall --purge # remove everything, including data and keys
|
|
151
177
|
```
|
|
152
178
|
|
|
153
179
|
All reversible. Your memories aren't locked in.
|
package/docs/releasing.md
CHANGED
|
@@ -5,9 +5,9 @@ Flair publishes eight workspace packages to npm under `@tpsdev-ai/*`. Releases a
|
|
|
5
5
|
(no `NPM_TOKEN` lives anywhere) and submits each package to npm's **staging** area.
|
|
6
6
|
A maintainer then approves the staged tarballs on npmjs.com with 2FA to make them live.
|
|
7
7
|
|
|
8
|
-
> `flair-bench` is version-bumped and tagged in lockstep with the other 7,
|
|
9
|
-
>
|
|
10
|
-
>
|
|
8
|
+
> `flair-bench` is version-bumped and tagged in lockstep with the other 7, and stages in
|
|
9
|
+
> its own step in CI for [historical reasons](#flair-bench-bootstrap-one-time-done). That
|
|
10
|
+
> step is no longer allowed to fail: all eight packages must stage for a release to pass.
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
merge release PR ──▶ push tag v0.11.0 ──▶ CI stages all packages ──▶ npm staging
|
|
@@ -29,13 +29,39 @@ the maintainer's 2FA approval.
|
|
|
29
29
|
### Phase 1 — open the release PR
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
# promote ## Unreleased to the new version in CHANGELOG.md first, then:
|
|
33
32
|
./scripts/release.sh 0.11.0
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
This bumps every workspace package to the version, aligns
|
|
37
|
-
`bun.lock`, builds, tests, and opens a `release: v0.11.0` PR.
|
|
38
|
-
(CI green + K&S approval) the same as any other PR.
|
|
35
|
+
This assembles the changelog, bumps every workspace package to the version, aligns
|
|
36
|
+
internal deps, refreshes `bun.lock`, builds, tests, and opens a `release: v0.11.0` PR.
|
|
37
|
+
Review and merge it (CI green + K&S approval) the same as any other PR.
|
|
38
|
+
|
|
39
|
+
**Nothing about the version is bumped by hand.** The version is declared outside
|
|
40
|
+
`package.json` too — `packages/flair-bench/src/version.ts` holds it as a `TOOL_VERSION`
|
|
41
|
+
constant — and `release.sh` bumps every such site. It checks them *before* creating the
|
|
42
|
+
branch, so an out-of-sync tree aborts while nothing has been touched, and again after the
|
|
43
|
+
bump so a missed site can't be committed. The same check runs in CI
|
|
44
|
+
(`node scripts/check-version-sync.mjs`) and fails on any file outside the known set that
|
|
45
|
+
declares the release version, so a new version-bearing file is caught on the PR that adds
|
|
46
|
+
it. If you ever find yourself editing a version constant by hand during a release, that is
|
|
47
|
+
a bug in the script — add the file to `SOURCE_VERSION_FILES` in
|
|
48
|
+
`scripts/check-version-sync.mjs` and to the `git add` list in `scripts/release.sh`.
|
|
49
|
+
|
|
50
|
+
**The changelog is assembled, not hand-promoted.** Entries land during development as one
|
|
51
|
+
file per change under `.changelog/unreleased/` (flair#835 — a shared `[Unreleased]` block
|
|
52
|
+
conflicted on every concurrent PR, and resolving a conflict dismisses approvals). The
|
|
53
|
+
release script runs:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node scripts/changelog-fragments.mjs promote 0.11.0
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
which writes a `## [0.11.0] - <date>` section from the fragments — Keep a Changelog category
|
|
60
|
+
order, filename order within a category, entry bodies copied verbatim — and deletes the
|
|
61
|
+
fragment files. Released history above it is not touched. It **refuses** to run when the
|
|
62
|
+
fragment directory is empty (nothing to release) or when someone hand-wrote an entry into
|
|
63
|
+
`## [Unreleased]` that the step would otherwise silently overwrite. Preview any time with
|
|
64
|
+
`node scripts/changelog-fragments.mjs render`.
|
|
39
65
|
|
|
40
66
|
### Phase 2 — tag the release
|
|
41
67
|
|
|
@@ -51,9 +77,10 @@ workflow, which:
|
|
|
51
77
|
|
|
52
78
|
1. Resolves the version from the tag and validates it as semver.
|
|
53
79
|
2. Verifies the tagged commit is an ancestor of `main` (a tag can't ship un-merged code).
|
|
54
|
-
3. Verifies all
|
|
80
|
+
3. Verifies all 8 `package.json` files are at that version.
|
|
55
81
|
4. Builds every package.
|
|
56
|
-
5. Runs `npm stage publish` for each package in dependency order (flair-client first)
|
|
82
|
+
5. Runs `npm stage publish` for each package in dependency order (flair-client first),
|
|
83
|
+
then `flair-bench` in its own step. Any one of the eight failing fails the release.
|
|
57
84
|
|
|
58
85
|
It authenticates via OIDC — no secrets, and it does **not** create or move any tag (the
|
|
59
86
|
tag you pushed is the trigger). Watch the run; when it's green, the packages are staged
|
|
@@ -66,7 +93,8 @@ by `scripts/changelog-extract.mjs`). It is idempotent: re-running the workflow o
|
|
|
66
93
|
re-pushing the tag updates the existing release rather than failing. The GitHub release
|
|
67
94
|
documents the tagged commit immediately; it does not wait on the npm 2FA gate. If the
|
|
68
95
|
CHANGELOG has no section for the version, this job fails loudly rather than cutting an
|
|
69
|
-
empty release —
|
|
96
|
+
empty release — which is why phase 1's fragment assembly refuses to produce an empty
|
|
97
|
+
section rather than letting the failure surface here, after the tag is already pushed.
|
|
70
98
|
|
|
71
99
|
> `workflow_dispatch` with a `version` input remains as a manual fallback (needs
|
|
72
100
|
> `Actions: write`), but the tag push is the normal path.
|
|
@@ -82,10 +110,9 @@ npm stage view <stage-id> # inspect one
|
|
|
82
110
|
npm stage approve <stage-id> # 2FA prompt; package goes live
|
|
83
111
|
```
|
|
84
112
|
|
|
85
|
-
There are
|
|
113
|
+
There are eight lockstep-staged packages, so eight approvals (the web UI lists them on
|
|
86
114
|
one page). Approve in dependency order if installing immediately — flair-client before
|
|
87
|
-
its dependents — though staging does not itself resolve dependencies.
|
|
88
|
-
stages separately and only appears here once its bootstrap (below) is done.
|
|
115
|
+
its dependents — though staging does not itself resolve dependencies.
|
|
89
116
|
|
|
90
117
|
Verify when done:
|
|
91
118
|
|
|
@@ -99,7 +126,7 @@ These are configured once and reused for every release.
|
|
|
99
126
|
|
|
100
127
|
### npm trusted publisher (per package)
|
|
101
128
|
|
|
102
|
-
For **each** of the
|
|
129
|
+
For **each** of the eight packages, on npmjs.com → the package → **Settings → Trusted
|
|
103
130
|
Publisher → Add**:
|
|
104
131
|
|
|
105
132
|
| Field | Value |
|
|
@@ -116,32 +143,39 @@ CI/OIDC identity from publishing anything live directly — the only path to liv
|
|
|
116
143
|
human 2FA approval of a staged package.
|
|
117
144
|
|
|
118
145
|
Packages: `flair-client`, `flair-mcp`, `flair`, `openclaw-flair`, `pi-flair`,
|
|
119
|
-
`n8n-nodes-flair`, `langgraph-flair`.
|
|
146
|
+
`n8n-nodes-flair`, `langgraph-flair`, `flair-bench`.
|
|
120
147
|
|
|
121
148
|
> A package must already exist on npm before a trusted publisher can be added — all
|
|
122
|
-
>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
(`
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
> eight already do. This account-level config can only be done by an npm org owner.
|
|
150
|
+
|
|
151
|
+
### `flair-bench` bootstrap (one-time, done)
|
|
152
|
+
|
|
153
|
+
**Nothing to do here.** This section is kept because the next brand-new package added to
|
|
154
|
+
the release set will hit the same wall, and because it explains why `flair-bench` still
|
|
155
|
+
stages in its own workflow step.
|
|
156
|
+
|
|
157
|
+
`flair-bench` (added 2026-07-12, flair#702) was wired into the version-bump/tag flow
|
|
158
|
+
(`scripts/release.sh`, `release-publish.yml`'s version-check) alongside the other 7 before
|
|
159
|
+
it existed on npm at all. That is a chicken-and-egg: `npm stage publish` categorically
|
|
160
|
+
requires the package to already exist on the registry (`npm help stage`: "Package must
|
|
161
|
+
exist"), and a Trusted Publisher can only be registered for a package that already exists.
|
|
162
|
+
So the workflow gave it a dedicated "Stage-publish flair-bench" step marked
|
|
163
|
+
`continue-on-error: true`, letting the expected failure pass without blocking the other 7,
|
|
164
|
+
and an npm org owner broke the cycle once:
|
|
165
|
+
|
|
166
|
+
1. One normal (non-staged) `npm publish --access public` from a machine logged into npm
|
|
167
|
+
with 2FA, to create the package on the registry. Any valid semver works — the next
|
|
168
|
+
lockstep release bumps it to match the other 7 automatically.
|
|
169
|
+
2. Add its Trusted Publisher using the same table as the other packages above.
|
|
170
|
+
|
|
171
|
+
Both are done, and the step staged `@tpsdev-ai/flair-bench` successfully at v0.30.0, so
|
|
172
|
+
`continue-on-error` has been **removed**: a flair-bench staging failure now fails the
|
|
173
|
+
release like any other package's.
|
|
174
|
+
|
|
175
|
+
> Why it mattered to go back and remove it: `continue-on-error: true` makes a step report
|
|
176
|
+
> `conclusion: success` even when it failed. While it was set, a green run was not evidence
|
|
177
|
+
> that flair-bench had staged — the only way to know was to read the raw log. A justified
|
|
178
|
+
> exception outlives the condition that justified it unless someone returns for it.
|
|
145
179
|
|
|
146
180
|
### GitHub `release` environment
|
|
147
181
|
|
|
@@ -41,6 +41,9 @@ Override per-run via `FLAIR_DEP_KEEP_CURRENT="pkg1,pkg2,@scope/pkg3"` env (addit
|
|
|
41
41
|
Every `dependencies` entry in any `package.json` must be a single concrete version (`"5.0.9"`), not a range (`"^5.0"`, `"~5.0.9"`, `">=5"`). Range specifiers expose us to silent supply-chain swaps every install — exactly the surface attackers exploit.
|
|
42
42
|
|
|
43
43
|
- `peerDependencies` may use ranges (host-provided; never installed by us). `devDependencies` are also exact-pinned for build reproducibility, though they don't ship in our published tarballs.
|
|
44
|
+
- **Flair declares no optional peers today**, and no `optionalDependencies` at all. If either is ever proposed as an install-weight fix, the mechanism has now been measured twice (`@harperfast/oauth` flair#750, `node-llama-cpp` flair#887) and the result is counter-intuitive enough to be worth stating: **only `peerDependencies` + `peerDependenciesMeta.optional` is skipped by a default install** (npm and bun alike). A plain `optionalDependencies` entry *is* still installed by default — "optional" there means "a failed install is non-fatal", not "skipped" — so it buys no install-weight reduction whatsoever. Exact-pinning an optional peer (rather than ranging it) is also legitimate where the consuming code path is version-sensitive, so that an operator who installs a different version gets told the version they have is not the version that was tested.
|
|
45
|
+
|
|
46
|
+
Note the corollary, measured during flair#893: an optional peer that is simply *absent* installs silently — npm prints no warning at all — so it cannot be relied on to prompt anyone to install it. Anything a user must install for a feature to work needs to be documented, or detected and reported at runtime.
|
|
44
47
|
- `bun.lock` is committed and frozen-lockfile installed in CI. Any unintended dep drift fails the workspace-deps consistency gate.
|
|
45
48
|
- Pin updates happen via deliberate, test-gated PRs — never auto-merged. **Renovate is enabled** (`.github/renovate.json`) to *propose* these updates on a schedule, but it respects the bake-time cooldown (`minimumReleaseAge: "7 days"`, matching `FLAIR_DEP_MIN_AGE_DAYS`) and opens PRs only — `automerge` is off, so every bump flows through the full test suite + K&S review. Renovate uses `rangeStrategy: "pin"` so it proposes exact-version bumps (never re-widens to ranges) and shares the keep-current allow-list with `check-dep-ages.mjs`. Vulnerability alerts bypass the cooldown so security fixes aren't delayed.
|
|
46
49
|
|
|
@@ -69,6 +72,39 @@ Only Nathan publishes to npm (per the existing MFA boundary). Flint preps the re
|
|
|
69
72
|
- The build host is not logged into npm by design.
|
|
70
73
|
- A planned post-publish smoke job will add an automated round-trip check after each publish to ensure cross-package resolution works on the actually-published artifacts.
|
|
71
74
|
|
|
75
|
+
### 7. Dependency audit gate: blocking, with dated exceptions
|
|
76
|
+
|
|
77
|
+
`bun audit` runs on every PR via the `audit` job and **blocks merge**. Every advisory it reports must either be fixed or appear in `.github/audit-allowlist.json`. There is no third option and no global escape hatch.
|
|
78
|
+
|
|
79
|
+
This replaced a step that could not fail:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
run: bun audit || echo "::warning::Audit found vulnerabilities — all in harper transitive deps (unreleased v5 build)"
|
|
83
|
+
continue-on-error: true
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Both mechanisms independently forced a pass (`|| echo` made the shell exit 0, so `continue-on-error` was dead config that never even fired). The justification was accurate the day it was written. It carried **no expiry**, so it outlived its reason — by the time it was removed, "all in harper transitive deps" was false, and a critical advisory was reaching users through a first-party workspace package while the gate reported green.
|
|
87
|
+
|
|
88
|
+
**The defect was the unexpirable exception, not the exception.** Some advisories genuinely cannot be fixed from this repo. So each one is now enumerated with:
|
|
89
|
+
|
|
90
|
+
| field | meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `ghsa` | the published advisory id |
|
|
93
|
+
| `package`, `severity` | re-verified against `bun audit` on every run; drift fails the gate |
|
|
94
|
+
| `class` | `no-patch-published`, `vendor-pinned`, or `remediation-available` |
|
|
95
|
+
| `introducedBy` | the dependency edge that pulls it in |
|
|
96
|
+
| `reason` | why it cannot be fixed here, specifically |
|
|
97
|
+
| `added`, `expires` | hard dates; lifetime is capped by severity |
|
|
98
|
+
| `removeWhen` | the concrete condition that retires the entry |
|
|
99
|
+
|
|
100
|
+
`scripts/audit-gate.mjs` fails the build on: an unlisted advisory, a malformed entry, an entry parked beyond its severity cap (critical 30 days, high 60, moderate/low 180), an **expired** entry, a **stale** entry whose advisory no longer appears, or a `no-patch-published` entry for which a patched version has since shipped. That last pair matters — an allowlist that only ever grows is the same failure with more ceremony.
|
|
101
|
+
|
|
102
|
+
When an entry expires the build fails and a human re-decides. **That is the mechanism working, not a flaw.** Re-dating an entry without re-reading its reason is how this file rots back into the thing it replaced.
|
|
103
|
+
|
|
104
|
+
Run it locally with `node scripts/audit-gate.mjs --explain`.
|
|
105
|
+
|
|
106
|
+
**Standing rule for every check in this repo:** any check that is advisory-only must carry, in a comment at its own definition, what would make it blocking and when that gets re-evaluated. Advisory is a terminating state with a defined promotion event, never a resting place.
|
|
107
|
+
|
|
72
108
|
---
|
|
73
109
|
|
|
74
110
|
## Automation
|
package/docs/troubleshooting.md
CHANGED
|
@@ -173,6 +173,30 @@ flair doctor # check for issues
|
|
|
173
173
|
|
|
174
174
|
**Fix:** Either rephrase the content to avoid injection patterns, or switch to default mode (remove `FLAIR_CONTENT_SAFETY=strict` from environment). In default mode, flagged content is stored but tagged — not rejected.
|
|
175
175
|
|
|
176
|
+
### "no writable migration data directory" / migrations reported failed
|
|
177
|
+
|
|
178
|
+
**Symptoms:** `flair doctor` reports every migration `failed` with a "no writable migration data directory" reason, and the instance log carries a matching `[flair-migrations]` line naming each path it tried.
|
|
179
|
+
|
|
180
|
+
**Cause:** flair keeps its migration bookkeeping (`.migrations/state.json`, the single-flight lock, and risk-scoped snapshots) in a directory alongside the instance's data — by default `~/.flair/data`, falling back to Harper's root path. On a provisioned install (a service-managed unit, a container, a component deployment) the account the process runs as may have a home directory it cannot write to, and if none of the candidates is usable there is nowhere safe to snapshot before a migration writes.
|
|
181
|
+
|
|
182
|
+
**Fix:** point `FLAIR_MIGRATION_DATA_DIR` at a writable directory and restart, or make one of the paths named in the error writable by the account the instance runs as:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
export FLAIR_MIGRATION_DATA_DIR=/var/lib/flair # add to the service unit / launch script to persist
|
|
186
|
+
flair restart
|
|
187
|
+
flair doctor # migrations should now report completed
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Migrations are boot-keyed, so the next start runs the cycle and clears the finding. Nothing is lost while this is unresolved — a migration that cannot snapshot refuses to run rather than writing unprotected.
|
|
191
|
+
|
|
192
|
+
### "Migration boot cycle never fired on this instance"
|
|
193
|
+
|
|
194
|
+
**Symptoms:** `flair doctor` reports the boot cycle never fired, and `/HealthDetail` shows `migrations.cyclePhase: "idle"`.
|
|
195
|
+
|
|
196
|
+
**Cause:** the trigger that schedules the migration cycle registers itself at boot, so `idle` means the running build's `dist/resources/migration-boot.js` never loaded — usually a partial or interrupted install rather than a runtime failure.
|
|
197
|
+
|
|
198
|
+
**Fix:** reinstall flair (`npm install -g @tpsdev-ai/flair`) and restart, then check the instance log for `[flair-migrations]` lines, which name any error raised while the trigger was loading.
|
|
199
|
+
|
|
176
200
|
## Getting Help
|
|
177
201
|
|
|
178
202
|
```bash
|
package/docs/upgrade.md
CHANGED
|
@@ -50,13 +50,25 @@ actually running:
|
|
|
50
50
|
stage the new packages without bouncing the process yet (the old
|
|
51
51
|
opt-in `--restart` flag still parses but is now a no-op — restart is the
|
|
52
52
|
default).
|
|
53
|
+
- **The restart runs through the newly installed CLI** (flair#905), not the
|
|
54
|
+
process that did the installing. Only version N's own code knows how version
|
|
55
|
+
N starts — spawn arguments, required environment, config templates and the
|
|
56
|
+
Harper dependency's own package name are all things a release may change, and
|
|
57
|
+
the pre-swap process would get every one of them wrong the same silent way.
|
|
58
|
+
The output names the CLI it handed the restart to. If that CLI can't be found
|
|
59
|
+
or its version can't be confirmed, the upgrade says so and restarts in-process
|
|
60
|
+
rather than refusing to start anything.
|
|
53
61
|
- **Post-restart verification** (skip with `--no-verify`) confirms the
|
|
54
62
|
restarted instance answers `/Health`, that an authenticated request
|
|
55
63
|
round-trips, and that the reported running version matches what was just
|
|
56
64
|
installed.
|
|
57
|
-
- **On verification
|
|
58
|
-
previously-running `@tpsdev-ai/flair` version,
|
|
59
|
-
re-verifies — then exits nonzero with a clear report of
|
|
65
|
+
- **On a failed restart OR a failed verification**, `flair upgrade`
|
|
66
|
+
automatically reinstalls the previously-running `@tpsdev-ai/flair` version,
|
|
67
|
+
restarts again, and re-verifies — then exits nonzero with a clear report of
|
|
68
|
+
what failed. Until flair#905 the rollback was wired to the *verification* leg
|
|
69
|
+
only: an upgrade that installed new packages and then failed to start them
|
|
70
|
+
exited 1 and left the operator on the new version with nothing running, which
|
|
71
|
+
is the single outcome this transaction exists to prevent. If the
|
|
60
72
|
rollback itself fails verification, it says so loudly and points at the
|
|
61
73
|
concrete pre-upgrade snapshot path (see "Pre-upgrade snapshot" below)
|
|
62
74
|
instead of retrying in a loop — see [Downgrade](#downgrade) for the
|
|
@@ -233,6 +245,56 @@ dropped, never shown green. The sweep also needs Basic-auth credentials
|
|
|
233
245
|
`--fabric-user`/`--fabric-password`) to authenticate each peer probe; a token-only
|
|
234
246
|
(`--fabric-token`) deploy skips it with a note instead of a silent no-op.
|
|
235
247
|
|
|
248
|
+
### Peer-replication errors are not verdicts
|
|
249
|
+
|
|
250
|
+
Harper replicates a deployed component to its cluster peers **asynchronously**, so
|
|
251
|
+
`harper deploy` can exit non-zero with
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Component 'flair' was deployed on the origin node but failed to replicate to 1 of 1
|
|
255
|
+
peer node(s): <peer> (Error: Connection closed 1006)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
for a deploy that then converges on its own moments later. That error describes one
|
|
259
|
+
instant, not an outcome.
|
|
260
|
+
|
|
261
|
+
Both `flair deploy` and `flair upgrade --target` therefore **check before they
|
|
262
|
+
declare**. On that error the CLI reads the peer node names out of Harper's own
|
|
263
|
+
message, polls each node's component file tree (`get_components` — path, size and
|
|
264
|
+
mtime), and compares it against the origin's. If every named peer converged, the
|
|
265
|
+
deploy is reported as a **success**, with a line saying that Harper's error resolved:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
⚠ harper reported a peer-replication failure, but every named peer node's component
|
|
269
|
+
tree matched the origin when checked afterwards — replication converged on its own.
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Convergence is only ever claimed on positive evidence. A peer whose name is not an
|
|
273
|
+
addressable host, whose hostname does not resolve, which cannot be reached, which
|
|
274
|
+
reports no such component, or which currently resolves to the *same address as the
|
|
275
|
+
deploy target* (a Fabric cluster endpoint is steered to one member node, so that
|
|
276
|
+
comparison would be a node against itself) is reported as unverified — never as
|
|
277
|
+
converged.
|
|
278
|
+
|
|
279
|
+
| Flag | Effect |
|
|
280
|
+
|------|--------|
|
|
281
|
+
| `--convergence-timeout <ms>` | How long to wait for replication to converge before reporting failure (default `180000`) |
|
|
282
|
+
| `--no-convergence-check` | Skip the poll and fail on Harper's error verbatim |
|
|
283
|
+
| `--ignore-replication-errors` | Accept an origin-only deploy when replication has not converged; the peer catches up via federation sync or a later deploy |
|
|
284
|
+
| `--deploy-retries <n>` | Retry the full deploy when replication is *observed* not to have converged (default `0`) |
|
|
285
|
+
|
|
286
|
+
**`--deploy-retries` defaults to `0`, deliberately.** A retry re-runs the whole
|
|
287
|
+
deploy, including Harper's own `npm install` into the component directory on every
|
|
288
|
+
node, and a real upgrade died that way — `ENOTEMPTY` on a native module, on the
|
|
289
|
+
*retry*, for a deploy whose peers had already converged. Retrying was compensating
|
|
290
|
+
for the absence of a convergence check; the poll above covers that window without
|
|
291
|
+
touching the cluster. When retries are enabled they are gated twice: only on
|
|
292
|
+
positively observed non-convergence, and only once the origin's component tree has
|
|
293
|
+
stopped changing across consecutive reads. If a retry does still fail differently
|
|
294
|
+
from the original error, the CLI reports the **original** failure as the headline and
|
|
295
|
+
labels the later one as a consequence of retrying — a retry can never change what the
|
|
296
|
+
failure is reported to be.
|
|
297
|
+
|
|
236
298
|
## Re-embedding after an upgrade
|
|
237
299
|
|
|
238
300
|
Two situations require a re-embed pass, and `flair doctor` will flag both:
|
|
@@ -345,6 +407,39 @@ reason as above: the restart is performed by the *old*, already-installed CLI.
|
|
|
345
407
|
- If you already hit it (upgrade "finished" but the server is down): run
|
|
346
408
|
`flair start` — not `restart` — and you're on the new version. Nothing was lost.
|
|
347
409
|
|
|
410
|
+
### Known issue — upgrading *from* 0.29.0 stops with a false "Harper binary not found"
|
|
411
|
+
|
|
412
|
+
0.30.0 renamed its Harper dependency from `@harperfast/harper` to the bare
|
|
413
|
+
`harper` package (flair#870, a real ~104 MB saving — the two names are the same
|
|
414
|
+
engine and no package manager can dedupe across them). `flair upgrade` replaces
|
|
415
|
+
the package tree *while the CLI is executing out of it*, and 0.29.0's restart
|
|
416
|
+
step only ever probes the old name — so once the swap lands, the name it is
|
|
417
|
+
looking for is genuinely gone:
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
Restarting Flair...
|
|
421
|
+
Stopping...
|
|
422
|
+
Starting...
|
|
423
|
+
❌ restart failed: Harper binary not found. Run 'flair init' first.
|
|
424
|
+
Flair may be partially down. Check: flair doctor
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The error is false twice over. The install is complete and Harper is present
|
|
428
|
+
under its new name; and `flair init` is the wrong remedy — on an initialised
|
|
429
|
+
instance it is not what you want to reach for, and it could not have fixed a
|
|
430
|
+
missing binary anyway.
|
|
431
|
+
|
|
432
|
+
**Workaround when upgrading from 0.29.0:** run `flair start`. The new version is
|
|
433
|
+
installed and correct; only the old CLI's restart step failed. `flair status`
|
|
434
|
+
should then report 0.30.0 and healthy. Nothing was lost — the upgrade never
|
|
435
|
+
touches `~/.flair/data`.
|
|
436
|
+
|
|
437
|
+
Forward-only, for the same reason as the two issues above: the code that
|
|
438
|
+
performs the restart is the version you are upgrading *from*. From flair#905
|
|
439
|
+
onward the restart is handed to the newly installed CLI and the Harper package
|
|
440
|
+
name is read from the post-swap `package.json` instead of being compiled in, so
|
|
441
|
+
a future dependency rename cannot reproduce it.
|
|
442
|
+
|
|
348
443
|
## Downgrade
|
|
349
444
|
|
|
350
445
|
Rolling back the **package** (above) assumes the data on disk is fine — only the new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"packageManager": "bun@1.3.10",
|
|
5
5
|
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
6
6
|
"type": "module",
|
|
@@ -63,19 +63,9 @@
|
|
|
63
63
|
"harper-fabric-embeddings": "^0.5.0",
|
|
64
64
|
"jose": "6.2.2",
|
|
65
65
|
"js-yaml": "4.3.0",
|
|
66
|
-
"node-llama-cpp": "3.18.1",
|
|
67
66
|
"tar": "7.5.20",
|
|
68
67
|
"tweetnacl": "1.0.3"
|
|
69
68
|
},
|
|
70
|
-
"optionalDependencies": {
|
|
71
|
-
"@node-llama-cpp/linux-arm64": "3.18.1",
|
|
72
|
-
"@node-llama-cpp/linux-armv7l": "3.18.1",
|
|
73
|
-
"@node-llama-cpp/linux-x64": "3.18.1",
|
|
74
|
-
"@node-llama-cpp/mac-arm64-metal": "3.18.1",
|
|
75
|
-
"@node-llama-cpp/mac-x64": "3.18.1",
|
|
76
|
-
"@node-llama-cpp/win-arm64": "3.18.1",
|
|
77
|
-
"@node-llama-cpp/win-x64": "3.18.1"
|
|
78
|
-
},
|
|
79
69
|
"overrides": {
|
|
80
70
|
"react-native-fs": "npm:empty-npm-package@1.0.0"
|
|
81
71
|
},
|