@tpsdev-ai/flair 0.30.0 → 0.31.1

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 (60) hide show
  1. package/README.md +194 -377
  2. package/dist/cli.js +1434 -288
  3. package/dist/deploy.js +212 -24
  4. package/dist/fabric-upgrade.js +16 -1
  5. package/dist/federation/scheduler.js +500 -0
  6. package/dist/install/clients.js +111 -53
  7. package/dist/lib/mcp-spec.js +128 -0
  8. package/dist/lib/safe-snapshot-extract.js +231 -0
  9. package/dist/lib/scheduler-platform.js +128 -0
  10. package/dist/lib/xml-escape.js +54 -0
  11. package/dist/rem/scheduler.js +35 -87
  12. package/dist/rem/snapshot.js +13 -0
  13. package/dist/replication-convergence.js +505 -0
  14. package/dist/resources/AdminPrincipals.js +10 -1
  15. package/dist/resources/Agent.js +105 -11
  16. package/dist/resources/AgentSeed.js +10 -2
  17. package/dist/resources/MemoryBootstrap.js +7 -8
  18. package/dist/resources/MemoryUsage.js +18 -0
  19. package/dist/resources/Presence.js +8 -1
  20. package/dist/resources/SemanticSearch.js +17 -45
  21. package/dist/resources/abstention.js +1 -1
  22. package/dist/resources/agent-admin.js +149 -0
  23. package/dist/resources/agent-auth.js +98 -5
  24. package/dist/resources/auth-middleware.js +92 -19
  25. package/dist/resources/embeddings-boot.js +10 -12
  26. package/dist/resources/embeddings-provider.js +10 -7
  27. package/dist/resources/health.js +24 -19
  28. package/dist/resources/in-process.js +234 -0
  29. package/dist/resources/mcp-handler.js +14 -4
  30. package/dist/resources/mcp-tools.js +23 -17
  31. package/dist/resources/migration-boot.js +80 -10
  32. package/dist/resources/migrations/data-dir.js +205 -0
  33. package/dist/resources/migrations/progress.js +33 -0
  34. package/dist/resources/migrations/runner.js +29 -2
  35. package/dist/resources/migrations/state.js +13 -2
  36. package/dist/resources/models-dir.js +18 -9
  37. package/dist/resources/presence-internal.js +6 -1
  38. package/dist/resources/record-owner-guard.js +149 -0
  39. package/dist/resources/semantic-retrieval-core.js +5 -4
  40. package/dist/src/lib/scheduler-platform.js +128 -0
  41. package/dist/src/lib/xml-escape.js +54 -0
  42. package/dist/src/rem/scheduler.js +35 -87
  43. package/docs/deploying-on-fabric.md +267 -0
  44. package/docs/deployment.md +5 -0
  45. package/docs/embedding-in-a-harper-app.md +303 -0
  46. package/docs/federation.md +61 -4
  47. package/docs/integrations.md +3 -0
  48. package/docs/mcp-clients.md +16 -7
  49. package/docs/quickstart.md +80 -54
  50. package/docs/releasing.md +72 -38
  51. package/docs/supply-chain-policy.md +36 -0
  52. package/docs/troubleshooting.md +24 -0
  53. package/docs/upgrade.md +99 -4
  54. package/package.json +1 -11
  55. package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
  56. package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
  57. package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
  58. package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
  59. package/dist/resources/rerank-provider.js +0 -569
  60. package/docs/rerank-provisioning.md +0 -101
@@ -2,37 +2,50 @@
2
2
 
3
3
  From zero to a persistent agent memory in five minutes.
4
4
 
5
- ## 0. Prerequisite
5
+ ## 0. Prerequisites
6
6
 
7
- **Node.js 22 or newer.** That's it — no Docker, no database to install, no API keys. Flair runs in a single process with embeddings computed locally.
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 higher
10
+ node --version # v22.x.x or newer
11
11
  ```
12
12
 
13
- If you need Node 22: [nodejs.org](https://nodejs.org/) or `brew install node@24`.
13
+ Need it? [nodejs.org](https://nodejs.org/), `nvm install 22`, or `brew install node`.
14
14
 
15
- ## 1. Install (30 seconds)
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`, and the client library all come in under one install.
29
+ One install gives you `flair`, `flair-mcp` and the client library.
22
30
 
23
- ## 2. Bootstrap Flair (1–2 minutes)
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
- First run does a few things:
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 (memory store) into `~/.flair/data/`.
32
- 2. Downloads the local embedding model (~80 MB first run only).
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. Creates a default agent (`--agent-id local` unless you pass one).
35
- 5. Opens a short **soul wizard** so your agent knows who it is.
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 this agent. You can edit or replace any entry later with `flair soul set`.
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 — 🟢 running (PID 12345, uptime 1m)
65
- URL: http://127.0.0.1:19926
74
+ Flair vX.Y.Z — running PID 12345 · uptime 0m
75
+ URL http://127.0.0.1:19926
66
76
 
