@tpsdev-ai/flair 0.29.0 → 0.30.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 (85) hide show
  1. package/dist/cli-shim.cjs +1 -1
  2. package/dist/cli.js +231 -104
  3. package/dist/deploy.js +29 -19
  4. package/dist/fabric-upgrade.js +61 -23
  5. package/dist/fleet-verify.js +3 -3
  6. package/dist/lib/mcp-enable.js +2 -2
  7. package/dist/rem/scheduler.js +226 -17
  8. package/dist/resources/A2AAdapter.js +1 -1
  9. package/dist/resources/Admin.js +1 -1
  10. package/dist/resources/AdminConnectors.js +1 -1
  11. package/dist/resources/AdminDashboard.js +1 -1
  12. package/dist/resources/AdminIdp.js +1 -1
  13. package/dist/resources/AdminInstance.js +1 -1
  14. package/dist/resources/AdminMemory.js +1 -1
  15. package/dist/resources/AdminPrincipals.js +1 -1
  16. package/dist/resources/Agent.js +1 -1
  17. package/dist/resources/AgentCard.js +1 -1
  18. package/dist/resources/AgentSeed.js +1 -1
  19. package/dist/resources/AttentionQuery.js +1 -1
  20. package/dist/resources/Credential.js +1 -1
  21. package/dist/resources/Federation.js +1 -1
  22. package/dist/resources/Instance.js +1 -1
  23. package/dist/resources/Integration.js +1 -1
  24. package/dist/resources/MCPClientMetadata.js +1 -1
  25. package/dist/resources/Memory.js +1 -1
  26. package/dist/resources/MemoryBootstrap.js +1 -1
  27. package/dist/resources/MemoryCandidate.js +168 -0
  28. package/dist/resources/MemoryConsolidate.js +1 -1
  29. package/dist/resources/MemoryDedupStats.js +1 -1
  30. package/dist/resources/MemoryFeed.js +1 -1
  31. package/dist/resources/MemoryGrant.js +1 -1
  32. package/dist/resources/MemoryMaintenance.js +1 -1
  33. package/dist/resources/MemoryReflect.js +2 -2
  34. package/dist/resources/MemoryReindex.js +1 -1
  35. package/dist/resources/MemoryUsage.js +1 -1
  36. package/dist/resources/OAuth.js +1 -1
  37. package/dist/resources/OAuthClient.js +1 -1
  38. package/dist/resources/OrgEvent.js +1 -1
  39. package/dist/resources/OrgEventCatchup.js +1 -1
  40. package/dist/resources/OrgEventMaintenance.js +1 -1
  41. package/dist/resources/PairingToken.js +1 -1
  42. package/dist/resources/Peer.js +1 -1
  43. package/dist/resources/Presence.js +3 -3
  44. package/dist/resources/RecordUsage.js +1 -1
  45. package/dist/resources/Relationship.js +1 -1
  46. package/dist/resources/SemanticSearch.js +1 -1
  47. package/dist/resources/SkillScan.js +1 -1
  48. package/dist/resources/Soul.js +1 -1
  49. package/dist/resources/SoulFeed.js +1 -1
  50. package/dist/resources/WorkspaceLatest.js +1 -1
  51. package/dist/resources/WorkspaceState.js +1 -1
  52. package/dist/resources/XAA.js +1 -1
  53. package/dist/resources/a2a-url.js +1 -1
  54. package/dist/resources/agent-auth.js +1 -1
  55. package/dist/resources/agentcard-fields.js +1 -1
  56. package/dist/resources/auth-middleware.js +1 -1
  57. package/dist/resources/embeddings-boot.js +4 -4
  58. package/dist/resources/embeddings-provider.js +5 -5
  59. package/dist/resources/federation-cleanup.js +5 -5
  60. package/dist/resources/federation-nonce-store.js +5 -5
  61. package/dist/resources/health.js +29 -6
  62. package/dist/resources/instance-identity.js +1 -1
  63. package/dist/resources/mcp-client-metadata-fields.js +1 -1
  64. package/dist/resources/mcp-handler.js +1 -1
  65. package/dist/resources/mcp-oauth.js +2 -2
  66. package/dist/resources/memory-read-scope.js +1 -1
  67. package/dist/resources/memory-reflect-lib.js +3 -3
  68. package/dist/resources/memory-visibility.js +3 -3
  69. package/dist/resources/migration-boot.js +1 -1
  70. package/dist/resources/migrations/embedding-stamp.js +3 -3
  71. package/dist/resources/migrations/graph-heal.js +2 -2
  72. package/dist/resources/migrations/ledger.js +1 -1
  73. package/dist/resources/migrations/registry.js +14 -5
  74. package/dist/resources/migrations/runner.js +64 -11
  75. package/dist/resources/migrations/synthetic-test-migration.js +1 -1
  76. package/dist/resources/migrations/visibility-backfill.js +232 -0
  77. package/dist/resources/record-types.js +28 -0
  78. package/dist/resources/semantic-retrieval-core.js +1 -1
  79. package/dist/resources/usage-recording.js +1 -1
  80. package/dist/src/rem/scheduler.js +449 -0
  81. package/docs/deployment.md +1 -0
  82. package/docs/supply-chain-policy.md +4 -4
  83. package/docs/upgrade.md +2 -2
  84. package/package.json +4 -4
  85. package/schemas/memory.graphql +9 -1
