@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.
Files changed (117) hide show
  1. package/README.md +194 -377
  2. package/dist/cli-shim.cjs +1 -1
  3. package/dist/cli.js +1567 -366
  4. package/dist/deploy.js +241 -43
  5. package/dist/fabric-upgrade.js +77 -24
  6. package/dist/federation/scheduler.js +500 -0
  7. package/dist/fleet-verify.js +3 -3
  8. package/dist/install/clients.js +111 -53
  9. package/dist/lib/mcp-enable.js +2 -2
  10. package/dist/lib/mcp-spec.js +128 -0
  11. package/dist/lib/safe-snapshot-extract.js +231 -0
  12. package/dist/lib/scheduler-platform.js +128 -0
  13. package/dist/lib/xml-escape.js +54 -0
  14. package/dist/rem/scheduler.js +212 -55
  15. package/dist/rem/snapshot.js +13 -0
  16. package/dist/replication-convergence.js +505 -0
  17. package/dist/resources/A2AAdapter.js +1 -1
  18. package/dist/resources/Admin.js +1 -1
  19. package/dist/resources/AdminConnectors.js +1 -1
  20. package/dist/resources/AdminDashboard.js +1 -1
  21. package/dist/resources/AdminIdp.js +1 -1
  22. package/dist/resources/AdminInstance.js +1 -1
  23. package/dist/resources/AdminMemory.js +1 -1
  24. package/dist/resources/AdminPrincipals.js +1 -1
  25. package/dist/resources/Agent.js +1 -1
  26. package/dist/resources/AgentCard.js +1 -1
  27. package/dist/resources/AgentSeed.js +1 -1
  28. package/dist/resources/AttentionQuery.js +1 -1
  29. package/dist/resources/Credential.js +1 -1
  30. package/dist/resources/Federation.js +1 -1
  31. package/dist/resources/Instance.js +1 -1
  32. package/dist/resources/Integration.js +1 -1
  33. package/dist/resources/MCPClientMetadata.js +1 -1
  34. package/dist/resources/Memory.js +1 -1
  35. package/dist/resources/MemoryBootstrap.js +8 -9
  36. package/dist/resources/MemoryCandidate.js +168 -0
  37. package/dist/resources/MemoryConsolidate.js +1 -1
  38. package/dist/resources/MemoryDedupStats.js +1 -1
  39. package/dist/resources/MemoryFeed.js +1 -1
  40. package/dist/resources/MemoryGrant.js +1 -1
  41. package/dist/resources/MemoryMaintenance.js +1 -1
  42. package/dist/resources/MemoryReflect.js +2 -2
  43. package/dist/resources/MemoryReindex.js +1 -1
  44. package/dist/resources/MemoryUsage.js +1 -1
  45. package/dist/resources/OAuth.js +1 -1
  46. package/dist/resources/OAuthClient.js +1 -1
  47. package/dist/resources/OrgEvent.js +1 -1
  48. package/dist/resources/OrgEventCatchup.js +1 -1
  49. package/dist/resources/OrgEventMaintenance.js +1 -1
  50. package/dist/resources/PairingToken.js +1 -1
  51. package/dist/resources/Peer.js +1 -1
  52. package/dist/resources/Presence.js +3 -3
  53. package/dist/resources/RecordUsage.js +1 -1
  54. package/dist/resources/Relationship.js +1 -1
  55. package/dist/resources/SemanticSearch.js +18 -46
  56. package/dist/resources/SkillScan.js +1 -1
  57. package/dist/resources/Soul.js +1 -1
  58. package/dist/resources/SoulFeed.js +1 -1
  59. package/dist/resources/WorkspaceLatest.js +1 -1
  60. package/dist/resources/WorkspaceState.js +1 -1
  61. package/dist/resources/XAA.js +1 -1
  62. package/dist/resources/a2a-url.js +1 -1
  63. package/dist/resources/abstention.js +1 -1
  64. package/dist/resources/agent-auth.js +1 -1
  65. package/dist/resources/agentcard-fields.js +1 -1
  66. package/dist/resources/auth-middleware.js +1 -1
  67. package/dist/resources/embeddings-boot.js +14 -16
  68. package/dist/resources/embeddings-provider.js +15 -12
  69. package/dist/resources/federation-cleanup.js +5 -5
  70. package/dist/resources/federation-nonce-store.js +5 -5
  71. package/dist/resources/health.js +53 -25
  72. package/dist/resources/in-process.js +225 -0
  73. package/dist/resources/instance-identity.js +1 -1
  74. package/dist/resources/mcp-client-metadata-fields.js +1 -1
  75. package/dist/resources/mcp-handler.js +1 -1
  76. package/dist/resources/mcp-oauth.js +2 -2
  77. package/dist/resources/mcp-tools.js +23 -17
  78. package/dist/resources/memory-read-scope.js +1 -1
  79. package/dist/resources/memory-reflect-lib.js +3 -3
  80. package/dist/resources/memory-visibility.js +3 -3
  81. package/dist/resources/migration-boot.js +81 -11
  82. package/dist/resources/migrations/data-dir.js +205 -0
  83. package/dist/resources/migrations/embedding-stamp.js +3 -3
  84. package/dist/resources/migrations/graph-heal.js +2 -2
  85. package/dist/resources/migrations/ledger.js +1 -1
  86. package/dist/resources/migrations/progress.js +33 -0
  87. package/dist/resources/migrations/registry.js +14 -5
  88. package/dist/resources/migrations/runner.js +93 -13
  89. package/dist/resources/migrations/state.js +13 -2
  90. package/dist/resources/migrations/synthetic-test-migration.js +1 -1
  91. package/dist/resources/migrations/visibility-backfill.js +232 -0
  92. package/dist/resources/models-dir.js +18 -9
  93. package/dist/resources/record-types.js +28 -0
  94. package/dist/resources/semantic-retrieval-core.js +6 -5
  95. package/dist/resources/usage-recording.js +1 -1
  96. package/dist/src/lib/scheduler-platform.js +128 -0
  97. package/dist/src/lib/xml-escape.js +54 -0
  98. package/dist/src/rem/scheduler.js +397 -0
  99. package/docs/deploying-on-fabric.md +267 -0
  100. package/docs/deployment.md +6 -0
  101. package/docs/embedding-in-a-harper-app.md +299 -0
  102. package/docs/federation.md +61 -4
  103. package/docs/integrations.md +3 -0
  104. package/docs/mcp-clients.md +16 -7
  105. package/docs/quickstart.md +80 -54
  106. package/docs/releasing.md +72 -38
  107. package/docs/supply-chain-policy.md +40 -4
  108. package/docs/troubleshooting.md +24 -0
  109. package/docs/upgrade.md +100 -5
  110. package/package.json +4 -14
  111. package/schemas/memory.graphql +9 -1
  112. package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
  113. package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
  114. package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
  115. package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
  116. package/dist/resources/rerank-provider.js +0 -569
  117. package/docs/rerank-provisioning.md +0 -101
