/** * @param { import("knex").Knex } knex * @returns { Promise } */ exports.up = async function (knex) { await knex.schema.createTable('comments', (table) => { table.uuid('id').primary().defaultTo(knex.raw('(UUID())')); table.string('parent_object_api_name').notNullable(); table.uuid('parent_record_id').notNullable(); table.uuid('author_user_id').notNullable(); table.text('content').notNullable(); table.timestamps(true, true); table.foreign('author_user_id').references('id').inTable('users').onDelete('CASCADE'); table.index(['parent_object_api_name', 'parent_record_id'], 'comments_parent_idx'); table.index(['author_user_id'], 'comments_author_idx'); }); await knex.schema.createTable('semantic_documents', (table) => { table.uuid('id').primary().defaultTo(knex.raw('(UUID())')); table.string('entity_type').notNullable(); table.uuid('entity_id').notNullable(); table.string('title').nullable(); table.text('narrative').nullable(); table.json('metadata').nullable(); table.json('source_summary').nullable(); table.timestamps(true, true); table.unique(['entity_type', 'entity_id'], { indexName: 'semantic_documents_entity_unique', }); table.index(['entity_type'], 'semantic_documents_type_idx'); }); await knex.schema.createTable('semantic_chunks', (table) => { table.uuid('id').primary().defaultTo(knex.raw('(UUID())')); table.uuid('semantic_document_id').notNullable(); table.integer('chunk_index').notNullable(); table.string('source_kind').notNullable().defaultTo('base_record'); table.uuid('source_ref_id').nullable(); table.text('text').notNullable(); table.json('metadata').nullable(); table.timestamps(true, true); table.foreign('semantic_document_id').references('id').inTable('semantic_documents').onDelete('CASCADE'); table.unique(['semantic_document_id', 'chunk_index'], { indexName: 'semantic_chunks_doc_index_unique', }); table.index(['semantic_document_id'], 'semantic_chunks_document_idx'); table.index(['source_kind'], 'semantic_chunks_source_kind_idx'); }); await knex.schema.createTable('semantic_links', (table) => { table.uuid('id').primary().defaultTo(knex.raw('(UUID())')); table.string('source_entity_type').notNullable(); table.uuid('source_entity_id').notNullable(); table.string('target_entity_type').notNullable(); table.uuid('target_entity_id').notNullable(); table.string('link_type').notNullable().defaultTo('related_to'); table.string('status').notNullable().defaultTo('suggested'); table.string('origin').notNullable().defaultTo('semantic'); table.decimal('confidence', 5, 4).notNullable().defaultTo(0); table.text('reason').nullable(); table.json('evidence').nullable(); table.uuid('suggested_by_user_id').nullable(); table.uuid('reviewed_by_user_id').nullable(); table.timestamp('reviewed_at').nullable(); table.timestamps(true, true); table.foreign('suggested_by_user_id').references('id').inTable('users').onDelete('SET NULL'); table.foreign('reviewed_by_user_id').references('id').inTable('users').onDelete('SET NULL'); table.unique( ['source_entity_type', 'source_entity_id', 'target_entity_type', 'target_entity_id', 'link_type'], { indexName: 'semantic_links_unique_pair_type' }, ); table.index(['source_entity_type', 'source_entity_id'], 'semantic_links_source_idx'); table.index(['target_entity_type', 'target_entity_id'], 'semantic_links_target_idx'); table.index(['status'], 'semantic_links_status_idx'); }); }; /** * @param { import("knex").Knex } knex * @returns { Promise } */ exports.down = async function (knex) { await knex.schema.dropTableIfExists('semantic_links'); await knex.schema.dropTableIfExists('semantic_chunks'); await knex.schema.dropTableIfExists('semantic_documents'); await knex.schema.dropTableIfExists('comments'); };