@tpsdev-ai/flair 0.20.1 → 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 (104) hide show
  1. package/README.md +39 -11
  2. package/SECURITY.md +52 -20
  3. package/config.yaml +32 -5
  4. package/dist/cli.js +2235 -260
  5. package/dist/deploy.js +96 -15
  6. package/dist/doctor-client.js +312 -0
  7. package/dist/fleet-presence.js +98 -0
  8. package/dist/fleet-verify.js +291 -0
  9. package/dist/install/clients.js +18 -0
  10. package/dist/mcp-client-assertion.js +364 -0
  11. package/dist/probe.js +97 -0
  12. package/dist/rem/restore.js +1 -1
  13. package/dist/rem/runner.js +82 -12
  14. package/dist/resources/Admin.js +1 -1
  15. package/dist/resources/AdminConnectors.js +1 -1
  16. package/dist/resources/AdminDashboard.js +1 -1
  17. package/dist/resources/AdminIdp.js +1 -1
  18. package/dist/resources/AdminInstance.js +1 -1
  19. package/dist/resources/AdminMemory.js +2 -2
  20. package/dist/resources/AdminPrincipals.js +1 -1
  21. package/dist/resources/Agent.js +14 -0
  22. package/dist/resources/AgentCard.js +1 -1
  23. package/dist/resources/AgentSeed.js +1 -1
  24. package/dist/resources/AttentionQuery.js +356 -0
  25. package/dist/resources/Credential.js +11 -1
  26. package/dist/resources/Federation.js +121 -2
  27. package/dist/resources/Integration.js +1 -1
  28. package/dist/resources/MCPClientMetadata.js +88 -0
  29. package/dist/resources/Memory.js +127 -22
  30. package/dist/resources/MemoryBootstrap.js +441 -85
  31. package/dist/resources/MemoryGrant.js +1 -1
  32. package/dist/resources/MemoryReflect.js +105 -49
  33. package/dist/resources/MemoryUsage.js +104 -0
  34. package/dist/resources/OAuth.js +69 -5
  35. package/dist/resources/OrgEvent.js +11 -0
  36. package/dist/resources/OrgEventCatchup.js +1 -1
  37. package/dist/resources/Presence.js +268 -17
  38. package/dist/resources/RecordUsage.js +230 -0
  39. package/dist/resources/Relationship.js +108 -26
  40. package/dist/resources/SemanticSearch.js +88 -320
  41. package/dist/resources/SkillScan.js +15 -0
  42. package/dist/resources/Soul.js +14 -0
  43. package/dist/resources/WorkspaceLatest.js +1 -1
  44. package/dist/resources/WorkspaceState.js +12 -1
  45. package/dist/resources/agent-auth.js +61 -3
  46. package/dist/resources/agentcard-fields.js +2 -2
  47. package/dist/resources/auth-middleware.js +42 -14
  48. package/dist/resources/bm25-filter.js +1 -1
  49. package/dist/resources/bm25.js +13 -7
  50. package/dist/resources/collision-lib.js +157 -0
  51. package/dist/resources/dedup.js +2 -2
  52. package/dist/resources/ed25519-auth.js +2 -2
  53. package/dist/resources/embeddings-boot.js +149 -0
  54. package/dist/resources/embeddings-provider.js +364 -106
  55. package/dist/resources/entity-vocab.js +139 -0
  56. package/dist/resources/federation-nonce-store.js +195 -0
  57. package/dist/resources/health.js +97 -6
  58. package/dist/resources/instance-identity.js +53 -0
  59. package/dist/resources/mcp-client-metadata-fields.js +112 -0
  60. package/dist/resources/mcp-handler.js +1 -1
  61. package/dist/resources/mcp-tools.js +88 -10
  62. package/dist/resources/memory-bootstrap-lib.js +1 -1
  63. package/dist/resources/memory-read-scope.js +58 -71
  64. package/dist/resources/memory-reflect-lib.js +289 -0
  65. package/dist/resources/memory-visibility.js +37 -0
  66. package/dist/resources/migration-boot.js +143 -0
  67. package/dist/resources/migrations/dir-safety.js +71 -0
  68. package/dist/resources/migrations/embedding-stamp.js +178 -0
  69. package/dist/resources/migrations/envelope.js +43 -0
  70. package/dist/resources/migrations/export.js +38 -0
  71. package/dist/resources/migrations/ledger.js +55 -0
  72. package/dist/resources/migrations/lock.js +154 -0
  73. package/dist/resources/migrations/progress.js +31 -0
  74. package/dist/resources/migrations/registry.js +36 -0
  75. package/dist/resources/migrations/risk-policy.js +23 -0
  76. package/dist/resources/migrations/runner.js +456 -0
  77. package/dist/resources/migrations/snapshot.js +127 -0
  78. package/dist/resources/migrations/source-fields.js +93 -0
  79. package/dist/resources/migrations/space.js +73 -0
  80. package/dist/resources/migrations/state.js +64 -0
  81. package/dist/resources/migrations/status.js +39 -0
  82. package/dist/resources/migrations/synthetic-test-migration.js +93 -0
  83. package/dist/resources/migrations/types.js +9 -0
  84. package/dist/resources/presence-internal.js +29 -0
  85. package/dist/resources/provenance.js +50 -0
  86. package/dist/resources/rate-limiter.js +8 -1
  87. package/dist/resources/scoring.js +124 -5
  88. package/dist/resources/semantic-retrieval-core.js +317 -0
  89. package/dist/version-check.js +167 -0
  90. package/dist/version-handshake.js +122 -0
  91. package/package.json +4 -5
  92. package/schemas/agent.graphql +7 -0
  93. package/schemas/event.graphql +5 -0
  94. package/schemas/federation.graphql +12 -0
  95. package/schemas/memory.graphql +82 -0
  96. package/schemas/schema.graphql +5 -43
  97. package/schemas/workspace.graphql +3 -0
  98. package/dist/resources/IngestEvents.js +0 -162
  99. package/dist/resources/IssueTokens.js +0 -19
  100. package/dist/resources/ObsAgentSnapshot.js +0 -13
  101. package/dist/resources/ObsEventFeed.js +0 -13
  102. package/dist/resources/ObsOffice.js +0 -19
  103. package/dist/resources/ObservationCenter.js +0 -23
  104. package/ui/observation-center.html +0 -385
