@tpsdev-ai/flair 0.29.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.
Files changed (117) hide show
  1. package/README.md +194 -377
  2. package/dist/cli-shim.cjs +1 -1
  3. package/dist/cli.js +1567 -366
  4. package/dist/deploy.js +241 -43
  5. package/dist/fabric-upgrade.js +77 -24
  6. package/dist/federation/scheduler.js +500 -0
  7. package/dist/fleet-verify.js +3 -3
  8. package/dist/install/clients.js +111 -53
  9. package/dist/lib/mcp-enable.js +2 -2
  10. package/dist/lib/mcp-spec.js +128 -0
  11. package/dist/lib/safe-snapshot-extract.js +231 -0
  12. package/dist/lib/scheduler-platform.js +128 -0
  13. package/dist/lib/xml-escape.js +54 -0
  14. package/dist/rem/scheduler.js +212 -55
  15. package/dist/rem/snapshot.js +13 -0
  16. package/dist/replication-convergence.js +505 -0
  17. package/dist/resources/A2AAdapter.js +1 -1
  18. package/dist/resources/Admin.js +1 -1
  19. package/dist/resources/AdminConnectors.js +1 -1
  20. package/dist/resources/AdminDashboard.js +1 -1
  21. package/dist/resources/AdminIdp.js +1 -1
  22. package/dist/resources/AdminInstance.js +1 -1
  23. package/dist/resources/AdminMemory.js +1 -1
  24. package/dist/resources/AdminPrincipals.js +1 -1
  25. package/dist/resources/Agent.js +1 -1
  26. package/dist/resources/AgentCard.js +1 -1
  27. package/dist/resources/AgentSeed.js +1 -1
  28. package/dist/resources/AttentionQuery.js +1 -1
  29. package/dist/resources/Credential.js +1 -1
  30. package/dist/resources/Federation.js +1 -1
  31. package/dist/resources/Instance.js +1 -1
  32. package/dist/resources/Integration.js +1 -1
  33. package/dist/resources/MCPClientMetadata.js +1 -1
  34. package/dist/resources/Memory.js +1 -1
  35. package/dist/resources/MemoryBootstrap.js +8 -9
  36. package/dist/resources/MemoryCandidate.js +168 -0
  37. package/dist/resources/MemoryConsolidate.js +1 -1
  38. package/dist/resources/MemoryDedupStats.js +1 -1
  39. package/dist/resources/MemoryFeed.js +1 -1
  40. package/dist/resources/MemoryGrant.js +1 -1
  41. package/dist/resources/MemoryMaintenance.js +1 -1
  42. package/dist/resources/MemoryReflect.js +2 -2
  43. package/dist/resources/MemoryReindex.js +1 -1
  44. package/dist/resources/MemoryUsage.js +1 -1
  45. package/dist/resources/OAuth.js +1 -1
  46. package/dist/resources/OAuthClient.js +1 -1
  47. package/dist/resources/OrgEvent.js +1 -1
  48. package/dist/resources/OrgEventCatchup.js +1 -1
  49. package/dist/resources/OrgEventMaintenance.js +1 -1
  50. package/dist/resources/PairingToken.js +1 -1
  51. package/dist/resources/Peer.js +1 -1
  52. package/dist/resources/Presence.js +3 -3
  53. package/dist/resources/RecordUsage.js +1 -1
  54. package/dist/resources/Relationship.js +1 -1
  55. package/dist/resources/SemanticSearch.js +18 -46
  56. package/dist/resources/SkillScan.js +1 -1
  57. package/dist/resources/Soul.js +1 -1
  58. package/dist/resources/SoulFeed.js +1 -1
  59. package/dist/resources/WorkspaceLatest.js +1 -1
  60. package/dist/resources/WorkspaceState.js +1 -1
  61. package/dist/resources/XAA.js +1 -1
  62. package/dist/resources/a2a-url.js +1 -1
  63. package/dist/resources/abstention.js +1 -1
  64. package/dist/resources/agent-auth.js +1 -1
  65. package/dist/resources/agentcard-fields.js +1 -1
  66. package/dist/resources/auth-middleware.js +1 -1
  67. package/dist/resources/embeddings-boot.js +14 -16
  68. package/dist/resources/embeddings-provider.js +15 -12
  69. package/dist/resources/federation-cleanup.js +5 -5
  70. package/dist/resources/federation-nonce-store.js +5 -5
  71. package/dist/resources/health.js +53 -25
  72. package/dist/resources/in-process.js +225 -0
  73. package/dist/resources/instance-identity.js +1 -1
  74. package/dist/resources/mcp-client-metadata-fields.js +1 -1
  75. package/dist/resources/mcp-handler.js +1 -1
  76. package/dist/resources/mcp-oauth.js +2 -2
  77. package/dist/resources/mcp-tools.js +23 -17
  78. package/dist/resources/memory-read-scope.js +1 -1
  79. package/dist/resources/memory-reflect-lib.js +3 -3
  80. package/dist/resources/memory-visibility.js +3 -3
  81. package/dist/resources/migration-boot.js +81 -11
  82. package/dist/resources/migrations/data-dir.js +205 -0
  83. package/dist/resources/migrations/embedding-stamp.js +3 -3
  84. package/dist/resources/migrations/graph-heal.js +2 -2
  85. package/dist/resources/migrations/ledger.js +1 -1
  86. package/dist/resources/migrations/progress.js +33 -0
  87. package/dist/resources/migrations/registry.js +14 -5
  88. package/dist/resources/migrations/runner.js +93 -13
  89. package/dist/resources/migrations/state.js +13 -2
  90. package/dist/resources/migrations/synthetic-test-migration.js +1 -1
  91. package/dist/resources/migrations/visibility-backfill.js +232 -0
  92. package/dist/resources/models-dir.js +18 -9
  93. package/dist/resources/record-types.js +28 -0
  94. package/dist/resources/semantic-retrieval-core.js +6 -5
  95. package/dist/resources/usage-recording.js +1 -1
  96. package/dist/src/lib/scheduler-platform.js +128 -0
  97. package/dist/src/lib/xml-escape.js +54 -0
  98. package/dist/src/rem/scheduler.js +397 -0
  99. package/docs/deploying-on-fabric.md +267 -0
  100. package/docs/deployment.md +6 -0
  101. package/docs/embedding-in-a-harper-app.md +299 -0
  102. package/docs/federation.md +61 -4
  103. package/docs/integrations.md +3 -0
  104. package/docs/mcp-clients.md +16 -7
  105. package/docs/quickstart.md +80 -54
  106. package/docs/releasing.md +72 -38
  107. package/docs/supply-chain-policy.md +40 -4
  108. package/docs/troubleshooting.md +24 -0
  109. package/docs/upgrade.md +100 -5
  110. package/package.json +4 -14
  111. package/schemas/memory.graphql +9 -1
  112. package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
  113. package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
  114. package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
  115. package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
  116. package/dist/resources/rerank-provider.js +0 -569
  117. package/docs/rerank-provisioning.md +0 -101
