@tpsdev-ai/flair 0.21.0 → 0.22.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 (74) hide show
  1. package/README.md +10 -7
  2. package/SECURITY.md +24 -2
  3. package/config.yaml +32 -5
  4. package/dist/cli.js +1755 -213
  5. package/dist/deploy.js +3 -4
  6. package/dist/fleet-presence.js +98 -0
  7. package/dist/fleet-verify.js +291 -0
  8. package/dist/mcp-client-assertion.js +364 -0
  9. package/dist/probe.js +97 -0
  10. package/dist/rem/runner.js +82 -12
  11. package/dist/resources/AttentionQuery.js +356 -0
  12. package/dist/resources/Credential.js +11 -1
  13. package/dist/resources/Federation.js +23 -0
  14. package/dist/resources/MCPClientMetadata.js +88 -0
  15. package/dist/resources/Memory.js +52 -53
  16. package/dist/resources/MemoryBootstrap.js +422 -76
  17. package/dist/resources/MemoryReflect.js +105 -49
  18. package/dist/resources/MemoryUsage.js +104 -0
  19. package/dist/resources/OAuth.js +8 -1
  20. package/dist/resources/OrgEvent.js +11 -0
  21. package/dist/resources/Presence.js +218 -19
  22. package/dist/resources/RecordUsage.js +230 -0
  23. package/dist/resources/Relationship.js +98 -25
  24. package/dist/resources/SemanticSearch.js +85 -317
  25. package/dist/resources/SkillScan.js +15 -0
  26. package/dist/resources/WorkspaceState.js +11 -0
  27. package/dist/resources/agent-auth.js +60 -2
  28. package/dist/resources/auth-middleware.js +18 -9
  29. package/dist/resources/bm25.js +12 -6
  30. package/dist/resources/collision-lib.js +157 -0
  31. package/dist/resources/embeddings-boot.js +149 -0
  32. package/dist/resources/embeddings-provider.js +364 -106
  33. package/dist/resources/entity-vocab.js +139 -0
  34. package/dist/resources/health.js +97 -6
  35. package/dist/resources/mcp-client-metadata-fields.js +112 -0
  36. package/dist/resources/mcp-handler.js +1 -1
  37. package/dist/resources/mcp-tools.js +88 -10
  38. package/dist/resources/memory-reflect-lib.js +289 -0
  39. package/dist/resources/migration-boot.js +143 -0
  40. package/dist/resources/migrations/dir-safety.js +71 -0
  41. package/dist/resources/migrations/embedding-stamp.js +178 -0
  42. package/dist/resources/migrations/envelope.js +43 -0
  43. package/dist/resources/migrations/export.js +38 -0
  44. package/dist/resources/migrations/ledger.js +55 -0
  45. package/dist/resources/migrations/lock.js +154 -0
  46. package/dist/resources/migrations/progress.js +31 -0
  47. package/dist/resources/migrations/registry.js +36 -0
  48. package/dist/resources/migrations/risk-policy.js +23 -0
  49. package/dist/resources/migrations/runner.js +456 -0
  50. package/dist/resources/migrations/snapshot.js +127 -0
  51. package/dist/resources/migrations/source-fields.js +93 -0
  52. package/dist/resources/migrations/space.js +73 -0
  53. package/dist/resources/migrations/state.js +64 -0
  54. package/dist/resources/migrations/status.js +39 -0
  55. package/dist/resources/migrations/synthetic-test-migration.js +93 -0
  56. package/dist/resources/migrations/types.js +9 -0
  57. package/dist/resources/presence-internal.js +29 -0
  58. package/dist/resources/provenance.js +50 -0
  59. package/dist/resources/rate-limiter.js +8 -1
  60. package/dist/resources/scoring.js +124 -5
  61. package/dist/resources/semantic-retrieval-core.js +317 -0
  62. package/dist/version-handshake.js +122 -0
  63. package/package.json +4 -5
  64. package/schemas/event.graphql +5 -0
  65. package/schemas/memory.graphql +66 -0
  66. package/schemas/schema.graphql +5 -43
  67. package/schemas/workspace.graphql +3 -0
  68. package/dist/resources/IngestEvents.js +0 -162
  69. package/dist/resources/IssueTokens.js +0 -19
  70. package/dist/resources/ObsAgentSnapshot.js +0 -13
  71. package/dist/resources/ObsEventFeed.js +0 -13
  72. package/dist/resources/ObsOffice.js +0 -19
  73. package/dist/resources/ObservationCenter.js +0 -23
  74. package/ui/observation-center.html +0 -385
@@ -4,9 +4,11 @@ import { isAdmin, resolveAgentAuth, allowVerified } from "./agent-auth.js";
4
4
  import { localInstanceId } from "./instance-identity.js";
5
5
  import { getEmbedding, getModelId } from "./embeddings-provider.js";
6
6
  import { scanFields, isStrictMode } from "./content-safety.js";
