@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.
- package/README.md +39 -11
- package/SECURITY.md +52 -20
- package/config.yaml +32 -5
- package/dist/cli.js +2235 -260
- package/dist/deploy.js +96 -15
- package/dist/doctor-client.js +312 -0
- package/dist/fleet-presence.js +98 -0
- package/dist/fleet-verify.js +291 -0
- package/dist/install/clients.js +18 -0
- package/dist/mcp-client-assertion.js +364 -0
- package/dist/probe.js +97 -0
- package/dist/rem/restore.js +1 -1
- package/dist/rem/runner.js +82 -12
- package/dist/resources/Admin.js +1 -1
- package/dist/resources/AdminConnectors.js +1 -1
- package/dist/resources/AdminDashboard.js +1 -1
- package/dist/resources/AdminIdp.js +1 -1
- package/dist/resources/AdminInstance.js +1 -1
- package/dist/resources/AdminMemory.js +2 -2
- package/dist/resources/AdminPrincipals.js +1 -1
- package/dist/resources/Agent.js +14 -0
- package/dist/resources/AgentCard.js +1 -1
- package/dist/resources/AgentSeed.js +1 -1
- package/dist/resources/AttentionQuery.js +356 -0
- package/dist/resources/Credential.js +11 -1
- package/dist/resources/Federation.js +121 -2
- package/dist/resources/Integration.js +1 -1
- package/dist/resources/MCPClientMetadata.js +88 -0
- package/dist/resources/Memory.js +127 -22
- package/dist/resources/MemoryBootstrap.js +441 -85
- package/dist/resources/MemoryGrant.js +1 -1
- package/dist/resources/MemoryReflect.js +105 -49
- package/dist/resources/MemoryUsage.js +104 -0
- package/dist/resources/OAuth.js +69 -5
- package/dist/resources/OrgEvent.js +11 -0
- package/dist/resources/OrgEventCatchup.js +1 -1
- package/dist/resources/Presence.js +268 -17
- package/dist/resources/RecordUsage.js +230 -0
- package/dist/resources/Relationship.js +108 -26
- package/dist/resources/SemanticSearch.js +88 -320
- package/dist/resources/SkillScan.js +15 -0
- package/dist/resources/Soul.js +14 -0
- package/dist/resources/WorkspaceLatest.js +1 -1
- package/dist/resources/WorkspaceState.js +12 -1
- package/dist/resources/agent-auth.js +61 -3
- package/dist/resources/agentcard-fields.js +2 -2
- package/dist/resources/auth-middleware.js +42 -14
- package/dist/resources/bm25-filter.js +1 -1
- package/dist/resources/bm25.js +13 -7
- package/dist/resources/collision-lib.js +157 -0
- package/dist/resources/dedup.js +2 -2
- package/dist/resources/ed25519-auth.js +2 -2
- package/dist/resources/embeddings-boot.js +149 -0
- package/dist/resources/embeddings-provider.js +364 -106
- package/dist/resources/entity-vocab.js +139 -0
- package/dist/resources/federation-nonce-store.js +195 -0
- package/dist/resources/health.js +97 -6
- package/dist/resources/instance-identity.js +53 -0
- package/dist/resources/mcp-client-metadata-fields.js +112 -0
- package/dist/resources/mcp-handler.js +1 -1
- package/dist/resources/mcp-tools.js +88 -10
- package/dist/resources/memory-bootstrap-lib.js +1 -1
- package/dist/resources/memory-read-scope.js +58 -71
- package/dist/resources/memory-reflect-lib.js +289 -0
- package/dist/resources/memory-visibility.js +37 -0
- package/dist/resources/migration-boot.js +143 -0
- package/dist/resources/migrations/dir-safety.js +71 -0
- package/dist/resources/migrations/embedding-stamp.js +178 -0
- package/dist/resources/migrations/envelope.js +43 -0
- package/dist/resources/migrations/export.js +38 -0
- package/dist/resources/migrations/ledger.js +55 -0
- package/dist/resources/migrations/lock.js +154 -0
- package/dist/resources/migrations/progress.js +31 -0
- package/dist/resources/migrations/registry.js +36 -0
- package/dist/resources/migrations/risk-policy.js +23 -0
- package/dist/resources/migrations/runner.js +456 -0
- package/dist/resources/migrations/snapshot.js +127 -0
- package/dist/resources/migrations/source-fields.js +93 -0
- package/dist/resources/migrations/space.js +73 -0
- package/dist/resources/migrations/state.js +64 -0
- package/dist/resources/migrations/status.js +39 -0
- package/dist/resources/migrations/synthetic-test-migration.js +93 -0
- package/dist/resources/migrations/types.js +9 -0
- package/dist/resources/presence-internal.js +29 -0
- package/dist/resources/provenance.js +50 -0
- package/dist/resources/rate-limiter.js +8 -1
- package/dist/resources/scoring.js +124 -5
- package/dist/resources/semantic-retrieval-core.js +317 -0
- package/dist/version-check.js +167 -0
- package/dist/version-handshake.js +122 -0
- package/package.json +4 -5
- package/schemas/agent.graphql +7 -0
- package/schemas/event.graphql +5 -0
- package/schemas/federation.graphql +12 -0
- package/schemas/memory.graphql +82 -0
- package/schemas/schema.graphql +5 -43
- package/schemas/workspace.graphql +3 -0
- package/dist/resources/IngestEvents.js +0 -162
- package/dist/resources/IssueTokens.js +0 -19
- package/dist/resources/ObsAgentSnapshot.js +0 -13
- package/dist/resources/ObsEventFeed.js +0 -13
- package/dist/resources/ObsOffice.js +0 -19
- package/dist/resources/ObservationCenter.js +0 -23
- 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
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
//
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
}
|