67
- ✓ all checks passing
68
- ```
77
+ Memory
78
+ Total 0
79
+ Durability permanent:0 · persistent:0 · standard:0 · ephemeral:0
80
+ Archived 0
69
81
 
70
- The **🟢** icon means everything is healthy. A **🟡** would mean there's something worth looking at — usually surfaced with a recommended command inline. See [docs/troubleshooting.md](troubleshooting.md) if you see **🔴 unreachable**.
82
+ Agents
83
+ Total 1 — local
71
84
 
72
- For the full picture — Memory, Agents, and Soul detail — pass the agent you just created:
85
+ REM
86
+ Nightly disabled
73
87
 
74
- ```bash
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
- Memory:
83
- Total: 0
84
- Durability: 0 permanent / 0 persistent / 0 standard / 0 ephemeral
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 --content "Harper v5 sandbox blocks node:module but process.dlopen works"
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 generates a semantic embedding locally and stores it. No network calls.
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 memory search --agent local --q "native addon loading in sandboxed runtimes"
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-04-22 · memory · 67%)
127
+ ( 2026-07-28 · standard · 100% )
112
128
  ```
113
129
 
114
- **You searched for a concept, not the keywords.** The 67% is the semantic-similarity score.
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
- This returns a formatted text block that includes the soul entries you just set plus recent/relevant memories. Paste that into any LLM session — Claude Code, Codex, Cursor, an Anthropic API call — to give the agent its identity + memory in one shot.
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
- If you use Claude Code, add this to your `CLAUDE.md`:
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 (see the [integration section in README.md](../README.md#integration)), Claude Code runs bootstrap automatically on every new session.
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
- | Let one agent read another's memories | `flair grant <from> <to>` ([docs/auth.md](auth.md)) |
138
- | Import memories from agentic-stack / Mem0 / etc. | [docs/bridges.md](bridges.md) |
139
- | Sync memories across machines | [docs/federation.md](federation.md) |
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 | [docs/troubleshooting.md](troubleshooting.md) |
142
- | Upgrade to a new version | `flair upgrade --restart` or [docs/upgrade.md](upgrade.md) |
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 (keeps data)
173
+ flair stop # stop the service, keep data
148
174
  flair restart # restart
149
- flair uninstall # remove the service (keeps data + keys)
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, but stages
9
- > in its own step in CI (allowed to fail) until its one-time bootstrap is done — see
10
- > [flair-bench bootstrap](#flair-bench-bootstrap-one-time) below.
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 internal deps, refreshes
37
- `bun.lock`, builds, tests, and opens a `release: v0.11.0` PR. Review and merge it
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 7 `package.json` files are at that version.
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 — so always promote `## [Unreleased]` to `## [X.Y.Z]` in the release PR.
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 seven lockstep-staged packages, so seven approvals (the web UI lists them on
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. `flair-bench`
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 seven packages, on npmjs.com → the package → **Settings → Trusted
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
- > seven already do. This account-level config can only be done by an npm org owner.
123
- > `flair-bench` doesn't exist on npm yet (verified via `npm view @tpsdev-ai/flair-bench`
124
- > 404 as of 2026-07-13) so it can't have a Trusted Publisher yet either — see below.
125
-
126
- ### `flair-bench` bootstrap (one-time)
127
-
128
- `flair-bench` (added 2026-07-12, flair#702) is wired into the version-bump/tag flow
129
- (`scripts/release.sh`, `release-publish.yml`'s version-check) alongside the other 7, but
130
- `npm stage publish` categorically requires the package already exist on the npm registry
131
- (`npm help stage`: "Package must exist"), and a Trusted Publisher can only be registered
132
- for a package that already exists chicken-and-egg for a brand-new package. Until an npm
133
- org owner does this once, the workflow's dedicated "Stage-publish flair-bench" step is
134
- expected to fail (it's `continue-on-error: true` so it doesn't block the other 7):
135
-
136
- 1. From a machine logged into npm with 2FA: `cd packages/flair-bench && npm run build &&
137
- npm publish --access public` one normal (non-staged) publish to create the package
138
- on the registry. Any valid semver works; the very next lockstep release will bump it
139
- to match the other 7 automatically (it's in `scripts/release.sh`'s `PACKAGES` array).
140
- 2. Add the Trusted Publisher for `@tpsdev-ai/flair-bench` using the same table as the
141
- other 7 above (org=`tpsdev-ai`, repo=`flair`, workflow=`release-publish.yml`,
142
- environment=`release`, allowed=`npm stage publish` only).
143
- 3. Remove `continue-on-error: true` from the "Stage-publish flair-bench" step in
144
- `release-publish.yml` once a tag push has staged it successfully.
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
@@ -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 failure**, `flair upgrade` automatically reinstalls the
58
- previously-running `@tpsdev-ai/flair` version, restarts again, and
59
- re-verifies — then exits nonzero with a clear report of what failed. If the
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
@@ -176,7 +188,7 @@ FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> \
176
188
 
177
189
  (or `--fabric-password-file <path>` instead of the `FABRIC_PASSWORD` env var — reads the
178
190
  password from a file, chmod 600). This resolves the target version (latest published
179
- `@tpsdev-ai/flair`, or pin one with `--version`), stages a clean deployable with the
191
+ `@tpsdev-ai/flair`, or pin one with `--flair-version`), stages a clean deployable with the
180
192
  required `harper` version pin applied (`--harper-version` to override),
181
193
  confirms the staged Harper build before deploying, then reuses `flair deploy` to push it
182
194
  and verifies the result. `--check` shows the version diff and plan without deploying
@@ -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.30.0",
3
+ "version": "0.31.1",
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
  },