@tpsdev-ai/flair 0.29.0 → 0.31.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 +194 -377
- package/dist/cli-shim.cjs +1 -1
- package/dist/cli.js +1567 -366
- package/dist/deploy.js +241 -43
- package/dist/fabric-upgrade.js +77 -24
- package/dist/federation/scheduler.js +500 -0
- package/dist/fleet-verify.js +3 -3
- package/dist/install/clients.js +111 -53
- package/dist/lib/mcp-enable.js +2 -2
- package/dist/lib/mcp-spec.js +128 -0
- package/dist/lib/safe-snapshot-extract.js +231 -0
- package/dist/lib/scheduler-platform.js +128 -0
- package/dist/lib/xml-escape.js +54 -0
- package/dist/rem/scheduler.js +212 -55
- package/dist/rem/snapshot.js +13 -0
- package/dist/replication-convergence.js +505 -0
- package/dist/resources/A2AAdapter.js +1 -1
- 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 +1 -1
- package/dist/resources/AdminPrincipals.js +1 -1
- package/dist/resources/Agent.js +1 -1
- package/dist/resources/AgentCard.js +1 -1
- package/dist/resources/AgentSeed.js +1 -1
- package/dist/resources/AttentionQuery.js +1 -1
- package/dist/resources/Credential.js +1 -1
- package/dist/resources/Federation.js +1 -1
- package/dist/resources/Instance.js +1 -1
- package/dist/resources/Integration.js +1 -1
- package/dist/resources/MCPClientMetadata.js +1 -1
- package/dist/resources/Memory.js +1 -1
- package/dist/resources/MemoryBootstrap.js +8 -9
- package/dist/resources/MemoryCandidate.js +168 -0
- package/dist/resources/MemoryConsolidate.js +1 -1
- package/dist/resources/MemoryDedupStats.js +1 -1
- package/dist/resources/MemoryFeed.js +1 -1
- package/dist/resources/MemoryGrant.js +1 -1
- package/dist/resources/MemoryMaintenance.js +1 -1
- package/dist/resources/MemoryReflect.js +2 -2
- package/dist/resources/MemoryReindex.js +1 -1
- package/dist/resources/MemoryUsage.js +1 -1
- package/dist/resources/OAuth.js +1 -1
- package/dist/resources/OAuthClient.js +1 -1
- package/dist/resources/OrgEvent.js +1 -1
- package/dist/resources/OrgEventCatchup.js +1 -1
- package/dist/resources/OrgEventMaintenance.js +1 -1
- package/dist/resources/PairingToken.js +1 -1
- package/dist/resources/Peer.js +1 -1
- package/dist/resources/Presence.js +3 -3
- package/dist/resources/RecordUsage.js +1 -1
- package/dist/resources/Relationship.js +1 -1
- package/dist/resources/SemanticSearch.js +18 -46
- package/dist/resources/SkillScan.js +1 -1
- package/dist/resources/Soul.js +1 -1
- package/dist/resources/SoulFeed.js +1 -1
- package/dist/resources/WorkspaceLatest.js +1 -1
- package/dist/resources/WorkspaceState.js +1 -1
- package/dist/resources/XAA.js +1 -1
- package/dist/resources/a2a-url.js +1 -1
- package/dist/resources/abstention.js +1 -1
- package/dist/resources/agent-auth.js +1 -1
- package/dist/resources/agentcard-fields.js +1 -1
- package/dist/resources/auth-middleware.js +1 -1
- package/dist/resources/embeddings-boot.js +14 -16
- package/dist/resources/embeddings-provider.js +15 -12
- package/dist/resources/federation-cleanup.js +5 -5
- package/dist/resources/federation-nonce-store.js +5 -5
- package/dist/resources/health.js +53 -25
- package/dist/resources/in-process.js +225 -0
- package/dist/resources/instance-identity.js +1 -1
- package/dist/resources/mcp-client-metadata-fields.js +1 -1
- package/dist/resources/mcp-handler.js +1 -1
- package/dist/resources/mcp-oauth.js +2 -2
- package/dist/resources/mcp-tools.js +23 -17
- package/dist/resources/memory-read-scope.js +1 -1
- package/dist/resources/memory-reflect-lib.js +3 -3
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +81 -11
- package/dist/resources/migrations/data-dir.js +205 -0
- package/dist/resources/migrations/embedding-stamp.js +3 -3
- package/dist/resources/migrations/graph-heal.js +2 -2
- package/dist/resources/migrations/ledger.js +1 -1
- package/dist/resources/migrations/progress.js +33 -0
- package/dist/resources/migrations/registry.js +14 -5
- package/dist/resources/migrations/runner.js +93 -13
- package/dist/resources/migrations/state.js +13 -2
- package/dist/resources/migrations/synthetic-test-migration.js +1 -1
- package/dist/resources/migrations/visibility-backfill.js +232 -0
- package/dist/resources/models-dir.js +18 -9
- package/dist/resources/record-types.js +28 -0
- package/dist/resources/semantic-retrieval-core.js +6 -5
- package/dist/resources/usage-recording.js +1 -1
- package/dist/src/lib/scheduler-platform.js +128 -0
- package/dist/src/lib/xml-escape.js +54 -0
- package/dist/src/rem/scheduler.js +397 -0
- package/docs/deploying-on-fabric.md +267 -0
- package/docs/deployment.md +6 -0
- package/docs/embedding-in-a-harper-app.md +299 -0
- package/docs/federation.md +61 -4
- package/docs/integrations.md +3 -0
- package/docs/mcp-clients.md +16 -7
- package/docs/quickstart.md +80 -54
- package/docs/releasing.md +72 -38
- package/docs/supply-chain-policy.md +40 -4
- package/docs/troubleshooting.md +24 -0
- package/docs/upgrade.md +100 -5
- package/package.json +4 -14
- package/schemas/memory.graphql +9 -1
- package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
- package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
- package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
- package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
- package/dist/resources/rerank-provider.js +0 -569
- package/docs/rerank-provisioning.md +0 -101
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resource, databases } from "
|
|
1
|
+
import { Resource, databases } from "harper";
|
|
2
2
|
import { allowVerified, resolveAgentAuth } from "./agent-auth.js";
|
|
3
3
|
import { getEmbedding } from "./embeddings-provider.js";
|
|
4
4
|
import { wrapUntrusted } from "./content-safety.js";
|
|
@@ -11,7 +11,7 @@ import { buildCollisionEntries, buildEntityMatchCondition, freshPresenceByAgent,
|
|
|
11
11
|
// The bounded HNSW candidate-pool retrieval for the task-relevant/teammate/
|
|
12
12
|
// collision surfaces (flair-bootstrap-scale-fix) — the SAME pure core
|
|
13
13
|
// SemanticSearch.ts's post() wraps, called bare here so an internal
|
|
14
|
-
// bootstrap call never trips SemanticSearch's rate-limit
|
|
14
|
+
// bootstrap call never trips SemanticSearch's rate-limit or
|
|
15
15
|
// retrievalCount hit-tracking side effects (see resources/
|
|
16
16
|
// semantic-retrieval-core.ts's module doc for the full boundary).
|
|
17
17
|
import { retrieveCandidates, DEFAULT_SELECT } from "./semantic-retrieval-core.js";
|
|
@@ -619,12 +619,11 @@ export class BootstrapMemories extends Resource {
|
|
|
619
619
|
queryEmbedding,
|
|
620
620
|
conditions: [scope.condition],
|
|
621
621
|
limit: candidatePoolK,
|
|
622
|
-
// HNSW-leg pushdown ONLY (K&S verdict): no BM25 fusion
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
// follow-ons, gated on their own harness runs.
|
|
622
|
+
// HNSW-leg pushdown ONLY (K&S verdict): no BM25 fusion for
|
|
623
|
+
// bootstrap — a different cost profile, since BM25 over the org
|
|
624
|
+
// corpus for a one-shot session-load could be MORE expensive than
|
|
625
|
+
// HNSW-only unless cached across sessions. Turning it on is an
|
|
626
|
+
// explicit opt-in follow-on, gated on its own harness run.
|
|
628
627
|
hybrid: false,
|
|
629
628
|
// Per-set (this K-bounded pool only, never cross-applied to the
|
|
630
629
|
// permanent/recent/predicted sets above) — see this function's
|
|
@@ -679,7 +678,7 @@ export class BootstrapMemories extends Resource {
|
|
|
679
678
|
// `candidates` are already `_score`-sorted best-first, so filtering
|
|
680
679
|
// preserves that order). `retrieveCandidates()`'s cosine similarity
|
|
681
680
|
// replaces the raw JS dot product as the ranking signal (HNSW-only,
|
|
682
|
-
// no BM25
|
|
681
|
+
// no BM25) — the K&S-ratified, closest-to-a-wash choice; the
|
|
683
682
|
// recall harness gates any regression from this ranking-signal
|
|
684
683
|
// change (magnitude-sensitive dot product → normalized cosine).
|
|
685
684
|
const scored = candidates
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { databases } from "harper";
|
|
2
|
+
import { resolveAgentAuth } from "./agent-auth.js";
|
|
3
|
+
import { makeAuthGate, makeReadScope, makeByIdReadGate, resolveAuthGate, stampAttribution, FORBIDDEN, UNAUTH, } from "./record-type-kit.js";
|
|
4
|
+
import { RECORD_TYPES } from "./record-types.js";
|
|
5
|
+
// Parameterized from RECORD_TYPES.MemoryCandidate (record-types slice 2,
|
|
6
|
+
// flair#520) rather than a hand-typed "owner-only" literal — the registry is
|
|
7
|
+
// the single source of truth this class draws its read-scope mode from.
|
|
8
|
+
// Exported solely so test/unit/record-types-registry.test.ts's drift
|
|
9
|
+
// tripwire can introspect the composed resolver's tagged `.mode`/
|
|
10
|
+
// `.ownerField` against RECORD_TYPES.MemoryCandidate — not for any other
|
|
11
|
+
// runtime consumer.
|
|
12
|
+
export const memoryCandidateReadScope = makeReadScope(RECORD_TYPES.MemoryCandidate.readScope, RECORD_TYPES.MemoryCandidate.ownerField);
|
|
13
|
+
const memoryCandidateByIdReadGate = makeByIdReadGate(memoryCandidateReadScope);
|
|
14
|
+
// See makeAuthGate's doc (record-type-kit.ts): must be wired as a genuine
|
|
15
|
+
// prototype method below, never a class-field assignment.
|
|
16
|
+
const memoryCandidateAuthGate = makeAuthGate();
|
|
17
|
+
/**
|
|
18
|
+
* MemoryCandidate resource — closes flair#849.
|
|
19
|
+
*
|
|
20
|
+
* The schema (schemas/memory.graphql) declared `@table` for this type with
|
|
21
|
+
* no `@export` and no resource file at all, so it had NO REST surface —
|
|
22
|
+
* `flair rem candidates`/`promote`/`reject` (src/cli.ts) all 404'd on every
|
|
23
|
+
* call, a clean-room dogfood blocker. Adding bare `@export` would have
|
|
24
|
+
* reopened the exact P0 leak the memory-soul-read-gate family fix closed for
|
|
25
|
+
* every other agent-facing table: Harper's `Resource` default is
|
|
26
|
+
* `allowRead/allowCreate/allowUpdate/allowDelete(user) { return
|
|
27
|
+
* user?.role.permission.super_user }` (test/unit/resource-allow-decision.test.ts
|
|
28
|
+
* enforces every exported resource makes an explicit allow-decision instead
|
|
29
|
+
* of falling through to that default), so an unguarded `@export` is reachable
|
|
30
|
+
* by `authorizeLocal`'s forged loopback super_user and, once the global gate
|
|
31
|
+
* stops rejecting a genuinely anonymous remote caller, by that caller too.
|
|
32
|
+
*
|
|
33
|
+
* Read: identity-gated (allowRead — verified agents, admins, and trusted
|
|
34
|
+
* internal calls pass; anonymous HTTP denied) AND per-agent scoped
|
|
35
|
+
* ("owner-only" — see RECORD_TYPES.MemoryCandidate's doc comment for why: a
|
|
36
|
+
* candidate is an unreviewed draft distillation, not yet promoted, and must
|
|
37
|
+
* not be org-readable). Same "owner-only" composition Relationship.ts uses,
|
|
38
|
+
* parameterized from the registry — this is NOT MemoryGrant.ts's bespoke
|
|
39
|
+
* owner-OR-grantee model (MemoryCandidate has no second-party field).
|
|
40
|
+
*
|
|
41
|
+
* Write: post()/put()/delete() all self-enforce inline (no allowCreate/
|
|
42
|
+
* allowUpdate/allowDelete wrapper — same deliberate omission Memory.ts's
|
|
43
|
+
* allowRead() doc explains: adding an unverified allow* gate on top of
|
|
44
|
+
* self-enforcing methods risks regressing owner writes). Non-admin agents
|
|
45
|
+
* may create/modify only their OWN candidates (`agentId` no-forge
|
|
46
|
+
* attribution, "validate-truthy" — present+mismatched agentId is rejected,
|
|
47
|
+
* absent passes through, matching Memory.post()/put()'s exact idiom for the
|
|
48
|
+
* same single-owner-field shape) and may delete only their own candidates
|
|
49
|
+
* (mirrors MemoryGrant.ts's delete() ownership check). Internal calls (the
|
|
50
|
+
* FLAIR-NIGHTLY-REM staging writer — resources/MemoryReflect.ts's
|
|
51
|
+
* `databases.flair.MemoryCandidate.put(row)` — and resources/health.ts's
|
|
52
|
+
* pending-count read) and admin agents pass every method unfiltered, exactly
|
|
53
|
+
* like every other table this pattern covers.
|
|
54
|
+
*/
|
|
55
|
+
export class MemoryCandidate extends databases.flair.MemoryCandidate {
|
|
56
|
+
/**
|
|
57
|
+
* Self-authorize now that the global gate is non-rejecting (memory-soul-
|
|
58
|
+
* read-gate family fix — same pattern as Memory.ts/Relationship.ts/
|
|
59
|
+
* WorkspaceState.ts/OrgEvent.ts/Soul.ts/MemoryGrant.ts). Harper routes
|
|
60
|
+
* `GET /MemoryCandidate/<id>` to get() and the collection describe
|
|
61
|
+
* (`GET /MemoryCandidate`) outside search(), so both must be gated here,
|
|
62
|
+
* not just search(). Per-record owner scoping happens in get() below; the
|
|
63
|
+
* collection scope is still in search().
|
|
64
|
+
*/
|
|
65
|
+
allowRead() { return memoryCandidateAuthGate.call(this); }
|
|
66
|
+
/**
|
|
67
|
+
* Override get() to scope by-id reads the same way search() scopes
|
|
68
|
+
* collection reads. Never distinguishes "doesn't exist" from "exists but
|
|
69
|
+
* not yours" — both return 404, never 403, so a denied caller can't use
|
|
70
|
+
* get() to enumerate other agents' candidate ids (same discipline
|
|
71
|
+
* makeByIdReadGate documents, and MemoryGrant.ts's get() hand-rolls
|
|
72
|
+
* identically). `flair rem promote`/`flair rem reject` (src/cli.ts) both
|
|
73
|
+
* fetch a candidate by id before writing back — this is their read path.
|
|
74
|
+
*/
|
|
75
|
+
async get(target) {
|
|
76
|
+
// Collection / query reads arrive as a RequestTarget with
|
|
77
|
+
// `isCollection === true`, and are governed by search() (same owner
|
|
78
|
+
// scoping). Only a genuine by-id get is ownership-checked below.
|
|
79
|
+
if (!target || (typeof target === "object" && target.isCollection)) {
|
|
80
|
+
return this.search(target);
|
|
81
|
+
}
|
|
82
|
+
return memoryCandidateByIdReadGate.call(this, target, (t) => super.get(t));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Scope collection reads to the caller's own candidates. This is the
|
|
86
|
+
* handler `flair rem candidates` reaches via `POST
|
|
87
|
+
* /MemoryCandidate/search_by_conditions` (src/cli.ts) — the agentId
|
|
88
|
+
* condition is wrapped as the OUTERMOST `and` block (Relationship.ts's
|
|
89
|
+
* same "Security Critical" discipline) so a caller-supplied `operator:
|
|
90
|
+
* "or"` in the request body cannot boolean-inject past the owner scope.
|
|
91
|
+
*/
|
|
92
|
+
async search(query) {
|
|
93
|
+
const ctx = this.getContext?.();
|
|
94
|
+
const gate = await resolveAuthGate(ctx, UNAUTH());
|
|
95
|
+
if (gate.kind === "denied")
|
|
96
|
+
return gate.response;
|
|
97
|
+
if (gate.kind === "unfiltered")
|
|
98
|
+
return super.search(query);
|
|
99
|
+
// Non-admin agent: scope to own candidates only.
|
|
100
|
+
const scope = await memoryCandidateReadScope(gate.agentId);
|
|
101
|
+
const agentCondition = scope.condition;
|
|
102
|
+
if (!query?.conditions) {
|
|
103
|
+
return super.search({ conditions: [agentCondition], ...(query || {}) });
|
|
104
|
+
}
|
|
105
|
+
return super.search({
|
|
106
|
+
...query,
|
|
107
|
+
conditions: [agentCondition, { conditions: query.conditions, operator: query.operator || "and" }],
|
|
108
|
+
operator: "and",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* No shipped CLI path calls POST today (the FLAIR-NIGHTLY-REM staging
|
|
113
|
+
* writer uses an internal `.put()`, not `.post()` — see MemoryReflect.ts).
|
|
114
|
+
* Guarded anyway, on the same "don't leave any exported verb unguarded"
|
|
115
|
+
* discipline the rest of this file follows: anonymous denied; a non-admin
|
|
116
|
+
* agent may only stage a candidate attributed to itself.
|
|
117
|
+
*/
|
|
118
|
+
async post(content) {
|
|
119
|
+
const ctx = this.getContext?.();
|
|
120
|
+
const auth = await resolveAgentAuth(ctx);
|
|
121
|
+
if (auth.kind === "anonymous")
|
|
122
|
+
return UNAUTH();
|
|
123
|
+
const attr = stampAttribution(auth, content, RECORD_TYPES.MemoryCandidate.ownerField, RECORD_TYPES.MemoryCandidate.attribution.post, "forbidden: cannot stage a memory candidate for another agent");
|
|
124
|
+
if (attr.denied)
|
|
125
|
+
return attr.denied;
|
|
126
|
+
return super.post(content);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `flair rem promote`/`flair rem reject` (src/cli.ts) both PUT the
|
|
130
|
+
* candidate row back with an updated status/reviewerId/reviewRationale/
|
|
131
|
+
* decidedAt, spreading the existing record (agentId included, unchanged).
|
|
132
|
+
* Non-admin: the no-forge attribution check rejects a PRESENT, mismatched
|
|
133
|
+
* agentId (an agent can't repurpose this endpoint to rewrite another
|
|
134
|
+
* agent's candidate) but never stamps one in — the promote/reject flow
|
|
135
|
+
* always carries the original agentId forward untouched.
|
|
136
|
+
*/
|
|
137
|
+
async put(content) {
|
|
138
|
+
const ctx = this.getContext?.();
|
|
139
|
+
const auth = await resolveAgentAuth(ctx);
|
|
140
|
+
if (auth.kind === "anonymous")
|
|
141
|
+
return UNAUTH();
|
|
142
|
+
const attr = stampAttribution(auth, content, RECORD_TYPES.MemoryCandidate.ownerField, RECORD_TYPES.MemoryCandidate.attribution.put, "forbidden: cannot modify a memory candidate owned by another agent");
|
|
143
|
+
if (attr.denied)
|
|
144
|
+
return attr.denied;
|
|
145
|
+
return super.put(content);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* No shipped CLI path calls DELETE today. Guarded on the same discipline
|
|
149
|
+
* as post() above — mirrors MemoryGrant.ts's delete(): owner-only,
|
|
150
|
+
* admin/internal unfiltered, a genuinely-missing id is a clean no-op
|
|
151
|
+
* super.delete() rather than a FORBIDDEN (no existence oracle).
|
|
152
|
+
*/
|
|
153
|
+
async delete(id, context) {
|
|
154
|
+
const ctx = this.getContext?.();
|
|
155
|
+
const gate = await resolveAuthGate(ctx, UNAUTH());
|
|
156
|
+
if (gate.kind === "denied")
|
|
157
|
+
return gate.response;
|
|
158
|
+
if (gate.kind === "unfiltered")
|
|
159
|
+
return super.delete(id, context);
|
|
160
|
+
const record = await super.get(id);
|
|
161
|
+
if (!record)
|
|
162
|
+
return super.delete(id, context);
|
|
163
|
+
if (record[RECORD_TYPES.MemoryCandidate.ownerField] !== gate.agentId) {
|
|
164
|
+
return FORBIDDEN("forbidden: cannot delete a memory candidate owned by another agent");
|
|
165
|
+
}
|
|
166
|
+
return super.delete(id, context);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* candidates Array<{ memory, suggestion, reason }>
|
|
16
16
|
* prompt string
|
|
17
17
|
*/
|
|
18
|
-
import { Resource, databases } from "
|
|
18
|
+
import { Resource, databases } from "harper";
|
|
19
19
|
import { isAdmin, allowVerified } from "./agent-auth.js";
|
|
20
20
|
import { evaluate, parseDuration } from "./memory-consolidate-lib.js";
|
|
21
21
|
export class ConsolidateMemories extends Resource {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* agent's own memories; this one is never scoped that way — it reads every
|
|
53
53
|
* agent's embeddings — so it follows the admin-gated precedent instead.
|
|
54
54
|
*/
|
|
55
|
-
import { Resource, databases } from "
|
|
55
|
+
import { Resource, databases } from "harper";
|
|
56
56
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
57
57
|
import { dirname } from "node:path";
|
|
58
58
|
import { allowAdmin } from "./agent-auth.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resource, databases } from "
|
|
1
|
+
import { Resource, databases } from "harper";
|
|
2
2
|
import { allowVerified } from "./agent-auth.js";
|
|
3
3
|
import { computeContentHash, findExistingMemoryByContentHash } from "./memory-feed-lib.js";
|
|
4
4
|
export class FeedMemories extends Resource {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { databases } from "
|
|
1
|
+
import { databases } from "harper";
|
|
2
2
|
import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
|
|
3
3
|
const FORBIDDEN = (msg) => new Response(JSON.stringify({ error: msg }), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
4
4
|
const UNAUTH = () => new Response(JSON.stringify({ error: "authentication required" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Migrated to the standard `extends Resource` shape with `allowCreate()`
|
|
17
17
|
* to gate auth correctly.
|
|
18
18
|
*/
|
|
19
|
-
import { Resource, databases } from "
|
|
19
|
+
import { Resource, databases } from "harper";
|
|
20
20
|
import { isAdmin } from "./agent-auth.js";
|
|
21
21
|
export class MemoryMaintenance extends Resource {
|
|
22
22
|
/** POST requires auth — either an agent acting on its own memories, or admin. */
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* Resource }` outright — see test/unit/resource-allow.test.ts). This
|
|
37
37
|
* resource is a thin orchestrator over the lib's tested functions.
|
|
38
38
|
*/
|
|
39
|
-
import { Resource, databases, models, logger } from "
|
|
39
|
+
import { Resource, databases, models, logger } from "harper";
|
|
40
40
|
import { randomBytes } from "node:crypto";
|
|
41
41
|
import { isAdmin, allowVerified } from "./agent-auth.js";
|
|
42
42
|
import { patchRecordSilent } from "./table-helpers.js";
|
|
@@ -143,7 +143,7 @@ export class ReflectMemories extends Resource {
|
|
|
143
143
|
existingPendingClaims.push(c.claim);
|
|
144
144
|
}
|
|
145
145
|
const toStage = dedupeCandidates(outcome.candidates, existingPendingClaims);
|
|
146
|
-
// generatedBy: GenerateResult in the pinned
|
|
146
|
+
// generatedBy: GenerateResult in the pinned harper 5.1.17 has
|
|
147
147
|
// no model/backend-id field (content/finishReason/usage/toolCalls/trace
|
|
148
148
|
// only) — the "from the generate result if available" branch is
|
|
149
149
|
// unreachable in this version, so this always falls back to the
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* This endpoint is idempotent: running it again finds zero drift.
|
|
25
25
|
*/
|
|
26
|
-
import { Resource, databases } from "
|
|
26
|
+
import { Resource, databases } from "harper";
|
|
27
27
|
import { isAdmin, allowAdmin } from "./agent-auth.js";
|
|
28
28
|
export class MemoryReindex extends Resource {
|
|
29
29
|
// Admin-only: permit verified ADMIN agents (Basic-admin is super_user and
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* to expose "which agent used which memory" cross-agent, and narrowing this
|
|
42
42
|
* costs nothing.
|
|
43
43
|
*/
|
|
44
|
-
import { databases } from "
|
|
44
|
+
import { databases } from "harper";
|
|
45
45
|
import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
|
|
46
46
|
const FORBIDDEN = (msg) => new Response(JSON.stringify({ error: msg }), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
47
47
|
const UNAUTH = () => new Response(JSON.stringify({ error: "authentication required" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
package/dist/resources/OAuth.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* requests slipped through once the gate stopped rejecting. resolveAgentAuth
|
|
11
11
|
* distinguishes internal/agent/anonymous explicitly.
|
|
12
12
|
*/
|
|
13
|
-
import { databases } from "
|
|
13
|
+
import { databases } from "harper";
|
|
14
14
|
import { resolveAgentAuth } from "./agent-auth.js";
|
|
15
15
|
import { invalidEntitiesResponse } from "./entity-vocab.js";
|
|
16
16
|
import { makeAuthGate, resolveAuthGate, stampAttribution, FORBIDDEN, UNAUTH, } from "./record-type-kit.js";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Sorted by createdAt ascending (oldest first for in-order processing).
|
|
10
10
|
* Limit 50 events max.
|
|
11
11
|
*/
|
|
12
|
-
import { Resource, databases } from "
|
|
12
|
+
import { Resource, databases } from "harper";
|
|
13
13
|
import { allowVerified, resolveAgentAuth } from "./agent-auth.js";
|
|
14
14
|
export class OrgEventCatchup extends Resource {
|
|
15
15
|
// Self-authorize via the Ed25519 agent verify (auth reshape removes the gate's
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* POST /OrgEventMaintenance/ — deletes all records where expiresAt < now.
|
|
5
5
|
* Auth: admin only.
|
|
6
6
|
*/
|
|
7
|
-
import { Resource, databases } from "
|
|
7
|
+
import { Resource, databases } from "harper";
|
|
8
8
|
import { allowAdmin } from "./agent-auth.js";
|
|
9
9
|
export class OrgEventMaintenance extends Resource {
|
|
10
10
|
// Admin-only: permit verified ADMIN agents (Basic-admin is super_user and
|
package/dist/resources/Peer.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* content. get() additionally gates currentTask itself to verified
|
|
27
27
|
* in-org agents only; see get()'s inline comment.
|
|
28
28
|
*/
|
|
29
|
-
import { databases } from "
|
|
29
|
+
import { databases } from "harper";
|
|
30
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
31
|
import { dirname, join } from "node:path";
|
|
32
32
|
import { fileURLToPath } from "node:url";
|
|
@@ -80,7 +80,7 @@ function resolveVersion() {
|
|
|
80
80
|
return process.env.npm_package_version ?? "dev";
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Resolve the running
|
|
83
|
+
* Resolve the running harper version. Unlike flair's own
|
|
84
84
|
* package.json (readable via a plain relative path above), Harper's package
|
|
85
85
|
* only exports "." → dist/index.js — there's no "./package.json" subpath, so
|
|
86
86
|
* requiring it directly throws (Node's exports-map enforcement). Resolve the
|
|
@@ -93,7 +93,7 @@ function resolveVersion() {
|
|
|
93
93
|
function resolveHarperVersion() {
|
|
94
94
|
try {
|
|
95
95
|
const req = createRequire(import.meta.url);
|
|
96
|
-
const mainPath = req.resolve("
|
|
96
|
+
const mainPath = req.resolve("harper");
|
|
97
97
|
const pkgPath = join(dirname(mainPath), "..", "package.json");
|
|
98
98
|
if (existsSync(pkgPath)) {
|
|
99
99
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
* sanitized (control-character-stripped, length-capped) and stored as-is,
|
|
94
94
|
* for audit/analytics only. Treat it as untrusted data, always.
|
|
95
95
|
*/
|
|
96
|
-
import { Resource } from "
|
|
96
|
+
import { Resource } from "harper";
|
|
97
97
|
import { resolveAgentAuth } from "./agent-auth.js";
|
|
98
98
|
import { checkRateLimit, rateLimitResponse } from "./rate-limiter.js";
|
|
99
99
|
import { recordUsageContribution, MAX_USAGE_IDS_PER_CALL } from "./usage-recording.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { databases } from "
|
|
1
|
+
import { databases } from "harper";
|
|
2
2
|
import { resolveAgentAuth } from "./agent-auth.js";
|
|
3
3
|
import { checkRateLimit, rateLimitResponse } from "./rate-limiter.js";
|
|
4
4
|
import { localInstanceId } from "./instance-identity.js";
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Resource, databases } from "
|
|
1
|
+
import { Resource, databases } from "harper";
|
|
2
2
|
import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
|
|
3
3
|
import { getEmbedding, getMode } from "./embeddings-provider.js";
|
|
4
4
|
import { patchRecord } from "./table-helpers.js";
|
|
5
5
|
import { checkRateLimit, rateLimitResponse } from "./rate-limiter.js";
|
|
6
6
|
import { resolveReadScope } from "./memory-read-scope.js";
|
|
7
|
-
import { isRerankEnabled, getRerankTopN, getRerankBudgetMs, getRerankMinCandidates, rerankCandidates, } from "./rerank-provider.js";
|
|
8
7
|
// The BM25 + union-RRF hybrid path is feature-flagged via hybridEnabled()
|
|
9
8
|
// (imported from ./bm25 — Harper-free so it's unit-testable). Default is ON as
|
|
10
9
|
// of 2026-07-08 (see ./bm25.ts's hybridEnabled() doc); set
|
|
@@ -15,7 +14,7 @@ import { hybridEnabled } from "./bm25.js";
|
|
|
15
14
|
// supersede/isAllowed) now lives in the pure, side-effect-free
|
|
16
15
|
// retrieveCandidates() core (flair-bootstrap-scale-fix, Kern-approved
|
|
17
16
|
// extraction) — MemoryBootstrap.ts calls the SAME core bare, without
|
|
18
|
-
// tripping this file's rate-limit/
|
|
17
|
+
// tripping this file's rate-limit/hit-tracking side effects. See
|
|
19
18
|
// resources/semantic-retrieval-core.ts's module doc for the full boundary.
|
|
20
19
|
import { retrieveCandidates, DEFAULT_SELECT } from "./semantic-retrieval-core.js";
|
|
21
20
|
import { attachTrust } from "./trust-block.js";
|
|
@@ -181,29 +180,18 @@ export class SemanticSearch extends Resource {
|
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
const hybrid = hybridEnabled();
|
|
184
|
-
// When the reranker is on, widen the legacy HNSW fetch so it has a deeper
|
|
185
|
-
// pool to re-score (retrieve topN → rerank → slice to limit). Decoupled
|
|
186
|
-
// from CANDIDATE_MULTIPLIER so composite re-ranking keeps its existing
|
|
187
|
-
// headroom. Scoped to the legacy (non-hybrid) vector path below — the
|
|
188
|
-
// hybrid path's candidate pool is already governed by CANDIDATE_MULTIPLIER
|
|
189
|
-
// (semantic leg) + SEM_LIMIT (BM25 leg) via RRF union; the
|
|
190
|
-
// reranker still applies to its output further down regardless of which
|
|
191
|
-
// path produced `filteredResults`.
|
|
192
|
-
const rerankOn = isRerankEnabled();
|
|
193
|
-
const rerankTopN = getRerankTopN();
|
|
194
183
|
// The overfetch policy (how many raw candidates to pull from the
|
|
195
184
|
// HNSW/BM25 legs relative to what the caller ultimately wants) is THIS
|
|
196
185
|
// wrapper's decision — retrieveCandidates() never multiplies its `limit`
|
|
197
186
|
// param internally (see resources/semantic-retrieval-core.ts's doc), so
|
|
198
187
|
// every caller (this one, and MemoryBootstrap's own K formula) computes
|
|
199
|
-
// its own fetch depth.
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
: (rerankOn ? Math.max(limit * CANDIDATE_MULTIPLIER, rerankTopN) : limit * CANDIDATE_MULTIPLIER);
|
|
188
|
+
// its own fetch depth. Both the hybrid path (CANDIDATE_MULTIPLIER on the
|
|
189
|
+
// semantic leg + SEM_LIMIT on the BM25 leg, fused by RRF) and the legacy
|
|
190
|
+
// vector-only path overfetch by the same multiplier, which is what gives
|
|
191
|
+
// composite re-scoring headroom to reorder before the final slice.
|
|
192
|
+
const candidateLimit = limit * CANDIDATE_MULTIPLIER;
|
|
205
193
|
const ctx = this.getContext?.();
|
|
206
|
-
|
|
194
|
+
const filteredResults = await retrieveCandidates({
|
|
207
195
|
queryEmbedding: qEmb,
|
|
208
196
|
q,
|
|
209
197
|
conditions,
|
|
@@ -233,10 +221,10 @@ export class SemanticSearch extends Resource {
|
|
|
233
221
|
withSemSimilarity: abstain || includeTrust,
|
|
234
222
|
});
|
|
235
223
|
// ─── flair#744 slice 2 — first-class abstention ("no memory covers this")
|
|
236
|
-
// Opt-in only. Evaluated on the RETRIEVED candidate pool, BEFORE the
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
224
|
+
// Opt-in only. Evaluated on the RETRIEVED candidate pool, BEFORE the final
|
|
225
|
+
// slice / hit-tracking, so an abstaining recall never bumps retrievalCount
|
|
226
|
+
// for memories it declines to surface. The decision reads ONLY the best
|
|
227
|
+
// absolute semantic similarity
|
|
240
228
|
// (never any principal/authority signal — abstention.ts is pure and
|
|
241
229
|
// authority-free), against the single GLOBAL threshold. Default OFF ⇒ this
|
|
242
230
|
// whole block is skipped and the response is byte-identical to pre-slice-2.
|
|
@@ -253,28 +241,12 @@ export class SemanticSearch extends Resource {
|
|
|
253
241
|
};
|
|
254
242
|
}
|
|
255
243
|
}
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
// Still gated on `qEmb` (an embedding was actually generated); the pure
|
|
263
|
-
// keyword-only fallback (no qEmb at all) is untouched either way. The
|
|
264
|
-
// reranker overwrites `_score` with the rerank score (so margin
|
|
265
|
-
// measurement reads it) and preserves the semantic score as `_semScore`;
|
|
266
|
-
// `_rawScore` is left as-is so recall-bench's scoring:"raw" path stays
|
|
267
|
-
// reproducible. On init failure, timeout, or any throw, rerankCandidates
|
|
268
|
-
// returns the input UNCHANGED and we fall through to retrieveCandidates'
|
|
269
|
-
// own vector-order sort — recall is never blocked. retrieveCandidates
|
|
270
|
-
// already returns its output sorted best-first, so the non-rerank branch
|
|
271
|
-
// needs no additional sort here.
|
|
272
|
-
if (rerankOn && qEmb && q && filteredResults.length >= getRerankMinCandidates()) {
|
|
273
|
-
filteredResults = await rerankCandidates(String(q), filteredResults, {
|
|
274
|
-
topN: rerankTopN,
|
|
275
|
-
budgetMs: getRerankBudgetMs(),
|
|
276
|
-
});
|
|
277
|
-
}
|
|
244
|
+
// retrieveCandidates() already returns its output sorted best-first
|
|
245
|
+
// (whichever leg produced it — legacy HNSW-only, the BM25+union-RRF hybrid
|
|
246
|
+
// path, or the keyword-only fallback all converge into the same shape), so
|
|
247
|
+
// the final slice needs no additional sort. A cross-encoder rerank stage
|
|
248
|
+
// used to sit here and reorder the pool before this slice; it was removed
|
|
249
|
+
// in flair#893 after measuring Δp@3 = 0.000 at 4.1× query latency.
|
|
278
250
|
const topResults = filteredResults.slice(0, limit);
|
|
279
251
|
// Async hit tracking — don't block the response
|
|
280
252
|
const now = new Date().toISOString();
|
package/dist/resources/Soul.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { databases } from "
|
|
1
|
+
import { databases } from "harper";
|
|
2
2
|
import { resolveAgentAuth } from "./agent-auth.js";
|
|
3
3
|
import { localInstanceId } from "./instance-identity.js";
|
|
4
4
|
import { makeAuthGate, stampAttribution, UNAUTH } from "./record-type-kit.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resource, databases } from '
|
|
1
|
+
import { Resource, databases } from 'harper';
|
|
2
2
|
import { allowVerified } from './agent-auth.js';
|
|
3
3
|
export class FeedSouls extends Resource {
|
|
4
4
|
// Self-authorize the subscription via the Ed25519 agent verify (auth reshape
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* GET /WorkspaceLatest/{agentId} — returns most recent WorkspaceState record.
|
|
5
5
|
* Auth: requesting agent must match agentId in path (or be admin).
|
|
6
6
|
*/
|
|
7
|
-
import { Resource, databases } from "
|
|
7
|
+
import { Resource, databases } from "harper";
|
|
8
8
|
import { allowVerified, resolveAgentAuth } from "./agent-auth.js";
|
|
9
9
|
export class WorkspaceLatest extends Resource {
|
|
10
10
|
// Self-authorize via the Ed25519 agent verify (auth reshape removes the gate's
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Note: Harper's static methods call instance methods with positional args only.
|
|
8
8
|
* Use this.getContext() to access request context (tpsAgent, tpsAgentIsAdmin).
|
|
9
9
|
*/
|
|
10
|
-
import { databases } from "
|
|
10
|
+
import { databases } from "harper";
|
|
11
11
|
import { resolveAgentAuth } from "./agent-auth.js";
|
|
12
12
|
import { invalidEntitiesResponse } from "./entity-vocab.js";
|
|
13
13
|
import { makeAuthGate, makeReadScope, makeByIdReadGate, resolveAuthGate, stampAttribution, FORBIDDEN, UNAUTH, } from "./record-type-kit.js";
|
package/dist/resources/XAA.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// reflect the REAL listening port, or a remote A2A peer that follows discovery
|
|
7
7
|
// hits a dead port.
|
|
8
8
|
//
|
|
9
|
-
// Kept free of any
|
|
9
|
+
// Kept free of any harper import so the resolution logic is
|
|
10
10
|
// unit-testable without spinning up Harper (mirrors agentcard-fields.ts —
|
|
11
11
|
// avoids the simulator-pattern drift that let the AdminInstance predicate be
|
|
12
12
|
// reproduced-not-imported).
|
|
@@ -100,7 +100,7 @@ export const STRONG_BAND = 0.55;
|
|
|
100
100
|
* Reads ONLY the `_semSimilarity` number the retrieval core
|
|
101
101
|
* (resources/semantic-retrieval-core.ts) attaches to each semantic-leg result
|
|
102
102
|
* WHEN abstention is requested — an absolute cosine similarity in [0,1],
|
|
103
|
-
* independent of the RRF normalization
|
|
103
|
+
* independent of the RRF normalization that makes the ranking `_score`
|
|
104
104
|
* a *relative* signal (the top RRF-fused result is normalized to 1.0 regardless
|
|
105
105
|
* of how weak the actual match is, so `_score` is unusable as a confidence
|
|
106
106
|
* floor — this is why abstention reads the absolute similarity instead).
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* verified against the agent's stored Ed25519 public key. Replay is bounded by a
|
|
16
16
|
* 30s timestamp window + a per-(agent,nonce) seen-set pruned to that window.
|
|
17
17
|
*/
|
|
18
|
-
import { databases } from "
|
|
18
|
+
import { databases } from "harper";
|
|
19
19
|
import { WINDOW_MS, isNonceReplay, recordNonce, importEd25519Key, b64ToArrayBuffer, parseTpsEd25519Header } from "./ed25519-auth.js";
|
|
20
20
|
/**
|
|
21
21
|
* Shared Harper user that verified Ed25519 agents resolve to (least-privilege
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// fallback that could surface a private soul (an operator's internal note,
|
|
8
8
|
// prompt fragment, or credential reminder) on the public endpoint.
|
|
9
9
|
//
|
|
10
|
-
// Kept free of any
|
|
10
|
+
// Kept free of any harper import so the real logic is unit-testable
|
|
11
11
|
// without spinning up Harper (avoids the simulator-pattern that let the
|
|
12
12
|
// description-fallback leak ship untested).
|
|
13
13
|
export function readSoulKind(entry) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { patchRecord } from "./table-helpers.js";
|
|
2
|
-
import { server, databases } from "
|
|
2
|
+
import { server, databases } from "harper";
|
|
3
3
|
import { getEmbedding } from "./embeddings-provider.js";
|
|
4
4
|
import { isAdmin, FLAIR_AGENT_USERNAME } from "./agent-auth.js";
|
|
5
5
|
import { WINDOW_MS, isNonceReplay, recordNonce, importEd25519Key, b64ToArrayBuffer, parseTpsEd25519Header } from "./ed25519-auth.js";
|