@@ -19,13 +19,13 @@ We don't pull in any newly-published dep version for **at least 7 days** after i
19
19
 
20
20
  #### 1a. Keep-current allow-list
21
21
 
22
- Some deps are tightly coupled to Flair's runtime correctness — Harper bug fixes and security patches land in `@harperfast/harper`, embedding-pipeline fixes land in `harper-fabric-embeddings`. We accept the bake-time risk and pull these eagerly. Current allow-list:
22
+ Some deps are tightly coupled to Flair's runtime correctness — Harper bug fixes and security patches land in `harper`, embedding-pipeline fixes land in `harper-fabric-embeddings`. We accept the bake-time risk and pull these eagerly. Current allow-list:
23
23
 
24
24
  | Package | Why kept current |
25
25
  |---------|------------------|
26
- | `@harperfast/harper` | Foundational. Vector-index and HNSW correctness fixes land here; we want them ASAP. High-volume upstream, fast detection if compromised. |
26
+ | `harper` | Foundational. Vector-index and HNSW correctness fixes land here; we want them ASAP. High-volume upstream, fast detection if compromised. **Bare name, not `@harperfast/harper`** — the two are permanent lockstep publishes of the same source, and depending on the scoped one made every install materialise both copies (~213 MB), because `@harperfast/oauth` peers on the bare name and npm cannot dedupe across two names. Aligned in flair#870. |
27
27
  | `harper-fabric-embeddings` | Embedding model loader. Coupled to Harper version. Same trust-and-volume reasoning. |