@@ -1,4 +1,4 @@
1
- import { Resource, databases } from "@harperfast/harper";
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/reranker/
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, no
623
- // reranker for bootstrap — a different cost profile (BM25 over the
624
- // org corpus for a one-shot session-load could be MORE expensive
625
- // than HNSW-only unless cached across sessions; the reranker is a
626
- // generative call per candidate). Both are explicit opt-in
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/rerank) — the K&S-ratified, closest-to-a-wash choice; the
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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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 @harperfast/harper 5.1.17 has
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 "@harperfast/harper";
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 "@harperfast/harper";
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" } });
@@ -1,4 +1,4 @@
1
- import { Resource, databases } from "@harperfast/harper";
1
+ import { Resource, databases } from "harper";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
  import { handleJwtBearerGrant } from "./XAA.js";
4
4
  import { resolveAgentAuth } from "./agent-auth.js";
@@ -1,4 +1,4 @@
1
- import { databases } from "@harperfast/harper";
1
+ import { databases } from "harper";
2
2
  import { allowAdmin } from "./agent-auth.js";
3
3
  /**
4
4
  * OAuthClient holds registered OAuth client records (incl. secrets) — admin/system
@@ -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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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
@@ -1,4 +1,4 @@
1
- import { databases } from "@harperfast/harper";
1
+ import { databases } from "harper";
2
2
  import { allowAdmin } from "./agent-auth.js";
3
3
  /**
4
4
  * PairingToken holds one-time federation pairing secrets — admin/system only,
@@ -1,4 +1,4 @@
1
- import { databases } from "@harperfast/harper";
1
+ import { databases } from "harper";
2
2
  import { allowAdmin } from "./agent-auth.js";
3
3
  /**
4
4
  * Peer holds federation peer records (URLs + credentials) — system/admin data,
@@ -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 "@harperfast/harper";
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 @harperfast/harper version. Unlike flair's own
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("@harperfast/harper");
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 "@harperfast/harper";
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 "@harperfast/harper";
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 "@harperfast/harper";
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/reranker/hit-tracking side effects. See
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. Hybrid's semantic leg is never rerank-widened
200
- // (matches the pre-extraction behavior: only the legacy path widened for
201
- // rerank).
202
- const candidateLimit = hybrid
203
- ? limit * CANDIDATE_MULTIPLIER
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
- let filteredResults = await retrieveCandidates({
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
- // reranker / final slice / hit-tracking, so an abstaining recall does no
238
- // rerank work and never bumps retrievalCount for memories it declines to
239
- // surface. The decision reads ONLY the best absolute semantic similarity
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
- // ─── Cross-encoder rerank (best-effort, fail-open to vector order) ───────
257
- // Re-scores query+candidate TOGETHER (cross-attention the pooled embedding
258
- // can't do) and reorders before the final slice. Reorders whatever
259
- // `filteredResults` retrieveCandidates() produced legacy HNSW-only OR
260
- // the BM25+union-RRF hybrid path since both converge into the same
261
- // shape (retrieveCandidates never exposes which leg produced a result).
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();
@@ -1,4 +1,4 @@
1
- import { Resource } from "@harperfast/harper";
1
+ import { Resource } from "harper";
2
2
  import { allowVerified } from "./agent-auth.js";
3
3
  import { scanSkillContent } from "./scan/skill-scanner.js";
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { databases } from "@harperfast/harper";
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 '@harperfast/harper';
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 "@harperfast/harper";
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 "@harperfast/harper";
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";
@@ -1,4 +1,4 @@
1
- import { databases } from "@harperfast/harper";
1
+ import { databases } from "harper";
2
2
  import { allowAdmin } from "./agent-auth.js";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -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 @harperfast/harper import so the resolution logic is
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 / rerank that make the ranking `_score`
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 "@harperfast/harper";
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 @harperfast/harper import so the real logic is unit-testable
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 "@harperfast/harper";
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";