@@ -0,0 +1,232 @@
1
+ /**
2
+ * visibility-backfill.ts — backfills the `visibility` field on Memory rows
3
+ * written before flair#509 (the durability-keyed default-visibility slice),
4
+ * which never stamped `visibility` at all. riskClass 'derived-only':
5
+ * `visibility` here is a derived stamp recomputable at any time from
6
+ * `durability` (which remains on the row, untouched, forever) — never the
7
+ * only copy of anything (invariant I) — and this migration only ever writes
8
+ * into a field that is CURRENTLY EMPTY, so the cheapest posture applies:
9
+ * metadata-only snapshot, no content-hash gate (row-count + stamp
10
+ * convergence only, same as `embedding-stamp`/`graph-heal`).
11
+ *
12
+ * WHY THIS EXISTS: `resources/Memory.ts`'s `defaultVisibilityForDurability`
13
+ * (flair#509) only stamps `visibility` on a WRITE — a row that has sat
14
+ * untouched since before that slice shipped has no `visibility` field at
15
+ * all. `src/cli.ts`'s federation push filter (`isFederationPrivateVisibility`)
16
+ * excludes only `visibility === "private"` — by design, per
17
+ * `resources/memory-visibility.ts`'s migration invariant, an ABSENT
18
+ * `visibility` field is NOT private and must keep syncing exactly as
19
+ * before (so today's single-org fleet never silently stops replicating
20
+ * pre-#509 rows). That is correct while every federation peer IS this same
21
+ * org's own fleet. A cross-organization hub pairing is coming, and at that
22
+ * point an absent-visibility row that should have been private (a
23
+ * `standard`/`ephemeral` note, never intended to leave the instance) would
24
+ * cross an org boundary it never should. This migration closes that gap
25
+ * proactively: once every row carries an explicit `visibility`, "absent"
26
+ * becomes the empty set, and the existing filter (unchanged, zero new
27
+ * behavior in it) is correct as-is for the cross-org case too.
28
+ *
29
+ * THE RULE (flair#509 — reused, not reinvented): `permanent`/`persistent`
30
+ * durability -> `shared`; `standard`/`ephemeral` durability, OR durability
31
+ * absent/unrecognised -> `private` (fail-safe: never widen access on a row
32
+ * whose intent this migration cannot actually determine).
33
+ * `deriveVisibilityFromDurability` below mirrors
34
+ * `resources/Memory.ts`'s (unexported) `defaultVisibilityForDurability`
35
+ * byte-for-byte. Deliberately DUPLICATED rather than imported: importing
36
+ * `resources/Memory.ts` here would drag in its module-top-level
37
+ * `export class Memory extends (databases as any).flair.Memory` — a class
38
+ * EXTENDS that only resolves once a live Harper has already registered the
39
+ * base `Memory` resource, which unit tests (no real Harper; see
40
+ * `test/unit/migrations-visibility-backfill.test.ts`) cannot satisfy. Same
41
+ * reasoning, same shape, as `src/cli.ts`'s `isFederationPrivateVisibility`
42
+ * duplicating `resources/memory-visibility.ts`'s `isPrivateVisibility`
43
+ * rather than importing it (see that file's comment) — both are one-line,
44
+ * pure, and load-bearing enough that a comment pointing at the canonical
45
+ * definition is the safer contract than a shared import with a landmine
46
+ * behind it. If this rule ever changes, both copies must change together.
47
+ *
48
+ * NEVER OVERWRITES an existing `visibility` value — that is an explicit
49
+ * author decision (their own call, even if it disagrees with what this
50
+ * migration would have derived). Only rows where `visibility` is
51
+ * null/undefined are eligible, and `run()` below re-verifies that on a
52
+ * freshly-read record immediately before writing (the actual enforcement
53
+ * of "never overwrite" — see the write-gate comment there), independent of
54
+ * how precisely the candidate-selection query below narrowed things.
55
+ *
56
+ * THE CANDIDATE QUERY — why it looks different from `embedding-stamp`'s:
57
+ * `visibility` is declared in `schemas/memory.graphql` but NOT `@indexed`
58
+ * (unlike `embeddingModel`, which flair#807 needed indexed for its own
59
+ * pending query). Every condition against it is therefore already a
60
+ * full-table-scan filter (`resources/search.ts`'s `filterByType`), whether
61
+ * or not the comparator is negated — the `not_equals`-forces-an-index-bypass
62
+ * mechanism `embedding-stamp.ts` depends on (flair#807) is simply moot for
63
+ * an attribute with no index to be stale in the first place. What DOES
64
+ * still matter, and is the same lesson flair#807 taught: a strict-equals
65
+ * filter (`recordValue === value`) never matches a row whose property was
66
+ * NEVER SET (`recordValue` reads back as `undefined`, and `undefined !==
67
+ * null`), so `{comparator: "equals", value: null}` alone would silently
68
+ * miss every truly-absent row — exactly the rows this migration exists to
69
+ * find. A NEGATED comparator does match them (`undefined !== "private"` is
70
+ * true), so the pending condition here is `visibility not_equals "private"
71
+ * AND visibility not_equals "shared"` — no separate `equals: null` leg
72
+ * needed (unlike `embedding-stamp`, whose write path can leave an
73
+ * intermediate explicit-null state on a failed HTTP regen; this migration's
74
+ * write is a single in-process read-modify-write that either fully
75
+ * succeeds or leaves the row in its exact prior state, so there is no
76
+ * intermediate state to add a leg for). This exact `not_equal`-covers-
77
+ * absent-and-null-and-nothing-else-with-the-value-itself semantics is
78
+ * already relied on, in production, by `resources/memory-read-scope.ts`'s
79
+ * cross-agent read-scope condition on this SAME field (`visibility !=
80
+ * 'private'` — "which INCLUDES records missing the field entirely") — this
81
+ * migration's query is not a new risk, it is the same proven mechanic.
82
+ *
83
+ * A garbage third value (something other than "private"/"shared"/absent)
84
+ * would also match this AND-of-two-`not_equals` condition — Harper's query
85
+ * algebra has no "attribute is absent" primitive to exclude that case at
86
+ * the query level. No code path in this codebase ever writes such a value
87
+ * today (confirmed by grep — every write site either omits `visibility`,
88
+ * defaults it via `defaultVisibilityForDurability`, or passes an operator-
89
+ * supplied string straight through with no allowlist, so nothing rules it
90
+ * out categorically either). The write-gate in `run()` is what actually
91
+ * protects against this, not the query: a row is only ever written to if a
92
+ * freshly-read `.get()` shows `visibility` is STILL null/undefined at write
93
+ * time. A genuinely garbage-valued row would be pulled in as a candidate,
94
+ * re-checked, found to already carry an explicit (if unexpected) value, and
95
+ * skipped — untouched, exactly like any other already-set row. Worst case
96
+ * this makes `countPending()` overcount relative to what `run()` will ever
97
+ * actually touch, which would show up as a real, visible completion-gate
98
+ * halt (`resources/migrations/runner.ts`'s "completion gate failed:
99
+ * rowsRemaining=N") rather than a silent skip or a false success — halt-
100
+ * don't-brick (invariant II), and worth surfacing loudly if it ever
101
+ * happens, since a garbage `visibility` value is itself a bug worth seeing.
102
+ *
103
+ * THE WRITE PATH — deliberately NOT `embedding-stamp`'s loopback-HTTP
104
+ * `PUT /Memory/:id` trick. That mechanism exists ONLY because
105
+ * `embedding-stamp` needs `resources/Memory.ts`'s SUBCLASS override (the
106
+ * `content.content && !content.embedding` regen branch) to actually fire,
107
+ * and the raw `databases.flair.Memory` reference (what this migration and
108
+ * that one both get via their table accessor) resolves to the RAW
109
+ * underlying table, silently skipping every subclass override — see that
110
+ * file's module doc for the full, empirically-confirmed mechanism. This
111
+ * migration needs NO subclass business logic (no dedup, no regen, no
112
+ * provenance stamping, no rate limiting) — it is a narrow, single-field
113
+ * patch, which is exactly the shape `resources/table-helpers.ts`'s
114
+ * `patchRecord` documents and this codebase already uses directly against
115
+ * the RAW table for the same kind of narrow metadata write:
116
+ * `resources/auth-middleware.ts`'s `backfillEmbedding()` (patches
117
+ * `embedding`), `resources/MemoryReflect.ts` and `resources/Memory.ts`
118
+ * itself (both patch `lastReflected` this exact way). Per that file's own
119
+ * warning, Harper's raw `.put()` is FULL RECORD REPLACEMENT — a partial
120
+ * object silently deletes every field not included — so `run()` below
121
+ * always spreads the freshly-read `existing` record before adding
122
+ * `visibility`, the same read-modify-write shape `patchRecord` implements
123
+ * (inlined here rather than calling that helper directly only because
124
+ * `run()` already needs its own `.get()` for the write-gate check above,
125
+ * and re-using that same read avoids a second, redundant round-trip).
126
+ *
127
+ * NO `recheckPending()`: that hook exists specifically to catch a stale
128
+ * SECONDARY INDEX diverging from live values (flair#807, `embedding-stamp`
129
+ * doc above) — a failure mode that requires an index to go stale in the
130
+ * first place. `visibility` has no index at all (every read here is
131
+ * already a live-record scan), so there is no analogous divergence for a
132
+ * safety net to guard against. Same reasoning `graph-heal.ts` (also
133
+ * `derived-only`, also unindexed-field-adjacent) already applies by also
134
+ * omitting it.
135
+ */
136
+ import { databases } from "harper";
137
+ function defaultMemoryTable() {
138
+ return databases.flair.Memory;
139
+ }
140
+ export const VISIBILITY_BACKFILL_ID = "visibility-backfill";
141
+ /**
142
+ * flair#509's rule, reused verbatim (see module doc for why this is a
143
+ * deliberate duplicate of `resources/Memory.ts`'s
144
+ * `defaultVisibilityForDurability` rather than an import). Total function —
145
+ * every input maps to exactly one of the two valid outputs, and the
146
+ * fail-safe branch (`private`) is the ELSE of the allowlist check, so any
147
+ * absent/null/unrecognised/wrong-typed `durability` value falls into it by
148
+ * construction, never needs its own case.
149
+ */
150
+ export function deriveVisibilityFromDurability(durability) {
151
+ return durability === "permanent" || durability === "persistent" ? "shared" : "private";
152
+ }
153
+ /**
154
+ * Rows this migration still needs to touch: `visibility` is neither
155
+ * "private" nor "shared" — see module doc for why this AND-of-two-
156
+ * `not_equals` correctly matches both a truly-absent property and an
157
+ * explicit `null`, without a separate `equals: null` leg.
158
+ */
159
+ function pendingCondition() {
160
+ return [
161
+ { attribute: "visibility", comparator: "not_equals", value: "private" },
162
+ { attribute: "visibility", comparator: "not_equals", value: "shared" },
163
+ ];
164
+ }
165
+ /**
166
+ * `getTable` is injectable so unit tests exercise this migration's full
167
+ * detect/countPending/run logic against an in-memory fake table (matching
168
+ * `embedding-stamp.ts`'s DI style) — no real Harper needed. Unlike
169
+ * `embedding-stamp`, there is no separate HTTP mechanism to inject: the
170
+ * write IS the table's own `.put()`, already covered by the same fake.
171
+ */
172
+ export function createVisibilityBackfillMigration(getTable = defaultMemoryTable) {
173
+ return {
174
+ id: VISIBILITY_BACKFILL_ID,
175
+ riskClass: "derived-only",
176
+ affectsTables: ["Memory"],
177
+ async detect() {
178
+ const table = getTable();
179
+ for await (const _row of table.search({ conditions: pendingCondition(), limit: 1 })) {
180
+ return true;
181
+ }
182
+ return false;
183
+ },
184
+ async countPending() {
185
+ const table = getTable();
186
+ let n = 0;
187
+ for await (const _row of table.search({ conditions: pendingCondition() }))
188
+ n++;
189
+ return n;
190
+ },
191
+ async run(batchSize) {
192
+ const table = getTable();
193
+ const candidates = [];
194
+ for await (const row of table.search({ conditions: pendingCondition(), limit: batchSize })) {
195
+ candidates.push(row);
196
+ }
197
+ const touchedIds = [];
198
+ for (const row of candidates) {
199
+ const id = String(row.id ?? "");
200
+ if (!id)
201
+ continue;
202
+ const existing = await table.get(id);
203
+ if (!existing)
204
+ continue; // deleted since the search above — nothing to fix
205
+ // The write-gate: this is what actually enforces "never overwrite an
206
+ // existing visibility value", independent of how precisely the
207
+ // candidate query above narrowed things (see module doc — a
208
+ // garbage third value would still be pulled in as a candidate).
209
+ // Re-checks the FRESHLY-READ record, not the (possibly stale)
210
+ // search-result row, and doubles as the idempotency/concurrent-
211
+ // writer guard `embedding-stamp.ts` gets from its own analogous
212
+ // pre-write check.
213
+ if (existing.visibility !== undefined && existing.visibility !== null)
214
+ continue;
215
+ const derived = deriveVisibilityFromDurability(existing.durability);
216
+ // Never-widen invariant, asserted, not just tested: the only two
217
+ // values `deriveVisibilityFromDurability` can ever produce are
218
+ // "private"/"shared" (see its own doc), so this can never fire
219
+ // today — it exists to fail loudly (halting this migration via the
220
+ // runner's mid-batch-throw path, never a silent bad write) if a
221
+ // future edit to that function ever widens its return type or is
222
+ // bypassed via a type-unsafe call.
223
+ if (derived !== "private" && derived !== "shared") {
224
+ throw new Error(`visibility-backfill: derived an invalid visibility for row ${id} — refusing to write`);
225
+ }
226
+ await table.put({ ...existing, visibility: derived });
227
+ touchedIds.push(id);
228
+ }
229
+ return { processed: touchedIds.length, touchedIds };
230
+ },
231
+ };
232
+ }
@@ -315,6 +315,34 @@ export const RECORD_TYPES = {
315
315
  federation: "included",
316
316
  mcp: { toolPrefix: "soul", readVerbs: ["get"], writeVerbs: ["store"] },
317
317
  },