28
- | `@harperfast/oauth` | Same high-trust `@harperfast/*` owner as `@harperfast/harper`. Used ONLY by the **default-OFF** native-MCP OAuth surface (`FLAIR_MCP_OAUTH`), which dynamically imports it only when the flag is on — it is **not loaded in the shipped default build**, so bake-time exposure is zero until an operator explicitly opts in. Pinned to the exact version whose `withMCPAuth` API the surface was built against; the API surface (not a floating range) is what we depend on. |
28
+ | `@harperfast/oauth` | Same high-trust upstream owner as `harper`. Used ONLY by the **default-OFF** native-MCP OAuth surface (`FLAIR_MCP_OAUTH`), which dynamically imports it only when the flag is on — it is **not loaded in the shipped default build**, so bake-time exposure is zero until an operator explicitly opts in. Pinned to the exact version whose `withMCPAuth` API the surface was built against; the API surface (not a floating range) is what we depend on. |
29
29
 
30
30
  Adding to this list is a deliberate decision. The bar:
31
31
  - The upstream is well-known and high-volume (gets eyeballs fast).
@@ -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,13 +72,46 @@ 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
75
111
 
76
112
  ### `.github/renovate.json` — deliberate, cooldown-gated update proposals
77
113
 
78
- Renovate opens PRs to propose dependency updates so we don't drift behind upstream indefinitely — but on our terms, not the registry's. It is configured to never auto-merge (`automerge: false`), to pin (`rangeStrategy: "pin"`, consistent with §2), and to respect the bake-time cooldown (`minimumReleaseAge: "7 days"`, matching `FLAIR_DEP_MIN_AGE_DAYS` in `check-dep-ages.mjs`) so it only proposes versions that have already cleared the detection window. Non-major updates are grouped; majors land as isolated PRs. The keep-current allow-list (`@harperfast/harper`, `harper-fabric-embeddings`, `@harperfast/oauth`) mirrors the script's `DEFAULT_KEEP_CURRENT` — keep the two in lockstep when either changes. Vulnerability alerts bypass the cooldown. Every Renovate PR still runs the full CI suite (including the bake-time and workspace-deps gates) and is K&S-reviewed before merge.
114
+ Renovate opens PRs to propose dependency updates so we don't drift behind upstream indefinitely — but on our terms, not the registry's. It is configured to never auto-merge (`automerge: false`), to pin (`rangeStrategy: "pin"`, consistent with §2), and to respect the bake-time cooldown (`minimumReleaseAge: "7 days"`, matching `FLAIR_DEP_MIN_AGE_DAYS` in `check-dep-ages.mjs`) so it only proposes versions that have already cleared the detection window. Non-major updates are grouped; majors land as isolated PRs. The keep-current allow-list (`harper`, `harper-fabric-embeddings`, `@harperfast/oauth`) mirrors the script's `DEFAULT_KEEP_CURRENT` — keep the two in lockstep when either changes. Vulnerability alerts bypass the cooldown. Every Renovate PR still runs the full CI suite (including the bake-time and workspace-deps gates) and is K&S-reviewed before merge.
79
115
 
80
116
  ### `scripts/check-workspace-deps.mjs` (already shipped, PR #368)
81
117
 
@@ -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
@@ -177,7 +189,7 @@ FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> \
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
191
  `@tpsdev-ai/flair`, or pin one with `--version`), stages a clean deployable with the
180
- required `@harperfast/harper` version pin applied (`--harper-version` to override),
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
183
195
  anything; `--yes` skips the confirmation prompt for scripted use.