@@ -1,7 +1,11 @@
1
1
  import { databases } from "@harperfast/harper";
2
2
  import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
3
3
  import { checkRateLimit, rateLimitResponse } from "./rate-limiter.js";
4
+ import { localInstanceId } from "./instance-identity.js";
5
+ import { buildProvenance } from "./provenance.js";
4
6
  const NOT_FOUND = () => new Response(JSON.stringify({ error: "not found" }), { status: 404, headers: { "Content-Type": "application/json" } });
7
+ const UNAUTH = () => new Response(JSON.stringify({ error: "authentication required" }), { status: 401, headers: { "Content-Type": "application/json" } });
8
+ const FORBIDDEN = (msg) => new Response(JSON.stringify({ error: msg }), { status: 403, headers: { "Content-Type": "application/json" } });
5
9
  /**
6
10
  * Relationship resource — entity-to-entity relationships with temporal validity.
7
11
  *
@@ -16,7 +20,7 @@ const NOT_FOUND = () => new Response(JSON.stringify({ error: "not found" }), { s
16
20
  export class Relationship extends databases.flair.Relationship {
17
21
  /**
18
22
  * Self-authorize now that the global gate is non-rejecting (memory-soul-
19
- * read-gate family fix, ops-oox7 — same pattern as Memory.ts/Soul.ts/
23
+ * read-gate family fix — same pattern as Memory.ts/Soul.ts/
20
24
  * WorkspaceState.ts). Closes the same P0 leak: Harper routes
21
25
  * `GET /Relationship/<id>` to get() and the collection describe
22
26
  * (`GET /Relationship`) outside search(), so neither was gated before this
@@ -85,18 +89,73 @@ export class Relationship extends databases.flair.Relationship {
85
89
  operator: "and",
86
90
  });
87
91
  }
92
+ /**
93
+ * ─── Auth reconcile (relationship-write-path, folded K&S refinement) ──────
94
+ *
95
+ * Upgraded from the older `request.tpsAgent`-direct pattern to
96
+ * `resolveAgentAuth` — matching Memory.post()/put() — so this write path
97
+ * gets the SAME three-way verdict handling (anonymous denied, verified
98
+ * agent stamped from the SIGNATURE never the body, internal/admin
99
+ * unfiltered) instead of a parallel, easy-to-drift auth mechanism. K&S both
100
+ * flagged this as a real divergence (the previous code had no
101
+ * internal/admin verdict paths at all — see the doc below for why that
102
+ * never actually bit anyone in practice, but was still the wrong shape to
103
+ * build the new ergonomic write surfaces on top of).
104
+ *
105
+ * - `anonymous` → 401, same as before.
106
+ * - `agent` + non-admin: a body-supplied `agentId` that MISMATCHES the
107
+ * verified identity is rejected outright (403) rather than silently
108
+ * overwritten — a clearer signal than Memory.post()'s "validate, don't
109
+ * stamp" idiom. `content.agentId` is then ALWAYS set from `auth.agentId`
110
+ * (never left as whatever the body claimed, even when it already
111
+ * matched) — the non-negotiable "agentId comes from the verdict, never
112
+ * the body" rule, applied unconditionally rather than only on mismatch.
113
+ * - `agent` + admin: `content.agentId` passes through UNFILTERED, matching
114
+ * the existing admin-bypass idiom already used by get()/search()/
115
+ * delete() below (an admin/migration tool may legitimately write on
116
+ * another agent's behalf).
117
+ * - `internal` (no HTTP request at all — a trusted in-process call):
118
+ * `content.agentId` also passes through unchanged. No in-process
119
+ * Relationship writer exists today (openclaw's integration writes via a
120
+ * real signed HTTP PUT, landing on the `agent` branch above), so this is
121
+ * forward-looking parity with Memory.post()/put() rather than a path
122
+ * this PR's callers actually exercise — but it closes the SAME latent gap
123
+ * the old code had: `request?.tpsAgent` was falsy for BOTH an anonymous
124
+ * HTTP caller and a true internal call, so an internal caller would have
125
+ * been wrongly 401'd too. resolveAgentAuth distinguishes the two.
126
+ */
88
127
  async put(content) {
89
128
  const ctx = this.getContext?.();
90
- const request = ctx?.request ?? ctx;
91
- const authAgent = request?.tpsAgent;
92
- if (!authAgent) {
93
- return new Response(JSON.stringify({ error: "authentication required" }), {
94
- status: 401, headers: { "content-type": "application/json" },
129
+ const auth = await resolveAgentAuth(ctx);
130
+ if (auth.kind === "anonymous") {
131
+ return UNAUTH();
132
+ }
133
+ if (auth.kind === "agent") {
134
+ if (!auth.isAdmin) {
135
+ if (content?.agentId && content.agentId !== auth.agentId) {
136
+ return FORBIDDEN("cannot write a relationship owned by another agent");
137
+ }
138
+ content.agentId = auth.agentId;
139
+ }
140
+ // admin: content.agentId left as provided (unfiltered) — see doc above.
141
+ }
142
+ // internal: content.agentId left as provided (unfiltered) — see doc above.
143
+ if (!content.agentId || typeof content.agentId !== "string") {
144
+ return new Response(JSON.stringify({ error: "agentId is required" }), {
145
+ status: 400, headers: { "content-type": "application/json" },
95
146
  });
96
147
  }
97
- const rl = checkRateLimit(authAgent);
98
- if (!rl.allowed)
99
- return rateLimitResponse(rl.retryAfterMs, "relationship");
148
+ // Rate limit keyed on the RESOLVED agentId (never a client-supplied one)
149
+ // — matches Memory.post()'s intent, extended to cover every
150
+ // resolveAgentAuth path (credentialed super_user, verifyAgentRequest
151
+ // fallback), not just the gate's own `tpsAgent` annotation. Internal
152
+ // calls have no per-agent identity to key on and are trusted, so they're
153
+ // exempt — same as Memory.post()'s `if (authenticatedAgent)` guard.
154
+ if (auth.kind === "agent") {
155
+ const rl = checkRateLimit(auth.agentId);
156
+ if (!rl.allowed)
157
+ return rateLimitResponse(rl.retryAfterMs, "relationship");
158
+ }
100
159
  // Validate required fields
101
160
  if (!content.subject || typeof content.subject !== "string") {
102
161
  return new Response(JSON.stringify({ error: "subject is required (string)" }), {
@@ -113,9 +172,9 @@ export class Relationship extends databases.flair.Relationship {
113
172
  status: 400, headers: { "content-type": "application/json" },
114
173
  });
115
174
  }
116
- // Normalize
175
+ // Normalize — lowercasing is load-bearing: MemoryBootstrap.ts's attention
176
+ // read matches lowercased predicted subjects against subject/object.
117
177
  const now = new Date().toISOString();
118
- content.agentId = authAgent;
119
178
  content.subject = content.subject.toLowerCase();
120
179
  content.predicate = content.predicate.toLowerCase();
121
180
  content.object = content.object.toLowerCase();
@@ -124,26 +183,49 @@ export class Relationship extends databases.flair.Relationship {
124
183
  content.validFrom = content.validFrom || now;
125
184
  // validTo left as null/undefined for active relationships
126
185
  content.confidence = content.confidence ?? 1.0;
186
+ // Write-time provenance stamp (relationship-write-path, folded K&S
187
+ // refinement) — reuses Memory's buildProvenance EXACTLY (./provenance.ts),
188
+ // same `{v, verified, claimed?}` shape, no Relationship-specific format.
189
+ // Additive/nullable on the schema side (schemas/memory.graphql) — a
190
+ // pre-existing row with no provenance field reads back `undefined`,
191
+ // unchanged behavior (migration-equivalence gate).
192
+ content.provenance = buildProvenance(auth, content.createdAt, content);
193
+ // Write-time originatorInstanceId stamp (federation-edge-hardening slice
194
+ // 1) — see resources/Memory.ts's stampOriginatorInstanceId doc for the
195
+ // full contract. No-op if already set (never fires for a genuine local
196
+ // write; a federation-synced record never reaches this method — the
197
+ // merge path writes via the raw table object, bypassing this class).
198
+ if (content.originatorInstanceId == null) {
199
+ content.originatorInstanceId = await localInstanceId();
200
+ }
127
201
  return super.put(content);
128
202
  }
203
+ /**
204
+ * Same auth reconcile as put() above — resolveAgentAuth replaces the
205
+ * `request.tpsAgent`-direct pattern. K&S both independently caught that
206
+ * delete() had the identical divergence the spec text only named on
207
+ * put(): anonymous and true-internal calls were indistinguishable (both
208
+ * read as a falsy `authAgent`), and there was no admin/internal verdict
209
+ * handling. Ownership-check logic (own-agent-or-admin) is otherwise
210
+ * unchanged — see test/integration/relationship-delete-authz.test.ts's doc
211
+ * comment for why calling `super.get()` with no target argument still
212
+ * resolves the URL-bound target record (a Harper Table-resource
213
+ * invariant), not an empty/collection result.
214
+ */
129
215
  async delete(_) {
130
216
  const ctx = this.getContext?.();
131
- const request = ctx?.request ?? ctx;
132
- const authAgent = request?.tpsAgent;
133
- const isAdminAgent = request?.tpsAgentIsAdmin ?? false;
134
- if (!authAgent) {
135
- return new Response(JSON.stringify({ error: "authentication required" }), {
136
- status: 401, headers: { "content-type": "application/json" },
137
- });
217
+ const auth = await resolveAgentAuth(ctx);
218
+ if (auth.kind === "anonymous") {
219
+ return UNAUTH();
138
220
  }
139
- // Non-admin: verify ownership before delete
140
- if (!isAdminAgent) {
141
- const existing = await super.get();
142
- if (existing?.agentId && existing.agentId !== authAgent) {
143
- return new Response(JSON.stringify({ error: "cannot delete another agent's relationship" }), {
144
- status: 403, headers: { "content-type": "application/json" },
145
- });
146
- }
221
+ // Trusted internal call or admin agent unfiltered, unchanged behavior.
222
+ if (auth.kind === "internal" || (auth.kind === "agent" && auth.isAdmin)) {
223
+ return super.delete(_);
224
+ }
225
+ // Non-admin agent: verify ownership before delete.
226
+ const existing = await super.get();
227
+ if (existing?.agentId && existing.agentId !== auth.agentId) {
228
+ return FORBIDDEN("cannot delete another agent's relationship");
147
229
  }
148
230
  return super.delete(_);
149
231
  }