318
+ // MemoryCandidate — flair#849. A candidate is an unreviewed draft
319
+ // distillation staged by the FLAIR-NIGHTLY-REM cycle (docs/rem.md);
320
+ // `readScope: "owner-only"` (not "open-within-org") is deliberate: a
321
+ // candidate must not be org-readable before a human/agent reviewer
322
+ // explicitly promotes or rejects it — this is the standard-review-bar
323
+ // choice per this file's header doc's review-gate tiering (an
324
+ // "open-within-org" or "none" entry would additionally require Sherlock's
325
+ // explicit security sign-off, which this entry does not carry). No
326
+ // `embedding` — the claim text has no dedicated recall surface (it isn't
327
+ // Memory.content). No `mcp` — no MCP tool exposes this table today.
328
+ MemoryCandidate: {
329
+ table: "MemoryCandidate",
330
+ ownerField: "agentId",
331
+ identity: "gated",
332
+ readScope: "owner-only",
333
+ attribution: { post: "validate-truthy", put: "validate-truthy" },
334
+ // No provenance field on the schema (schemas/memory.graphql) — nobody
335
+ // stamps buildProvenance() for this table.
336
+ provenance: false,
337
+ remEligible: false,
338
+ // Not in Federation.ts's push table map nor src/cli.ts's
339
+ // runFederationSyncOnce hardcoded list (`["Memory", "Soul", "Agent",
340
+ // "Relationship"]`) — per this file's header doc's federation section,
341
+ // a new type's registration must not silently inherit whatever that
342
+ // filter does or doesn't already exclude, so this is "excluded" by the
343
+ // same reasoning, not by omission.
344
+ federation: "excluded",
345
+ },
318
346
  };
319
347
  // ─── Composite MCP tools (the second, and only other, reviewed chokepoint) ─
320
348
  //
@@ -36,7 +36,7 @@
36
36
  // before calling in). Never exposes which internal leg (BM25+RRF hybrid vs.
37
37
  // legacy HNSW-only vs. keyword-only fallback) produced a given result — the
38
38
  // output shape is identical regardless of `hybrid`.
39
- import { databases } from "@harperfast/harper";
39
+ import { databases } from "harper";
40
40
  import { withDetachedTxn } from "./table-helpers.js";
41
41
  import { wrapUntrusted } from "./content-safety.js";
42
42
  import { cosineSimilarity } from "./dedup.js";
@@ -45,7 +45,7 @@
45
45
  * memories it cannot see). So the scope gate lives HERE, not retrofitted
46
46
  * onto recordUsageContribution() — do not "unify" the two surfaces.
47
47
  */
48
- import { databases } from "@harperfast/harper";
48
+ import { databases } from "harper";
49
49
  import { withDetachedTxn } from "./table-helpers.js";
50
50
  import { resolveReadScope } from "./memory-read-scope.js";
51
51
  /**