@@ -223,7 +235,7 @@ Exit codes:
223
235
  *Flair's own* federation peer table, not Harper Fabric's own cluster-replication
224
236
  nodes. Harper's `cluster_status` operation (the one that would answer "what nodes are
225
237
  in this cluster and are they in sync") is harper-pro-only and unavailable in the OSS
226
- `@harperfast/harper` build this CLI ships — there is no way for this CLI to enumerate
238
+ `harper` build this CLI ships — there is no way for this CLI to enumerate
227
239
  Fabric's own replication topology, on the origin or anywhere else. A Fabric replica
228
240
  that was never separately paired as a Flair federation peer (`flair federation pair`)
229
241
  is invisible to this sweep: `0 peers known` means "0 peers on file," never "0 peers
@@ -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.29.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",
@@ -56,26 +56,16 @@
56
56
  "node": ">=22"
57
57
  },
58
58
  "dependencies": {
59
- "@harperfast/harper": "5.1.22",
60
59
  "@harperfast/oauth": "2.4.0",
61
60
  "@types/js-yaml": "4.0.9",
62
61
  "commander": "14.0.3",
62
+ "harper": "5.1.22",
63
63
  "harper-fabric-embeddings": "^0.5.0",
64
64
  "jose": "6.2.2",
65
- "js-yaml": "4.1.1",
66
- "node-llama-cpp": "3.18.1",
67
- "tar": "7.5.13",
65
+ "js-yaml": "4.3.0",
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
  },