7
+ import { invalidEntitiesResponse } from "./entity-vocab.js";
7
8
  import { checkRateLimit, rateLimitResponse } from "./rate-limiter.js";
8
9
  import { resolveReadScope } from "./memory-read-scope.js";
9
10
  import { DEDUP_COSINE_THRESHOLD_DEFAULT, DEDUP_LEXICAL_THRESHOLD_DEFAULT, DEDUP_MIN_CONTENT_LENGTH, computeMatchConfidence, cosineSimilarity, isConservativeMatch, } from "./dedup.js";
11
+ import { buildProvenance } from "./provenance.js";
10
12
  const FORBIDDEN = (msg) => new Response(JSON.stringify({ error: msg }), { status: 403, headers: { "Content-Type": "application/json" } });
11
13
  const UNAUTH = () => new Response(JSON.stringify({ error: "authentication required" }), { status: 401, headers: { "Content-Type": "application/json" } });
12
14
  const NOT_FOUND = () => new Response(JSON.stringify({ error: "not found" }), { status: 404, headers: { "Content-Type": "application/json" } });
@@ -171,10 +173,29 @@ async function runDedupGate(ctx, content) {
171
173
  if (typeof content.content !== "string" || content.content.length < DEDUP_MIN_CONTENT_LENGTH) {
172
174
  return null;
173
175
  }
176
+ // flair#504 Phase 2: 'document' — this embedding IS the stored vector (the
177
+ // "generate embedding if missing" step in post()/put() below reuses
178
+ // whatever this computes), so it MUST use the same inputType as every
179
+ // other document-write site or dedup's cosine compare would cross prefixed
180
+ // and unprefixed spaces. All three Memory doc sites (here, post(), put())
181
+ // move together in one commit for exactly that reason — see
182
+ // embeddings-provider.ts's file header for why the VALUE must be the
183
+ // literal 'document', never the prefix string.
184
+ //
185
+ // Dedup-during-transition transient (documented per Kern's review, not a
186
+ // bug to fix here): mid stage-2 re-embed, a NEW write embeds 'document'
187
+ // (prefixed) but may compare against an OLDER stored vector that hasn't
188
+ // been re-embedded yet (unprefixed) — cross-space cosine, so dedup can
189
+ // miss a near-duplicate during that window. Bounded (the re-embed pass is
190
+ // batched and finishes in minutes), self-healing once the pass completes,
191
+ // and a missed dedup is a duplicate row, not data loss — quality, not
192
+ // correctness. Stage 1 (this PR) doesn't trigger this at all: no re-embed
193
+ // runs, so there's no mixed-space window until stage 2's separate,
194
+ // deliberate ops step.
174
195
  let embedding = Array.isArray(content.embedding) ? content.embedding : null;
175
196
  if (!embedding) {
176
197
  try {
177
- embedding = await getEmbedding(content.content);
198
+ embedding = await getEmbedding(content.content, "document");
178
199
  }
179
200
  catch {
180
201
  embedding = null;
@@ -314,55 +335,17 @@ function defaultVisibilityForDurability(durability) {
314
335
  /**
315
336
  * ─── Write-time provenance stamp (memory-provenance slice 1) ────────────────
316
337
  *
317
- * Foundational capture for an emergent-trust model: every Memory write gets a
318
- * structured, versioned `provenance` JSON blob recording what the server can
319
- * actually VERIFY about the write, plus (optionally) what the caller merely
320
- * CLAIMS. Deliberately minimal verified fields only:
321
- *
322
- * { v: 1,
323
- * verified: { agentId: <string|null>, timestamp: <ISO string> },
324
- * claimed?: { model: <string> } }
325
- *
326
- * - `verified.agentId` comes from the ALREADY-RESOLVED auth verdict
327
- * (resolveAgentAuth) — never from anything the caller can forge on the
328
- * request body. `kind: "agent"` → the Ed25519-verified agentId. Any other
329
- * verdict (in practice only `kind: "internal"` — a trusted in-process call
330
- * with no per-agent identity to attribute) stamps `null` rather than
331
- * throwing; `kind: "anonymous"` never reaches here — both post()/put()
332
- * already 401 it before this point.
333
- * - `verified.timestamp` reuses the server-clock `createdAt` the caller has
334
- * already computed by this point (never client-suppliable) — the same
335
- * "stamp a dynamic attribute the server controls" mechanism as the existing
336
- * `embeddingModel = getModelId()` stamp elsewhere in this file. NOTE: the
337
- * *signed* Ed25519 request timestamp is also available (it's verified in
338
- * auth-middleware) but currently discarded there rather than threaded
339
- * through to resource methods — a future slice can carry it alongside (or
340
- * instead of) this server-clock timestamp without changing this shape's
341
- * `v: 1` contract. Not done here to keep auth-middleware untouched.
342
- * - `claimed.model` is an OPTIONAL, UNVERIFIED passthrough: included only
343
- * when the incoming write payload itself already carries a non-empty
344
- * string `model` field. No client/CLI sets one today — this just means the
345
- * server won't discard it if/when a future write path does. Never
346
- * invented, never defaulted, and the `claimed` key is omitted entirely
347
- * (not stamped as `{}`) when absent.
348
- *
349
- * Deliberately NOT implemented in this slice: a context-fingerprint field —
350
- * bootstrap doesn't return the IDs a fingerprint would need, so it requires
351
- * client cooperation that's out of scope here.
338
+ * `buildProvenance` itself now lives in ./provenance.ts (imported above)
339
+ * extracted so resources/Relationship.ts's write path can reuse the EXACT
340
+ * same `{v, verified, claimed?}` shape (the relationship-write-path spec's
341
+ * "reuse buildProvenance as-is" contract) instead of a hand-copied format
342
+ * that could drift. See that module for the full field-by-field rationale
343
+ * (verified.agentId from the auth verdict never the body, verified.timestamp
344
+ * = the server-computed createdAt, optional unverified claimed.model
345
+ * passthrough). Deliberately NOT implemented in this slice: a
346
+ * context-fingerprint field — bootstrap doesn't return the IDs a fingerprint
347
+ * would need, so it requires client cooperation that's out of scope here.
352
348
  */
353
- function buildProvenance(auth, createdAt, content) {
354
- const provenance = {
355
- v: 1,
356
- verified: {
357
- agentId: auth.kind === "agent" ? auth.agentId : null,
358
- timestamp: createdAt,
359
- },
360
- };
361
- if (typeof content?.model === "string" && content.model.length > 0) {
362
- provenance.claimed = { model: content.model };
363
- }
364
- return JSON.stringify(provenance);
365
- }
366
349
  /**
367
350
  * ─── Write-time originatorInstanceId stamp (federation-edge-hardening slice 1) ──
368
351
  *
@@ -567,6 +550,13 @@ export class Memory extends databases.flair.Memory {
567
550
  if (!content.validFrom) {
568
551
  content.validFrom = content.createdAt;
569
552
  }
553
+ // attention-plane vocabulary gate (flair#675): `entities`, if present,
554
+ // must be well-formed vocabulary strings — see resources/entity-vocab.ts.
555
+ // Field is additive/optional (v1 schema-only; no auto-derivation here —
556
+ // that producer is a follow-up); absent entities is not an error.
557
+ const entitiesError = invalidEntitiesResponse(content.entities);
558
+ if (entitiesError)
559
+ return entitiesError;
570
560
  if (content.durability === "ephemeral" && !content.expiresAt) {
571
561
  const ttlHours = Number(process.env.FLAIR_EPHEMERAL_TTL_HOURS || 24);
572
562
  content.expiresAt = new Date(Date.now() + ttlHours * 3600_000).toISOString();
@@ -601,9 +591,11 @@ export class Memory extends databases.flair.Memory {
601
591
  delete content.lexicalThreshold;
602
592
  }
603
593
  // Generate embedding from content text (no-op if the dedup gate above
604
- // already computed one for this content).
594
+ // already computed one for this content). flair#504 Phase 2: 'document'
595
+ // — see runDedupGate's comment above for why all three Memory doc sites
596
+ // must move together.
605
597
  if (content.content && !content.embedding) {
606
- const vec = await getEmbedding(content.content);
598
+ const vec = await getEmbedding(content.content, "document");
607
599
  if (vec) {
608
600
  content.embedding = vec;
609
601
  content.embeddingModel = getModelId();
@@ -699,6 +691,10 @@ export class Memory extends databases.flair.Memory {
699
691
  if (content.supersedes && !content.validFrom) {
700
692
  content.validFrom = content.createdAt;
701
693
  }
694
+ // attention-plane vocabulary gate (flair#675) — see post()'s comment above.
695
+ const entitiesError = invalidEntitiesResponse(content.entities);
696
+ if (entitiesError)
697
+ return entitiesError;
702
698
  // Content safety scan on updated content + summary.
703
699
  if (content.content || content.summary) {
704
700
  const safety = scanFields(content, ["content", "summary"]);
@@ -745,9 +741,12 @@ export class Memory extends databases.flair.Memory {
745
741
  dedupMatch = await runDedupGate(ctx, content);
746
742
  }
747
743
  // Re-generate embedding if content changed (no-op if the dedup gate above
748
- // already computed one for this content).
744
+ // already computed one for this content). flair#504 Phase 2: 'document'
745
+ // — this is also the regen branch `flair reembed` triggers (clears
746
+ // embedding/embeddingModel then hits this put()), so it's what actually
747
+ // re-embeds a stale row WITH the prefix once stage 2 runs.
749
748
  if (content.content && !content.embedding) {
750
- const vec = await getEmbedding(content.content);
749
+ const vec = await getEmbedding(content.content, "document");
751
750
  if (vec) {
752
751
  content.embedding = vec;
753
752
  content.embeddingModel = getModelId();