@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
|
@@ -13,25 +13,25 @@
|
|
|
13
13
|
* No daemon code lives here — the scheduler invokes the shim, the shim
|
|
14
14
|
* invokes `flair rem nightly run-once`, the runner module does the work.
|
|
15
15
|
*/
|
|
16
|
-
import { existsSync,
|
|
16
|
+
import { existsSync, chmodSync, rmSync } from "node:fs";
|
|
17
17
|
import { resolve, dirname } from "node:path";
|
|
18
|
-
import { homedir
|
|
19
|
-
import { spawn
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { escapeXml } from "../lib/xml-escape.js";
|
|
22
|
+
import { detectPlatform as detectPlatformFor, spawnReport, readTemplate as readTemplateFrom, renderTemplateWith, writeFileWithDir, interpretActiveResult, describeLoadFailure as describeLoadFailureFor, STATUS_CHECK_TIMEOUT_MS, } from "../lib/scheduler-platform.js";
|
|
23
|
+
// Re-exported so this module's public surface is unchanged by the extraction
|
|
24
|
+
// into src/lib/scheduler-platform.ts (a second scheduler — `flair federation
|
|
25
|
+
// sync enable` — needs the identical launchctl/systemctl interpretation, and
|
|
26
|
+
// flair#850's lesson must have exactly one implementation).
|
|
27
|
+
export { interpretActiveResult };
|
|
21
28
|
export const SHIM_PATH_DEFAULT = resolve(homedir(), ".flair", "bin", "flair-rem-nightly");
|
|
22
29
|
export const LAUNCHD_PLIST_PATH = resolve(homedir(), "Library", "LaunchAgents", "dev.flair.rem.nightly.plist");
|
|
23
30
|
export const SYSTEMD_USER_DIR = resolve(homedir(), ".config", "systemd", "user");
|
|
24
31
|
export const SYSTEMD_TIMER_PATH = resolve(SYSTEMD_USER_DIR, "flair-rem-nightly.timer");
|
|
25
32
|
export const SYSTEMD_SERVICE_PATH = resolve(SYSTEMD_USER_DIR, "flair-rem-nightly.service");
|
|
26
33
|
function detectPlatform(override) {
|
|
27
|
-
|
|
28
|
-
return override;
|
|
29
|
-
const p = platform();
|
|
30
|
-
if (p === "darwin")
|
|
31
|
-
return "darwin";
|
|
32
|
-
if (p === "linux")
|
|
33
|
-
return "linux";
|
|
34
|
-
throw new Error(`unsupported platform for REM nightly scheduler: ${p} (only darwin and linux)`);
|
|
34
|
+
return detectPlatformFor("REM nightly scheduler", override);
|
|
35
35
|
}
|
|
36
36
|
function defaultTemplateRoot() {
|
|
37
37
|
// Templates live alongside dist/ in the published package and alongside
|
|
@@ -50,19 +50,30 @@ function defaultTemplateRoot() {
|
|
|
50
50
|
throw new Error(`unable to locate templates directory (looked in: ${candidates.join(", ")})`);
|
|
51
51
|
}
|
|
52
52
|
export function renderTemplate(text, subs) {
|
|
53
|
-
return text
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
return renderTemplateWith(text, { ...subs }, (v) => v);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* renderTemplate() for the launchd plist template specifically: substituted
|
|
57
|
+
* values are XML-escaped.
|
|
58
|
+
*
|
|
59
|
+
* A launchd plist is XML, so a substitution carrying `&`, `<`, `>`, `"` or
|
|
60
|
+
* `'` makes it malformed and `launchctl bootstrap` rejects it — the timer
|
|
61
|
+
* silently never registers. FLAIR_URL is the realistic carrier (a URL with
|
|
62
|
+
* more than one query parameter contains `&`), but HOME and SHIM_PATH are
|
|
63
|
+
* arbitrary paths and equally capable of it. HOUR/MINUTE and AGENT_ID are
|
|
64
|
+
* validated upstream in buildSubstitutions(), but they are escaped too
|
|
65
|
+
* rather than exempted — the point of a chokepoint is that no value gets to
|
|
66
|
+
* argue it is special.
|
|
67
|
+
*
|
|
68
|
+
* Deliberately NOT folded into renderTemplate(): the same substitutions are
|
|
69
|
+
* rendered into the systemd unit files and the shell shim, where XML
|
|
70
|
+
* escaping would be corruption rather than a fix.
|
|
71
|
+
*/
|
|
72
|
+
export function renderPlistTemplate(text, subs) {
|
|
73
|
+
return renderTemplateWith(text, { ...subs }, escapeXml);
|
|
59
74
|
}
|
|
60
75
|
export function readTemplate(rootDir, relativePath) {
|
|
61
|
-
|
|
62
|
-
if (!existsSync(full)) {
|
|
63
|
-
throw new Error(`template not found: ${full}`);
|
|
64
|
-
}
|
|
65
|
-
return readFileSync(full, "utf-8");
|
|
76
|
+
return readTemplateFrom(rootDir, relativePath);
|
|
66
77
|
}
|
|
67
78
|
/**
|
|
68
79
|
* Validates the hour:minute schedule. Throws on invalid input rather than
|
|
@@ -93,32 +104,6 @@ function buildSubstitutions(opts, shimPath, flairBin) {
|
|
|
93
104
|
MINUTE_PAD: String(opts.minute).padStart(2, "0"),
|
|
94
105
|
};
|
|
95
106
|
}
|
|
96
|
-
function writeFileWithDir(path, contents, mode = 0o600) {
|
|
97
|
-
const dir = dirname(path);
|
|
98
|
-
if (!existsSync(dir))
|
|
99
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
100
|
-
writeFileSync(path, contents, { mode });
|
|
101
|
-
}
|
|
102
|
-
// 30s ceiling on launchctl/systemctl invocations so a hung service manager
|
|
103
|
-
// can't block the CLI indefinitely. Sherlock #415 follow-up.
|
|
104
|
-
const SPAWN_TIMEOUT_MS = 30_000;
|
|
105
|
-
// Status-check spawns (launchctl print / systemctl is-active) return
|
|
106
|
-
// near-instantly when the service manager is reachable, and fail fast (no
|
|
107
|
-
// hang) when it isn't (e.g. "Failed to connect to bus"). A short ceiling
|
|
108
|
-
// keeps `flair rem nightly status` and the Health endpoint responsive even
|
|
109
|
-
// when the query is inconclusive.
|
|
110
|
-
const STATUS_CHECK_TIMEOUT_MS = 5_000;
|
|
111
|
-
function spawnReport(cmd, timeoutMs = SPAWN_TIMEOUT_MS) {
|
|
112
|
-
const r = spawnSync(cmd[0], cmd.slice(1), {
|
|
113
|
-
encoding: "buffer",
|
|
114
|
-
timeout: timeoutMs,
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
code: r.status,
|
|
118
|
-
stdout: r.stdout?.toString("utf-8") ?? "",
|
|
119
|
-
stderr: r.stderr?.toString("utf-8") ?? "",
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
107
|
/**
|
|
123
108
|
* Command to genuinely query the platform scheduler's active/loaded state
|
|
124
109
|
* for a given install (as opposed to the shape of `loadCommand`, which
|
|
@@ -131,37 +116,6 @@ function activeCheckCommand(plat) {
|
|
|
131
116
|
}
|
|
132
117
|
return ["systemctl", "--user", "is-active", "flair-rem-nightly.timer"];
|
|
133
118
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Interprets the result of `activeCheckCommand()`. Shared by the sync
|
|
136
|
-
* (CLI) and async (server) callers.
|
|
137
|
-
*
|
|
138
|
-
* - true — the service manager confirms the job is loaded/active.
|
|
139
|
-
* - false — confirmed NOT active. This includes the "no session bus" case
|
|
140
|
-
* (flair#850): when `systemctl --user` can't reach a bus, it fails
|
|
141
|
-
* before printing a status word ("Failed to connect to bus: No medium
|
|
142
|
-
* found") — but nothing CAN be running without a bus, so `false` is the
|
|
143
|
-
* honest answer, not "unknown".
|
|
144
|
-
* - null — genuinely inconclusive (the command itself couldn't run at
|
|
145
|
-
* all — e.g. the binary is missing — with no output to interpret).
|
|
146
|
-
*/
|
|
147
|
-
export function interpretActiveResult(plat, code, stdout, stderr) {
|
|
148
|
-
const noOutput = !stdout.trim() && !stderr.trim();
|
|
149
|
-
if (plat === "darwin") {
|
|
150
|
-
if (code === 0)
|
|
151
|
-
return true;
|
|
152
|
-
if (code === null && noOutput)
|
|
153
|
-
return null; // spawn itself failed — inconclusive
|
|
154
|
-
return false; // launchctl ran and reported not-loaded
|
|
155
|
-
}
|
|
156
|
-
const out = stdout.trim();
|
|
157
|
-
if (out === "active" || out === "activating")
|
|
158
|
-
return true;
|
|
159
|
-
if (out === "inactive" || out === "failed" || out === "unknown")
|
|
160
|
-
return false;
|
|
161
|
-
if (code === null && noOutput)
|
|
162
|
-
return null; // spawn itself failed — inconclusive
|
|
163
|
-
return false; // covers the no-bus case: empty stdout, nonzero/failed exit
|
|
164
|
-
}
|
|
165
119
|
/**
|
|
166
120
|
* Synchronous active-state check for CLI use (`flair rem nightly status`).
|
|
167
121
|
* Blocking is fine here — this is a one-shot process and the caller wants
|
|
@@ -220,13 +174,7 @@ export async function queryActiveStateAsync(plat, timeoutMs = STATUS_CHECK_TIMEO
|
|
|
220
174
|
* still has something to go on.
|
|
221
175
|
*/
|
|
222
176
|
export function describeLoadFailure(plat, loadResult) {
|
|
223
|
-
|
|
224
|
-
if (plat === "linux" && /failed to connect to bus/i.test(stderr)) {
|
|
225
|
-
return ("No systemd user session bus is available in this session (common over ssh without lingering, " +
|
|
226
|
-
"in containers, or under CI). Fix: enable lingering for this user — `loginctl enable-linger <user>` " +
|
|
227
|
-
"— then re-run `flair rem nightly enable`.");
|
|
228
|
-
}
|
|
229
|
-
return null;
|
|
177
|
+
return describeLoadFailureFor(plat, loadResult, "flair rem nightly enable");
|
|
230
178
|
}
|
|
231
179
|
/**
|
|
232
180
|
* Formats the `flair rem nightly enable` report from an `EnableResult`.
|
|
@@ -330,7 +278,7 @@ export function enableScheduler(opts) {
|
|
|
330
278
|
// 2. Write the scheduler entry.
|
|
331
279
|
if (plat === "darwin") {
|
|
332
280
|
const plistPath = opts.launchdPlistOverride ?? LAUNCHD_PLIST_PATH;
|
|
333
|
-
const plistContents =
|
|
281
|
+
const plistContents = renderPlistTemplate(readTemplate(templateRoot, "launchd/dev.flair.rem.nightly.plist.tmpl"), subs);
|
|
334
282
|
writeFileWithDir(plistPath, plistContents, 0o600);
|
|
335
283
|
const loadCommand = ["launchctl", "bootstrap", `gui/${process.getuid?.() ?? ""}`, plistPath];
|
|
336
284
|
let loadResult;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Deploying on Harper Fabric
|
|
2
|
+
|
|
3
|
+
Run Flair as a component on [Harper Fabric](https://www.harperdb.io/) — managed, hosted,
|
|
4
|
+
multi-region — instead of a local process you own. The hosted counterpart to
|
|
5
|
+
[deployment.md](deployment.md); two things define it: you *deploy* rather than install,
|
|
6
|
+
and **you get no shell on the node.**
|
|
7
|
+
|
|
8
|
+
## When to use it
|
|
9
|
+
|
|
10
|
+
| | Standalone local | Harper Fabric |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| You run | `flair init` | `flair deploy` |
|
|
13
|
+
| Shell on the node | yes | **no** |
|
|
14
|
+
| Multi-region / failover | no | yes |
|
|
15
|
+
| Upgrade | `flair upgrade` | `flair upgrade --target <url>` |
|
|
16
|
+
|
|
17
|
+
Choose Fabric for an always-on hub in more than one region. For a single instance on a VPS
|
|
18
|
+
you already have, [Remote Server](../README.md#remote-server) is simpler and keeps your shell.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
### 1. Deploy the component
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export FABRIC_USER=<admin> FABRIC_PASSWORD=<pass>
|
|
28
|
+
|
|
29
|
+
# Validate args and package layout without deploying
|
|
30
|
+
flair deploy --fabric-org <org> --fabric-cluster <cluster> --dry-run
|
|
31
|
+
|
|
32
|
+
flair deploy --fabric-org <org> --fabric-cluster <cluster>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Target defaults to `https://<cluster>.<org>.harperfabric.com`; override with `--target`.
|
|
36
|
+
|
|
37
|
+
Credentials go via the environment, not argv, so they stay out of `ps`. Use
|
|
38
|
+
`--fabric-password-file <path>` (mode `0600`) when scripting; inline flags leak to history.
|
|
39
|
+
|
|
40
|
+
Deploy verifies the served API (Harper reports success for a component serving
|
|
41
|
+
nothing; `--no-verify` opts out), waits 600s for replication (Harper's 120s default
|
|
42
|
+
aborts mid-replicate on Fabric), and polls for convergence before reporting one.
|
|
43
|
+
|
|
44
|
+
> `--fabric-token` is accepted but **fails** — `deploy_component` is Basic-auth only.
|
|
45
|
+
|
|
46
|
+
### 2. Provision
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
flair init --target https://<cluster>.<org>.harperfabric.com \
|
|
50
|
+
--ops-target <ops-url> \
|
|
51
|
+
--cluster-admin-user <user> --cluster-admin-pass <pass> \
|
|
52
|
+
--remote --force
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `--force` is required — this writes to a live instance.
|
|
56
|
+
- `--remote` marks it a federation **hub** and creates the `flair_pair_initiator` role;
|
|
57
|
+
without it, pairing later fails role-not-found.
|
|
58
|
+
- Generated admin password lands in `~/.tps/secrets/flair-fabric-hdb` (mode `0600`);
|
|
59
|
+
`--flair-admin-pass` to choose your own.
|
|
60
|
+
|
|
61
|
+
Run **once**, before serving multi-region traffic — see
|
|
62
|
+
[Multi-region](#multi-region-replication-vs-federation).
|
|
63
|
+
|
|
64
|
+
### 3. Verify
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -sf https://<cluster>.<org>.harperfabric.com/Health
|
|
68
|
+
flair fleet verify --target https://<cluster>.<org>.harperfabric.com
|
|
69
|
+
flair status --target https://<cluster>.<org>.harperfabric.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. Connect a client
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export FLAIR_URL=https://<cluster>.<org>.harperfabric.com
|
|
76
|
+
|
|
77
|
+
# Register an agent — --ops-target is required, see Ports below
|
|
78
|
+
flair agent add mybot --target "$FLAIR_URL" --ops-target <ops-url>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also set `FLAIR_PUBLIC_URL` to that URL in the component's environment — OAuth metadata
|
|
82
|
+
and A2A discovery advertise it, or clients see a loopback address.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Ports: the derivation trap
|
|
87
|
+
|
|
88
|
+
Locally, Flair serves data on `19926` and the ops API on `19925`. The CLI derives
|
|
89
|
+
**ops = data − 1** everywhere.
|
|
90
|
+
|
|
91
|
+
A managed endpoint is HTTPS on 443 with no port, so derivation produces **`:442`** —
|
|
92
|
+
where nothing answers:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# --target https://<cluster>.<org>.harperfabric.com → ops derived as :442 ✗
|
|
96
|
+
# --target https://<fabric-node>:19926/<instance> → ops derived as :19925 ✓
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Pass `--ops-target <url>` explicitly** (or set `FLAIR_OPS_TARGET`) on any command that
|
|
100
|
+
touches the ops API: `init --target`, `agent add --target`, `federation token --target`.
|
|
101
|
+
|
|
102
|
+
> **Gap — needs a Fabric account to verify.** This guide does not state the correct
|
|
103
|
+
> ops-API URL for a managed `*.harperfabric.com` instance, or whether the ops API is
|
|
104
|
+
> reachable remotely there at all. The derivation above is certain (read from source);
|
|
105
|
+
> the right value to pass is not.
|
|
106
|
+
|
|
107
|
+
Precedence: `--target` > `--url` > `FLAIR_TARGET` > `FLAIR_URL` > localhost. For ops:
|
|
108
|
+
`--ops-target` > `FLAIR_OPS_TARGET` > derived > localhost.
|
|
109
|
+
|
|
110
|
+
## Auth
|
|
111
|
+
|
|
112
|
+
Flair ships `authorizeLocal: false` — it only governs loopback, so it changes nothing
|
|
113
|
+
here; remote callers always need real credentials.
|
|
114
|
+
|
|
115
|
+
- Admin ops use Basic auth; agents use Ed25519 ([auth.md](auth.md)).
|
|
116
|
+
- `/Health` is public — your liveness check.
|
|
117
|
+
- Don't debug with raw `curl` — on Fabric the auth gate fires before the resource
|
|
118
|
+
handler, so a hand-rolled request 401s in a way that looks like a Flair bug and isn't.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Pairing a spoke to a hosted hub
|
|
123
|
+
|
|
124
|
+
No shell on the hub, so mint the token remotely rather than over `ssh`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# On any machine — note --ops-target, this hits the ops API
|
|
128
|
+
FLAIR_ADMIN_PASS=<hub-admin-password> flair federation token \
|
|
129
|
+
--target https://<cluster>.<org>.harperfabric.com \
|
|
130
|
+
--ops-target <ops-url> > ./pair-triple.json
|
|
131
|
+
|
|
132
|
+
# On the spoke
|
|
133
|
+
flair federation pair https://<cluster>.<org>.harperfabric.com \
|
|
134
|
+
--token-from ./pair-triple.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`federation token` takes `--admin-pass`, **not** `--admin-pass-file`. The triple holds a
|
|
138
|
+
one-time credential — delete it after pairing (60-min TTL, `--ttl`).
|
|
139
|
+
|
|
140
|
+
### Federation is push-only
|
|
141
|
+
|
|
142
|
+
**A spoke pushes up. It cannot pull down.** `POST /FederationSync` is one-directional per
|
|
143
|
+
call and there is no pull endpoint anywhere.
|
|
144
|
+
|
|
145
|
+
For both directions, each side pairs **as a spoke of the other**: two pairings, two
|
|
146
|
+
tokens, two syncs. No setting makes sync bidirectional.
|
|
147
|
+
|
|
148
|
+
Syncs `Memory`, `Soul`, `Agent`, `Relationship`. **`Presence` is not federated** — no
|
|
149
|
+
cross-spoke roster.
|
|
150
|
+
|
|
151
|
+
### Keeping a spoke synced
|
|
152
|
+
|
|
153
|
+
`flair federation sync` is one-shot; `watch` dies with its terminal. Use the scheduled
|
|
154
|
+
driver ([federation.md](federation.md#keeping-it-synced)):
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
flair federation sync enable --interval 300
|
|
158
|
+
flair federation sync status
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> **The driver is local-only.** It writes a launchd job or systemd timer **on the machine
|
|
162
|
+
> running the CLI**; `--target` just points that local job at a remote instance. You
|
|
163
|
+
> cannot install a driver on a Fabric node.
|
|
164
|
+
|
|
165
|
+
A periodic one-shot, not a supervised watcher; `--interval` is the latency knob.
|
|
166
|
+
`flair federation status` omits its driver verdict for a remote `--target`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Multi-region: replication vs federation
|
|
171
|
+
|
|
172
|
+
Fabric gives you N regional nodes running one component — **not** N Flair instances.
|
|
173
|
+
Instance identity lives in the `Instance` table (`schemas/federation.graphql`); it is a
|
|
174
|
+
Harper table, so **it replicates** — every node shares one Flair identity.
|
|
175
|
+
|
|
176
|
+
| Layer | Handles | Scope |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| Harper replication | your app across its own regions | intra-app, automatic |
|
|
179
|
+
| Flair federation | this app ↔ another Flair instance | instance-to-instance, you pair it |
|
|
180
|
+
|
|
181
|
+
**You do not federate your own regions to each other** — Harper already did. Use
|
|
182
|
+
`flair federation pair` only to reach a *separate* Flair instance.
|
|
183
|
+
|
|
184
|
+
One asymmetry: identity replicates, but `flair-instance.yaml` (port config, per data
|
|
185
|
+
directory) does not. A node reporting a different **port** is expected; a different
|
|
186
|
+
**instance id** is not.
|
|
187
|
+
|
|
188
|
+
Provision once, from one place, before serving multi-region traffic: step 2's
|
|
189
|
+
`flair init … --remote` seeds the identity row so it exists before any node needs it.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Operating it
|
|
194
|
+
|
|
195
|
+
### What works remotely
|
|
196
|
+
|
|
197
|
+
| Command | Gives you |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `GET /Health` | liveness, no auth |
|
|
200
|
+
| `flair status --target <url>` | subsystem rollups; the only byte counts available remotely |
|
|
201
|
+
| `flair quality --target <url>` | recall/coverage; halted-migration reasons |
|
|
202
|
+
| `flair fleet verify --target <url>` | health, auth, version across origin + Flair peers |
|
|
203
|
+
| `flair federation status\|verify\|reachability --target <url>` | peer table, sync recency, probes |
|
|
204
|
+
|
|
205
|
+
`fleet verify` exit codes: 1 origin failed, 2 peer version skew, 3 peer unverifiable.
|
|
206
|
+
|
|
207
|
+
> **A credential mismatch renders as an empty section, not an error.** `flair status`
|
|
208
|
+
> reads `/HealthDetail` with `FLAIR_ADMIN_PASS` / `HDB_ADMIN_PASSWORD` / a pinned agent
|
|
209
|
+
> key — **not** the `FABRIC_*` credentials `deploy` and `fleet verify` use. On failure it
|
|
210
|
+
> renders blank — a blank Disk section means "couldn't authenticate" as often as
|
|
211
|
+
> "nothing to report".
|
|
212
|
+
|
|
213
|
+
### What doesn't
|
|
214
|
+
|
|
215
|
+
**`flair doctor` takes no `--target`** — it hardcodes localhost, reads a local PID file
|
|
216
|
+
and shells out to `lsof`. The command you'd reach for when something breaks is unavailable
|
|
217
|
+
here. Unavailable too: `start`, `stop`, `restart`, `snapshot`, `reembed`, `rem`, `bridge`.
|
|
218
|
+
|
|
219
|
+
**Fabric's own cluster topology is invisible.** `fleet verify` sweeps *Flair's* federation
|
|
220
|
+
peer table, not Harper's cluster nodes — `cluster_status` is harper-pro-only and absent
|
|
221
|
+
from the OSS `harper` build. **`0 peers known` means "0 on file", never "0 exist."**
|
|
222
|
+
|
|
223
|
+
**There is no disk or quota telemetry.** `flair status` reports usage for two directories:
|
|
224
|
+
no free space, no total, no quota, no warning threshold, walk capped at six levels, no
|
|
225
|
+
per-component size. `system_information` is never called — an instance can hit its quota
|
|
226
|
+
with nothing saying so. The one indirect signal is a migration halting for space.
|
|
227
|
+
|
|
228
|
+
> **On `get_components`.** A source comment claims Harper excludes `node_modules`
|
|
229
|
+
> server-side. That is uncited and unverified, so this guide doesn't rely on it — and it
|
|
230
|
+
> wouldn't matter: Flair calls it only as a post-failure convergence oracle and discards
|
|
231
|
+
> the `size` field. Component disk usage is invisible structurally.
|
|
232
|
+
|
|
233
|
+
### Known hazard: unbounded npm cache
|
|
234
|
+
|
|
235
|
+
**Open — [flair#886](https://github.com/tpsdev-ai/flair/issues/886).** Every deploy runs a
|
|
236
|
+
server-side `npm install` using the node's default cache. npm never evicts it, so it
|
|
237
|
+
grows until it fills the quota.
|
|
238
|
+
|
|
239
|
+
No in-product mitigation: no cache flag, no alternate location, no cleanup. One install
|
|
240
|
+
per deploy bounds the *rate*, not the total. Harper consults `install_command` only when
|
|
241
|
+
`node_modules` is absent and `deploy_component` has no force-reinstall option, so the
|
|
242
|
+
obvious fix isn't available — and clearing the cache needs node access this shape
|
|
243
|
+
doesn't give you.
|
|
244
|
+
|
|
245
|
+
### Backup and rollback
|
|
246
|
+
|
|
247
|
+
`flair snapshot` is local-only. Back up before every upgrade — `flair backup` takes
|
|
248
|
+
**`--url`**, not `--target`:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
flair backup --url https://<cluster>.<org>.harperfabric.com \
|
|
252
|
+
--admin-pass-file <path> --output ./flair-backup.json
|
|
253
|
+
|
|
254
|
+
# --check shows the version diff and plan without deploying
|
|
255
|
+
flair upgrade --target https://<cluster>.<org>.harperfabric.com --check
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Full path: [upgrade.md](upgrade.md#upgrading-a-fabric-deployed-instance).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## See also
|
|
263
|
+
|
|
264
|
+
- [deployment.md](deployment.md) — the standalone local shape
|
|
265
|
+
- [upgrade.md](upgrade.md#upgrading-a-fabric-deployed-instance) — Fabric upgrades, fleet verify
|
|
266
|
+
- [federation.md](federation.md) — pairing, sync driver, conflict resolution
|
|
267
|
+
- [spoke-bringup.md](spoke-bringup.md) · [auth.md](auth.md) · [secrets-and-keys.md](secrets-and-keys.md)
|
package/docs/deployment.md
CHANGED
|
@@ -116,6 +116,11 @@ Note: embeddings run on CPU in Docker (no Metal acceleration). Performance is ac
|
|
|
116
116
|
|
|
117
117
|
Deploying to a Harper Fabric cluster is a different mechanism from the installs above — `flair deploy` pushes Flair as a cluster component instead of `npm install -g`. To upgrade an already-deployed Fabric instance in place, use `FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> flair upgrade --target <fabric-url>` (or `--fabric-password-file <path>` in place of the env var), not the local upgrade path. Inline `--fabric-user`/`--fabric-password` flags also work but are discouraged — both leak to shell history and `ps`. See [`docs/upgrade.md` — Upgrading a Fabric-deployed instance](upgrade.md#upgrading-a-fabric-deployed-instance) for the full walkthrough, including the automatic post-deploy fleet-convergence sweep.
|
|
118
118
|
|
|
119
|
+
For the hosted shape end to end — when to choose it, ports and auth against a managed
|
|
120
|
+
Fabric endpoint, pairing local spokes to a hosted hub, and what you can and cannot
|
|
121
|
+
observe without a shell on the node — see
|
|
122
|
+
[`docs/deploying-on-fabric.md`](deploying-on-fabric.md).
|
|
123
|
+
|
|
119
124
|
---
|
|
120
125
|
|
|
121
126
|
## Remote Access
|