diff --git a/backend/src/lib/feature-flags.ts b/backend/src/lib/feature-flags.ts index 047b706..3f65fe1 100644 --- a/backend/src/lib/feature-flags.ts +++ b/backend/src/lib/feature-flags.ts @@ -17,6 +17,13 @@ const registry = createFlagRegistry({ 'notelett_auto_embed_enabled': false, 'notelett_duplicate_check_enabled': true, 'notelett_suggest_links_enabled': true, + // Smart Actions feature flags (§6.3) + 'notelett_smart_actions_enabled': false, + 'notelett_auto_link_enabled': false, + 'notelett_copilot_llm_enabled': false, + 'notelett_voice_capture_enabled': false, + 'notelett_scheduled_actions_enabled': false, + 'notelett_webhooks_enabled': false, }, enabled: config.FEATURE_FLAGS_ENABLED, }); diff --git a/backend/src/modules/note-prompts/routes.ts b/backend/src/modules/note-prompts/routes.ts index 5945a78..1d04b38 100644 --- a/backend/src/modules/note-prompts/routes.ts +++ b/backend/src/modules/note-prompts/routes.ts @@ -6,6 +6,7 @@ import type { FastifyInstance } from 'fastify'; import { z } from 'zod'; import { getUserId, getRequestProductId } from '../../lib/request-context.js'; import { BadRequestError, NotFoundError } from '@bytelyst/errors'; +import { isFeatureEnabled } from '../../lib/feature-flags.js'; import { embedText, cosineSimilarity, stripHtmlForEmbedding } from '../../lib/embeddings.js'; import { llm } from '../../lib/llm.js'; import { @@ -70,6 +71,7 @@ export async function notePromptRoutes(app: FastifyInstance): Promise { // ── Run a prompt template against a note ──────────────────────── app.post('/note-prompts/run', async (req) => { + if (!isFeatureEnabled('notelett_smart_actions_enabled')) throw new BadRequestError('Smart Actions are disabled'); const userId = getUserId(req); const productId = getRequestProductId(req); const input = RunPromptSchema.parse(req.body); diff --git a/backend/src/modules/note-prompts/scheduler.ts b/backend/src/modules/note-prompts/scheduler.ts index aa1c004..6203331 100644 --- a/backend/src/modules/note-prompts/scheduler.ts +++ b/backend/src/modules/note-prompts/scheduler.ts @@ -12,6 +12,7 @@ import { getUserId, getRequestProductId } from '../../lib/request-context.js'; import { BadRequestError, NotFoundError } from '@bytelyst/errors'; import { getCollection } from '../../lib/datastore.js'; import { PRODUCT_ID } from '../../lib/product-config.js'; +import { isFeatureEnabled } from '../../lib/feature-flags.js'; import { llm } from '../../lib/llm.js'; import * as noteRepo from '../notes/repository.js'; import * as promptRepo from './repository.js'; @@ -241,6 +242,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise // ── Schedule CRUD ───────────────────────────────────────────── app.get('/prompt-schedules', async (req) => { + if (!isFeatureEnabled('notelett_scheduled_actions_enabled')) throw new BadRequestError('Scheduled actions are disabled'); const userId = getUserId(req); const items = await scheduleCollection().findMany({ filter: { productId: PRODUCT_ID, userId }, @@ -251,6 +253,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.post('/prompt-schedules', async (req, reply) => { + if (!isFeatureEnabled('notelett_scheduled_actions_enabled')) throw new BadRequestError('Scheduled actions are disabled'); const userId = getUserId(req); const input = CreateScheduleSchema.parse(req.body); const now = new Date().toISOString(); @@ -274,6 +277,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.get('/prompt-schedules/:id', async (req) => { + if (!isFeatureEnabled('notelett_scheduled_actions_enabled')) throw new BadRequestError('Scheduled actions are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const doc = await scheduleCollection().findById(id, userId); @@ -282,6 +286,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.patch('/prompt-schedules/:id', async (req) => { + if (!isFeatureEnabled('notelett_scheduled_actions_enabled')) throw new BadRequestError('Scheduled actions are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const input = UpdateScheduleSchema.parse(req.body); @@ -298,6 +303,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.delete('/prompt-schedules/:id', async (req, reply) => { + if (!isFeatureEnabled('notelett_scheduled_actions_enabled')) throw new BadRequestError('Scheduled actions are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const existing = await scheduleCollection().findById(id, userId); @@ -309,6 +315,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise // ── Webhook CRUD ────────────────────────────────────────────── app.get('/prompt-webhooks', async (req) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const items = await webhookCollection().findMany({ filter: { productId: PRODUCT_ID, userId }, @@ -319,6 +326,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.post('/prompt-webhooks', async (req, reply) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const input = CreateWebhookSchema.parse(req.body); const now = new Date().toISOString(); @@ -342,6 +350,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.get('/prompt-webhooks/:id', async (req) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const doc = await webhookCollection().findById(id, userId); @@ -350,6 +359,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.patch('/prompt-webhooks/:id', async (req) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const input = UpdateWebhookSchema.parse(req.body); @@ -361,6 +371,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise }); app.delete('/prompt-webhooks/:id', async (req, reply) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const existing = await webhookCollection().findById(id, userId); @@ -372,6 +383,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise // ── Trigger a webhook (F26) ─────────────────────────────────── app.post('/prompt-webhooks/:id/trigger', async (req) => { + if (!isFeatureEnabled('notelett_webhooks_enabled')) throw new BadRequestError('Webhooks are disabled'); const userId = getUserId(req); const { id } = req.params as { id: string }; const input = TriggerWebhookSchema.parse(req.body);