diff --git a/src/clients.ts b/src/clients.ts index a6a8348..4c036e1 100644 --- a/src/clients.ts +++ b/src/clients.ts @@ -1,6 +1,7 @@ import { Client } from "@notionhq/client"; import { assertWorkspaceScope, getAccountId, DEFAULT_ACCOUNT_ID } from "./context.js"; import { getAccountToken, ensureAccountToken, onAccountTokensInvalidated } from "./account-tokens.js"; +import { NOTION_API_VERSION } from "./notion-version.js"; // PAT tokens — used for ALL operations except /v1/search. // PATs inherit the creator user's permissions (full read/write by ID) but @@ -58,9 +59,9 @@ if (bearerToken && bearerToken.length < 32) { process.exit(1); } -// Notion API version. 2025-09-03 unlocks multi-source databases, -// data_sources endpoints, comments threading, and file uploads. -export const NOTION_API_VERSION = "2025-09-03"; +// Notion API version: single source of truth in notion-version.ts. Re-exported +// here for the many call sites that already import it from clients.ts. +export { NOTION_API_VERSION }; // PAT clients (default — used everywhere except search). export const globalcriptoClient = new Client({ diff --git a/src/notion-oauth.ts b/src/notion-oauth.ts index 1027365..5d30c34 100644 --- a/src/notion-oauth.ts +++ b/src/notion-oauth.ts @@ -13,9 +13,7 @@ import { invalidateAccountTokens } from "./account-tokens.js"; const AUTHORIZE_URL = "https://api.notion.com/v1/oauth/authorize"; const TOKEN_URL = "https://api.notion.com/v1/oauth/token"; const ME_URL = "https://api.notion.com/v1/users/me"; -// Keep in sync with NOTION_API_VERSION in clients.ts (not imported — clients.ts -// process.exit()s at load when NOTION_*_TOKEN are unset, which breaks unit tests). -const NOTION_VERSION = "2025-09-03"; +import { NOTION_API_VERSION as NOTION_VERSION } from "./notion-version.js"; /** Stable account id for a connected Notion workspace (P1: account = a Notion * OAuth identity). Prefixed so it never collides with the built-in 'bruno'. */ diff --git a/src/notion-version.ts b/src/notion-version.ts new file mode 100644 index 0000000..f4ff85e --- /dev/null +++ b/src/notion-version.ts @@ -0,0 +1,8 @@ +// src/notion-version.ts +// Single source of truth for the pinned Notion API version. Lives in its own +// side-effect-free module (no env validation, no client construction) so any +// file can import it WITHOUT pulling clients.ts's import-time env checks — which +// is exactly why several modules used to redeclare the literal "2025-09-03". +// +// 2025-09-03: multi-source databases, file uploads, comments, schema PATCH. +export const NOTION_API_VERSION = "2025-09-03"; diff --git a/src/portal/task-tracker.ts b/src/portal/task-tracker.ts index 6d04b18..eab3edf 100644 --- a/src/portal/task-tracker.ts +++ b/src/portal/task-tracker.ts @@ -20,8 +20,8 @@ import { type Detection, type DataSourceLite, } from "./task-tracker-schema.js"; +import { NOTION_API_VERSION as NOTION_VERSION } from "../notion-version.js"; -const NOTION_VERSION = "2025-09-03"; // manter em sincronia com clients.ts const NOTION_API = "https://api.notion.com"; const TASKS_DB_KIND = "tasks_db"; // Quantas data sources inspecionar (1 GET de schema cada) na detecção. Bound de diff --git a/src/rag/search.ts b/src/rag/search.ts index 0291467..cfd5c74 100644 --- a/src/rag/search.ts +++ b/src/rag/search.ts @@ -177,47 +177,6 @@ function maxPerUrlConfig(): number { return Number.isFinite(v) ? v : 3; } -/** - * Optional recency boost applied AFTER reranking (or after RRF fallback). - * Controlled by RECENCY_BOOST env var (default "off" / 0). - * - * Formula: final_score = score * (1 + beta * exp(-age_days / halflife)) - * beta = RECENCY_BOOST (e.g. 0.15 → max +15% for today's docs) - * halflife = RECENCY_HALFLIFE_DAYS (default 30) - * age_days derived from chunk.metadata.data (ISO date string) or source_updated. - * - * With RECENCY_BOOST=0 (or unset) returns hits unchanged — zero cost. - * Positive beta boosts recent content; negative beta penalizes it (unusual). - */ -export function recencyBoostEnabled(): boolean { - const v = Number(process.env.RECENCY_BOOST); - return Number.isFinite(v) && v !== 0; -} - -export function applyRecencyBoost(hits: SearchHit[], now: Date = new Date()): SearchHit[] { - const beta = Number(process.env.RECENCY_BOOST); - if (!Number.isFinite(beta) || beta === 0) return hits; - const halflife = Number(process.env.RECENCY_HALFLIFE_DAYS ?? 30); - const hl = Number.isFinite(halflife) && halflife > 0 ? halflife : 30; - // ln(2)/halflife is the decay constant for exp(-age * lambda) - const lambda = Math.LN2 / hl; - - const boosted = hits.map((h) => { - const dateStr = - (typeof h.chunk.metadata?.data === "string" ? h.chunk.metadata.data : undefined) ?? - (h.chunk.source_updated instanceof Date ? h.chunk.source_updated.toISOString() : undefined); - if (!dateStr) return h; - const docDate = new Date(dateStr); - if (isNaN(docDate.getTime())) return h; - const ageDays = Math.max(0, (now.getTime() - docDate.getTime()) / 86_400_000); - const boost = 1 + beta * Math.exp(-ageDays * lambda); - return { ...h, score: h.score * boost }; - }); - - // Re-sort by boosted score (recency may reorder). - return boosted.sort((a, b) => b.score - a.score); -} - /** * Query-time result diversification. Iterates `hits` in their given (already * ranked) order and KEEPS a hit unless: @@ -426,11 +385,6 @@ export async function brainSearch( } } - // Optional recency boost (RECENCY_BOOST env, default off). Applied after all - // ranking/diversification so it can only reorder within the already-diversified - // topK set, not expand it. Zero cost when RECENCY_BOOST=0 or unset. - hits = applyRecencyBoost(hits); - // 002-app-v2 — AI search transparency log. Companion to the recordUsage // metering above, but appended AFTER the hits are computed so the row carries // the real result count. Only in-request searches are logged (cron/eval have diff --git a/src/tasks/adapter.ts b/src/tasks/adapter.ts index 3054ce6..684a837 100644 --- a/src/tasks/adapter.ts +++ b/src/tasks/adapter.ts @@ -25,9 +25,9 @@ import { type CanonicalPriority, type CanonicalField, } from "./model.js"; +import { NOTION_API_VERSION as NOTION_VERSION } from "../notion-version.js"; const NOTION_API = "https://api.notion.com"; -const NOTION_VERSION = "2025-09-03"; // keep in sync with clients.ts /** The owner's Tasks Tracker data_source (workspace 'personal') — formerly * hardcoded in briefing/daily-briefing.ts:31. Now ONLY a safety net used when