@tpsdev-ai/flair 0.21.0 → 0.22.1
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 +10 -7
- package/SECURITY.md +24 -2
- package/config.yaml +32 -5
- package/dist/cli.js +1811 -221
- package/dist/deploy.js +3 -4
- package/dist/fleet-presence.js +98 -0
- package/dist/fleet-verify.js +291 -0
- package/dist/mcp-client-assertion.js +364 -0
- package/dist/probe.js +97 -0
- package/dist/rem/runner.js +82 -12
- package/dist/resources/AttentionQuery.js +356 -0
- package/dist/resources/Credential.js +11 -1
- package/dist/resources/Federation.js +23 -0
- package/dist/resources/MCPClientMetadata.js +88 -0
- package/dist/resources/Memory.js +52 -53
- package/dist/resources/MemoryBootstrap.js +422 -76
- package/dist/resources/MemoryReflect.js +105 -49
- package/dist/resources/MemoryUsage.js +104 -0
- package/dist/resources/OAuth.js +8 -1
- package/dist/resources/OrgEvent.js +11 -0
- package/dist/resources/Presence.js +218 -19
- package/dist/resources/RecordUsage.js +230 -0
- package/dist/resources/Relationship.js +98 -25
- package/dist/resources/SemanticSearch.js +85 -317
- package/dist/resources/SkillScan.js +15 -0
- package/dist/resources/WorkspaceState.js +11 -0
- package/dist/resources/agent-auth.js +60 -2
- package/dist/resources/auth-middleware.js +18 -9
- package/dist/resources/bm25.js +12 -6
- package/dist/resources/collision-lib.js +157 -0
- 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/health.js +97 -6
- 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-reflect-lib.js +289 -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 +455 -0
- package/dist/resources/migrations/snapshot.js +127 -0
- package/dist/resources/migrations/source-fields.js +93 -0
- package/dist/resources/migrations/space.js +167 -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-handshake.js +122 -0
- package/package.json +4 -5
- package/schemas/event.graphql +5 -0
- package/schemas/memory.graphql +66 -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,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IngestEvents.ts — Observatory ingestion endpoint.
|
|
3
|
-
*
|
|
4
|
-
* POST /IngestEvents
|
|
5
|
-
* Auth: TPS-Ed25519 signed by the office's private key (verified against
|
|
6
|
-
* ObsOffice.publicKey stored at registration time).
|
|
7
|
-
*
|
|
8
|
-
* Body: {
|
|
9
|
-
* officeId: string;
|
|
10
|
-
* events: OrgEventRecord[];
|
|
11
|
-
* agents: AgentStatus[];
|
|
12
|
-
* syncedAt: string;
|
|
13
|
-
* }
|
|
14
|
-
*
|
|
15
|
-
* Actions:
|
|
16
|
-
* 1. Verify Ed25519 signature against stored office public key
|
|
17
|
-
* 2. Upsert ObsAgentSnapshot for each agent
|
|
18
|
-
* 3. Insert ObsEventFeed for each new event (30-day TTL)
|
|
19
|
-
* 4. Update ObsOffice.lastSeen + agentCount
|
|
20
|
-
*
|
|
21
|
-
* Rate limit: 1 call / 10s per office (enforced by createdAt delta check)
|
|
22
|
-
* Batch limit: 100 events per call
|
|
23
|
-
*/
|
|
24
|
-
import { Resource, databases } from "@harperfast/harper";
|
|
25
|
-
import { createPublicKey, verify } from "node:crypto";
|
|
26
|
-
const BATCH_LIMIT = 100;
|
|
27
|
-
const RATE_LIMIT_MS = 10_000;
|
|
28
|
-
const EVENT_TTL_DAYS = 30;
|
|
29
|
-
function verifyEd25519Signature(publicKeyHex, authHeader, officeId) {
|
|
30
|
-
try {
|
|
31
|
-
// Header format: TPS-Ed25519 officeId:ts:nonce:sig
|
|
32
|
-
const prefix = "TPS-Ed25519 ";
|
|
33
|
-
if (!authHeader.startsWith(prefix))
|
|
34
|
-
return false;
|
|
35
|
-
const parts = authHeader.slice(prefix.length).split(":");
|
|
36
|
-
if (parts.length < 4)
|
|
37
|
-
return false;
|
|
38
|
-
const [id, ts, nonce, ...sigParts] = parts;
|
|
39
|
-
const sig = sigParts.join(":");
|
|
40
|
-
if (id !== officeId)
|
|
41
|
-
return false;
|
|
42
|
-
// Replay protection: reject signatures older than 5 minutes
|
|
43
|
-
const age = Date.now() - Number(ts);
|
|
44
|
-
if (age > 5 * 60 * 1000 || age < -30_000)
|
|
45
|
-
return false;
|
|
46
|
-
const pubKeyBytes = Buffer.from(publicKeyHex.replace(/=\s*/g, ""), "hex");
|
|
47
|
-
const spkiHeader = Buffer.from("302a300506032b6570032100", "hex");
|
|
48
|
-
const pubKey = createPublicKey({ key: Buffer.concat([spkiHeader, pubKeyBytes]), format: "der", type: "spki" });
|
|
49
|
-
const payload = Buffer.from(`${id}:${ts}:${nonce}:POST:/IngestEvents`);
|
|
50
|
-
const sigBuf = Buffer.from(sig, "base64");
|
|
51
|
-
return verify(null, payload, pubKey, sigBuf);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
export class IngestEvents extends Resource {
|
|
58
|
-
// Public POST by design: the handler self-verifies the OFFICE's Ed25519
|
|
59
|
-
// signature against ObsOffice.publicKey (not an agent identity), so it can't use
|
|
60
|
-
// agent auth. allowCreate=true lets the request reach post() where that
|
|
61
|
-
// verification happens — same self-gating pattern as FederationPair/FederationSync.
|
|
62
|
-
allowCreate() {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
async post(body, context) {
|
|
66
|
-
// Harper v5 does not populate this.request on Resource subclasses —
|
|
67
|
-
// getContext() is the only reliable path (the previous
|
|
68
|
-
// `(this as any).request` read was always undefined, so authHeader was
|
|
69
|
-
// always undefined and every request 401'd before reaching the office
|
|
70
|
-
// Ed25519 signature check below).
|
|
71
|
-
const ctx = this.getContext?.();
|
|
72
|
-
const request = ctx?.request ?? ctx;
|
|
73
|
-
const authHeader = request?.headers?.get?.("authorization") ?? request?.headers?.authorization;
|
|
74
|
-
// Parse and validate body
|
|
75
|
-
let payload;
|
|
76
|
-
try {
|
|
77
|
-
payload = (typeof body === "string" ? JSON.parse(body) : body);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return new Response(JSON.stringify({ error: "invalid JSON" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
81
|
-
}
|
|
82
|
-
const { officeId, events = [], agents = [], syncedAt } = payload;
|
|
83
|
-
if (!officeId) {
|
|
84
|
-
return new Response(JSON.stringify({ error: "officeId required" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
85
|
-
}
|
|
86
|
-
// Look up the office
|
|
87
|
-
const office = await databases.flair.ObsOffice.get(officeId).catch(() => null);
|
|
88
|
-
if (!office) {
|
|
89
|
-
return new Response(JSON.stringify({ error: "office not registered — POST /ObsOffice first" }), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
90
|
-
}
|
|
91
|
-
// Verify Ed25519 signature
|
|
92
|
-
if (!authHeader || !verifyEd25519Signature(String(office.publicKey), authHeader, officeId)) {
|
|
93
|
-
return new Response(JSON.stringify({ error: "invalid signature" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
94
|
-
}
|
|
95
|
-
// Rate limit check
|
|
96
|
-
if (office.lastSeen) {
|
|
97
|
-
const msSinceLastSync = Date.now() - new Date(office.lastSeen).getTime();
|
|
98
|
-
if (msSinceLastSync < RATE_LIMIT_MS) {
|
|
99
|
-
return new Response(JSON.stringify({ error: "rate limit: 1 call per 10s" }), { status: 429, headers: { "Content-Type": "application/json" } });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Batch limit
|
|
103
|
-
if (events.length > BATCH_LIMIT) {
|
|
104
|
-
return new Response(JSON.stringify({ error: `batch limit: max ${BATCH_LIMIT} events` }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
105
|
-
}
|
|
106
|
-
const now = new Date().toISOString();
|
|
107
|
-
const expiresAt = new Date(Date.now() + EVENT_TTL_DAYS * 24 * 60 * 60 * 1000).toISOString();
|
|
108
|
-
// Upsert agent snapshots
|
|
109
|
-
for (const agent of agents) {
|
|
110
|
-
if (!agent.agentId)
|
|
111
|
-
continue;
|
|
112
|
-
const snapshotId = `${officeId}:${agent.agentId}`;
|
|
113
|
-
await databases.flair.ObsAgentSnapshot.put({
|
|
114
|
-
id: snapshotId,
|
|
115
|
-
officeId,
|
|
116
|
-
agentId: agent.agentId,
|
|
117
|
-
name: agent.name ?? agent.agentId,
|
|
118
|
-
role: agent.role,
|
|
119
|
-
status: agent.status ?? "unknown",
|
|
120
|
-
model: agent.model,
|
|
121
|
-
lastActivity: agent.lastSeen ?? now,
|
|
122
|
-
lastHeartbeat: now,
|
|
123
|
-
updatedAt: now,
|
|
124
|
-
}).catch((e) => console.warn(`[IngestEvents] snapshot upsert failed for ${snapshotId}: ${e.message}`));
|
|
125
|
-
}
|
|
126
|
-
// Insert event feed entries (skip duplicates)
|
|
127
|
-
let inserted = 0;
|
|
128
|
-
for (const ev of events) {
|
|
129
|
-
if (!ev.id || !ev.kind)
|
|
130
|
-
continue;
|
|
131
|
-
const feedId = `${officeId}:${ev.id}`;
|
|
132
|
-
const existing = await databases.flair.ObsEventFeed.get(feedId).catch(() => null);
|
|
133
|
-
if (existing)
|
|
134
|
-
continue;
|
|
135
|
-
await databases.flair.ObsEventFeed.put({
|
|
136
|
-
id: feedId,
|
|
137
|
-
officeId,
|
|
138
|
-
kind: ev.kind,
|
|
139
|
-
authorId: ev.authorId,
|
|
140
|
-
summary: ev.summary,
|
|
141
|
-
refId: ev.refId,
|
|
142
|
-
scope: ev.scope,
|
|
143
|
-
createdAt: ev.createdAt,
|
|
144
|
-
receivedAt: now,
|
|
145
|
-
expiresAt,
|
|
146
|
-
}).catch((e) => console.warn(`[IngestEvents] event insert failed for ${feedId}: ${e.message}`));
|
|
147
|
-
inserted++;
|
|
148
|
-
}
|
|
149
|
-
// Update office lastSeen + agentCount
|
|
150
|
-
await databases.flair.ObsOffice.put({
|
|
151
|
-
...office,
|
|
152
|
-
status: "online",
|
|
153
|
-
lastSeen: now,
|
|
154
|
-
agentCount: agents.length,
|
|
155
|
-
updatedAt: now,
|
|
156
|
-
}).catch(() => { });
|
|
157
|
-
return new Response(JSON.stringify({ ok: true, events: inserted, agents: agents.length }), {
|
|
158
|
-
status: 200,
|
|
159
|
-
headers: { "Content-Type": "application/json" },
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Resource, databases } from "@harperfast/harper";
|
|
2
|
-
export class IssueTokens extends Resource {
|
|
3
|
-
static loadAsInstance = false;
|
|
4
|
-
async get(_target) {
|
|
5
|
-
const { refresh_token: refreshToken, operation_token: jwt } = await databases.system.hdb_user.operation({ operation: "create_authentication_tokens" }, this.getContext());
|
|
6
|
-
return { refreshToken, jwt };
|
|
7
|
-
}
|
|
8
|
-
async post(_target, data) {
|
|
9
|
-
if (!data?.username || !data?.password) {
|
|
10
|
-
throw new Error("username and password are required");
|
|
11
|
-
}
|
|
12
|
-
const { refresh_token: refreshToken, operation_token: jwt } = await databases.system.hdb_user.operation({
|
|
13
|
-
operation: "create_authentication_tokens",
|
|
14
|
-
username: data.username,
|
|
15
|
-
password: data.password,
|
|
16
|
-
});
|
|
17
|
-
return { refreshToken, jwt };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { databases } from "@harperfast/harper";
|
|
2
|
-
import { allowVerified, allowAdmin } from "./agent-auth.js";
|
|
3
|
-
/**
|
|
4
|
-
* ObsAgentSnapshot — observatory per-agent snapshot read-model. Writes are
|
|
5
|
-
* system-driven (internal sync); agents may read. See ObsOffice for the
|
|
6
|
-
* allowRead=allowVerified security default (flag for Sherlock).
|
|
7
|
-
*/
|
|
8
|
-
export class ObsAgentSnapshot extends databases.flair.ObsAgentSnapshot {
|
|
9
|
-
allowRead() { return allowVerified(this.getContext?.()); }
|
|
10
|
-
allowCreate() { return allowAdmin(this.getContext?.()); }
|
|
11
|
-
allowUpdate() { return allowAdmin(this.getContext?.()); }
|
|
12
|
-
allowDelete() { return allowAdmin(this.getContext?.()); }
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { databases } from "@harperfast/harper";
|
|
2
|
-
import { allowVerified, allowAdmin } from "./agent-auth.js";
|
|
3
|
-
/**
|
|
4
|
-
* ObsEventFeed — observatory event-feed read-model. Writes are system-driven
|
|
5
|
-
* (internal sync); agents may read. See ObsOffice for the allowRead=allowVerified
|
|
6
|
-
* security default (flag for Sherlock).
|
|
7
|
-
*/
|
|
8
|
-
export class ObsEventFeed extends databases.flair.ObsEventFeed {
|
|
9
|
-
allowRead() { return allowVerified(this.getContext?.()); }
|
|
10
|
-
allowCreate() { return allowAdmin(this.getContext?.()); }
|
|
11
|
-
allowUpdate() { return allowAdmin(this.getContext?.()); }
|
|
12
|
-
allowDelete() { return allowAdmin(this.getContext?.()); }
|
|
13
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { databases } from "@harperfast/harper";
|
|
2
|
-
import { allowVerified, allowAdmin } from "./agent-auth.js";
|
|
3
|
-
/**
|
|
4
|
-
* ObsOffice — observatory office-layout read-model. Writes are system-driven
|
|
5
|
-
* (internal sync); agents may read. Self-authorizes now that the global gate is
|
|
6
|
-
* non-rejecting.
|
|
7
|
-
*
|
|
8
|
-
* SECURITY DEFAULT (flag for Sherlock): allowRead is allowVerified (anonymous
|
|
9
|
-
* denied). The ObservationCenter HTML shell is public but its data API calls
|
|
10
|
-
* authenticate (admin-pass). If the public Office Space renderer needs anonymous
|
|
11
|
-
* Obs reads, relax to allowRead=true WITH field-allowlisting (as Presence.get does)
|
|
12
|
-
* — do NOT expose raw rows publicly.
|
|
13
|
-
*/
|
|
14
|
-
export class ObsOffice extends databases.flair.ObsOffice {
|
|
15
|
-
allowRead() { return allowVerified(this.getContext?.()); }
|
|
16
|
-
allowCreate() { return allowAdmin(this.getContext?.()); }
|
|
17
|
-
allowUpdate() { return allowAdmin(this.getContext?.()); }
|
|
18
|
-
allowDelete() { return allowAdmin(this.getContext?.()); }
|
|
19
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Resource } from "@harperfast/harper";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
5
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const HTML_PATH = resolve(HERE, "..", "..", "ui", "observation-center.html");
|
|
7
|
-
const HTML = readFileSync(HTML_PATH, "utf8");
|
|
8
|
-
/**
|
|
9
|
-
* GET /ObservationCenter — serves the read-only dashboard shell.
|
|
10
|
-
* The HTML handles its own Basic-auth prompt and polls authenticated JSON endpoints.
|
|
11
|
-
*/
|
|
12
|
-
export class ObservationCenter extends Resource {
|
|
13
|
-
// The dashboard shell is just HTML + inline JS; the JS prompts the user
|
|
14
|
-
// for admin-pass and authenticates each API call. The shell itself is
|
|
15
|
-
// intentionally public so it can render before auth happens.
|
|
16
|
-
allowRead() { return true; }
|
|
17
|
-
async get() {
|
|
18
|
-
return new Response(HTML, {
|
|
19
|
-
status: 200,
|
|
20
|
-
headers: { "content-type": "text/html; charset=utf-8" },
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|