@@ -188,7 +188,15 @@ type MemoryUsage @table(database: "flair") {
188
188
  # that requires both --rationale and --to (soul|memory). Rejected candidates
189
189
  # retain their decision history so recurring false-positives become visible
190
190
  # via the `supersedes` chain.
191
- type MemoryCandidate @table(database: "flair") {
191
+ #
192
+ # flair#849: this type shipped WITHOUT @export, so it had no REST surface at
193
+ # all — `flair rem candidates`/`promote`/`reject` 404'd on every call, a
194
+ # clean-room dogfood blocker. @export alone would have reopened the exact P0
195
+ # leak the memory-soul-read-gate family fix closed for every other
196
+ # agent-facing table (anonymous read/write of every row); see
197
+ # resources/MemoryCandidate.ts for the paired identity-gated, owner-scoped
198
+ # resource class that makes @export here safe.
199
+ type MemoryCandidate @table(database: "flair") @export {
192
200
  id: ID @primaryKey
193
201
  agentId: String! @indexed
194
202
  claim: String! # distilled lesson text
@@ -0,0 +1,28 @@
1
+ #!/bin/sh
2
+ # Flair federation sync driver shim.
3
+ # Deployed by `flair federation sync enable` to {{SHIM_PATH}}.
4
+ # Invoked by launchd (macOS) or systemd (Linux) every {{INTERVAL_SECONDS}}s.
5
+ #
6
+ # One-shot by design: every run is a fresh, fully bounded process. See
7
+ # src/federation/scheduler.ts for why this is a periodic one-shot rather than
8
+ # a supervised long-lived watcher.
9
+ #
10
+ # The admin password is NEVER written into the scheduler unit. The unit
11
+ # carries FLAIR_ADMIN_PASS_FILE — a PATH — and the CLI reads the file itself
12
+ # through readAdminPassFileSecure(), which refuses anything not owner-only.
13
+ # Passing the path (not the value) also keeps it out of `ps`.
14
+ #
15
+ # Logs land in {{HOME}}/.flair/logs/federation-sync.{stdout,stderr}.log.
16
+ set -e
17
+
18
+ if [ -n "${FLAIR_TARGET:-}" ]; then
19
+ set -- --target "${FLAIR_TARGET}"
20
+ else
21
+ set --
22
+ fi
23
+
24
+ if [ -n "${FLAIR_ADMIN_PASS_FILE:-}" ] && [ -f "${FLAIR_ADMIN_PASS_FILE}" ]; then
25
+ set -- "$@" --admin-pass-file "${FLAIR_ADMIN_PASS_FILE}"
26
+ fi
27
+
28
+ exec "{{FLAIR_BIN}}" federation sync "$@"
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4
+ <plist version="1.0">
5
+ <dict>
6
+ <key>Label</key>
7
+ <string>dev.flair.federation.sync</string>
8
+
9
+ <key>ProgramArguments</key>
10
+ <array>
11
+ <string>{{SHIM_PATH}}</string>
12
+ </array>
13
+
14
+ <!-- Periodic one-shot. NOT KeepAlive: the sync carries no state between
15
+ runs, and KeepAlive restarts a process that exits while doing nothing
16
+ for one that hangs. See src/federation/scheduler.ts. -->
17
+ <key>StartInterval</key>
18
+ <integer>{{INTERVAL_SECONDS}}</integer>
19
+
20
+ <!-- Sync once as soon as the job is loaded, so `enable` visibly works
21
+ instead of leaving the operator waiting out a whole interval. -->
22
+ <key>RunAtLoad</key>
23
+ <true/>
24
+
25
+ <key>StandardOutPath</key>
26
+ <string>{{HOME}}/.flair/logs/federation-sync.stdout.log</string>
27
+
28
+ <key>StandardErrorPath</key>
29
+ <string>{{HOME}}/.flair/logs/federation-sync.stderr.log</string>
30
+
31
+ <key>WorkingDirectory</key>
32
+ <string>{{HOME}}</string>
33
+
34
+ <key>EnvironmentVariables</key>
35
+ <dict>
36
+ <key>HOME</key>
37
+ <string>{{HOME}}</string>
38
+ <key>PATH</key>
39
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
40
+ <!-- A PATH to the password file, never the password. -->
41
+ <key>FLAIR_ADMIN_PASS_FILE</key>
42
+ <string>{{ADMIN_PASS_FILE}}</string>
43
+ <key>FLAIR_TARGET</key>
44
+ <string>{{FLAIR_TARGET}}</string>
45
+ </dict>
46
+ </dict>
47
+ </plist>
@@ -0,0 +1,21 @@
1
+ [Unit]
2
+ Description=Flair federation sync (one-shot, driven by flair-federation-sync.timer)
3
+ After=network-online.target
4
+
5
+ [Service]
6
+ # Type=oneshot, not a Restart=always long-lived watcher: the sync carries no
7
+ # state between runs, and every network call inside it is bounded by its own
8
+ # timeout, so a run always terminates and the timer always gets to fire the
9
+ # next one. See src/federation/scheduler.ts.
10
+ Type=oneshot
11
+ ExecStart={{SHIM_PATH}}
12
+ # A PATH to the password file, never the password.
13
+ Environment=FLAIR_ADMIN_PASS_FILE={{ADMIN_PASS_FILE}}
14
+ Environment=FLAIR_TARGET={{FLAIR_TARGET}}
15
+ StandardOutput=append:{{HOME}}/.flair/logs/federation-sync.stdout.log
16
+ StandardError=append:{{HOME}}/.flair/logs/federation-sync.stderr.log
17
+
18
+ # No [Install] section: this unit is started by flair-federation-sync.timer,
19
+ # which is what `flair federation sync enable` enables. A WantedBy here would
20
+ # only invite someone to enable the service directly and get exactly one sync
21
+ # per boot.
@@ -0,0 +1,16 @@
1
+ [Unit]
2
+ Description=Flair federation sync timer (every {{INTERVAL_SECONDS}}s)
3
+
4
+ [Timer]
5
+ # Sync shortly after the timer starts (enable, and every boot thereafter), so
6
+ # `enable` visibly works instead of leaving the operator waiting out a whole
7
+ # interval. The launchd side gets this from RunAtLoad.
8
+ OnActiveSec=10s
9
+ # Then repeat, measured from the END of the previous run — a slow sync delays
10
+ # the next one rather than overlapping with it.
11
+ OnUnitActiveSec={{INTERVAL_SECONDS}}s
12
+ AccuracySec=30s
13
+ Unit=flair-federation-sync.service
14
+
15
+ [Install]
16
+ WantedBy=timers.target