feat: scaffold Business/Life/Intelligence/Trust Engine tables

Per Blueprint v4.0 §12 canonical data models:
- Business Engine: organizations extended (legal_name, country, registry_id,
  domain, external_ids), transactions (minor-unit amounts, evidence_status),
  documents (metadata only -- binary/OCR stays in Paperless via paperless_id)
- Life Engine: goals (horizon/metric/target/milestones)
- Intelligence Engine: decisions (context/options/assumptions/evidence),
  opportunities, ai_requests (context_manifest_hash for audit without
  storing sensitive payload content in Postgres)
- Trust Engine: observations (generic subject_type/subject_id so any
  entity -- user, org, device -- can feed reputation/trust scores)

Applied directly to Supabase staging Postgres (14 tables total now).
This is also the first drizzle/ migration actually committed -- the
earlier one from the Identity Engine work never made it into git.
This commit is contained in:
valentinbvro 2026-07-21 19:44:45 +02:00
parent 0a4ffc41e6
commit 5656f1cc1c
4 changed files with 1522 additions and 1 deletions

View file

@ -0,0 +1,187 @@
CREATE TYPE "public"."document_classification" AS ENUM('c0', 'c1', 'c2', 'c3', 'c4');--> statement-breakpoint
CREATE TYPE "public"."goal_status" AS ENUM('not_started', 'on_track', 'at_risk', 'achieved', 'abandoned');--> statement-breakpoint
CREATE TYPE "public"."membership_role" AS ENUM('owner', 'admin', 'member');--> statement-breakpoint
CREATE TYPE "public"."task_status" AS ENUM('open', 'in_progress', 'blocked', 'done', 'cancelled');--> statement-breakpoint
CREATE TYPE "public"."transaction_evidence_status" AS ENUM('missing', 'partial', 'complete', 'not_required');--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "ai_requests" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"requested_by_user_id" uuid NOT NULL,
"purpose" text NOT NULL,
"context_manifest_hash" text NOT NULL,
"model" text NOT NULL,
"template_version" text,
"cost_usd_minor_units" integer,
"result_status" text DEFAULT 'pending' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"completed_at" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "audit_log" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"actor_id" uuid,
"action" text NOT NULL,
"resource" text NOT NULL,
"reason" text,
"correlation_id" uuid,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "consent_records" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"purpose" text NOT NULL,
"granted_at" timestamp DEFAULT now() NOT NULL,
"revoked_at" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "decisions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"owner_user_id" uuid NOT NULL,
"context" text NOT NULL,
"options" jsonb DEFAULT '[]'::jsonb NOT NULL,
"assumptions" jsonb DEFAULT '[]'::jsonb NOT NULL,
"evidence" jsonb DEFAULT '[]'::jsonb NOT NULL,
"selected_option" text,
"outcome_review" text,
"review_due_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "documents" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"organization_id" uuid,
"owner_user_id" uuid NOT NULL,
"classification" "document_classification" DEFAULT 'c2' NOT NULL,
"paperless_id" text,
"filename" text NOT NULL,
"checksum" text,
"expires_at" timestamp,
"retention_policy" text,
"ocr_status" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"version" integer DEFAULT 1 NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "goals" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"owner_user_id" uuid NOT NULL,
"horizon" text NOT NULL,
"metric" text NOT NULL,
"target" text NOT NULL,
"milestones" jsonb DEFAULT '[]'::jsonb NOT NULL,
"status" "goal_status" DEFAULT 'not_started' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"version" integer DEFAULT 1 NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "memberships" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"role" "membership_role" DEFAULT 'member' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "observations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"subject_type" text NOT NULL,
"subject_id" text NOT NULL,
"metric" text NOT NULL,
"value" text NOT NULL,
"unit" text,
"source" text NOT NULL,
"observed_at" timestamp DEFAULT now() NOT NULL,
"confidence" numeric(5, 4)
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "opportunities" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"source" text NOT NULL,
"entity_type" text,
"entity_id" text,
"value_range_min" numeric(18, 0),
"value_range_max" numeric(18, 0),
"probability" numeric(5, 4),
"next_action" text,
"expires_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "organizations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"name" text NOT NULL,
"legal_name" text,
"country" text,
"registry_id" text,
"domain" text,
"external_ids" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"version" integer DEFAULT 1 NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "outbox_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"event_type" text NOT NULL,
"event_version" integer DEFAULT 1 NOT NULL,
"subject_id" uuid,
"payload" jsonb NOT NULL,
"correlation_id" uuid,
"created_at" timestamp DEFAULT now() NOT NULL,
"processed_at" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "tasks" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"owner_user_id" uuid NOT NULL,
"title" text NOT NULL,
"priority" integer DEFAULT 3 NOT NULL,
"due_at" timestamp,
"source_entity_type" text,
"source_entity_id" uuid,
"status" "task_status" DEFAULT 'open' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"version" integer DEFAULT 1 NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "tenants" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "transactions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" uuid NOT NULL,
"organization_id" uuid NOT NULL,
"type" text NOT NULL,
"amount_minor_units" numeric(18, 0) NOT NULL,
"currency" text NOT NULL,
"transaction_date" timestamp NOT NULL,
"evidence_status" "transaction_evidence_status" DEFAULT 'missing' NOT NULL,
"source" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"deleted_at" timestamp,
"version" integer DEFAULT 1 NOT NULL
);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1784655760242,
"tag": "0000_ancient_silver_surfer",
"breakpoints": true
}
]
}

View file

@ -1,10 +1,87 @@
import { pgTable, pgEnum, uuid, text, timestamp, jsonb, integer } from 'drizzle-orm/pg-core';
import { pgTable, pgEnum, uuid, text, timestamp, jsonb, integer, numeric } from 'drizzle-orm/pg-core';
// --- Business Engine (blueprint sectiunea 5, 12): companii, tranzactii, documente, taskuri ---
// Organizations e sursa de adevar in CEO Postgres pentru companii private (blueprint sectiunea 9);
// ERPNext devine sursa de adevar doar pentru "Accounting official", dupa activare -- vezi
// Blueprint v4.0 sectiunea 9 si Anexa I decizia #3.
export const organizations = pgTable('organizations', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
name: text('name').notNull(),
legalName: text('legal_name'),
country: text('country'),
registryId: text('registry_id'),
domain: text('domain'),
externalIds: jsonb('external_ids').notNull().default({}),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
version: integer('version').notNull().default(1),
});
export const transactionEvidenceStatus = pgEnum('transaction_evidence_status', [
'missing',
'partial',
'complete',
'not_required',
]);
// amounts in minor units (cents), niciodata float, per blueprint 12.1
export const transactions = pgTable('transactions', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
organizationId: uuid('organization_id').notNull(),
type: text('type').notNull(),
amountMinorUnits: numeric('amount_minor_units', { precision: 18, scale: 0 }).notNull(),
currency: text('currency').notNull(),
transactionDate: timestamp('transaction_date').notNull(),
evidenceStatus: transactionEvidenceStatus('evidence_status').notNull().default('missing'),
source: text('source'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
version: integer('version').notNull().default(1),
});
export const documentClassification = pgEnum('document_classification', ['c0', 'c1', 'c2', 'c3', 'c4']);
// CEO OS pastreaza doar metadata/ownership/permission decisions (blueprint sectiunea 13);
// binarul si OCR-ul raman in Paperless-ngx, legate prin paperlessId
export const documents = pgTable('documents', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
organizationId: uuid('organization_id'),
ownerUserId: uuid('owner_user_id').notNull(),
classification: documentClassification('classification').notNull().default('c2'),
paperlessId: text('paperless_id'),
filename: text('filename').notNull(),
checksum: text('checksum'),
expiresAt: timestamp('expires_at'),
retentionPolicy: text('retention_policy'),
ocrStatus: text('ocr_status'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
version: integer('version').notNull().default(1),
});
export const taskStatus = pgEnum('task_status', ['open', 'in_progress', 'blocked', 'done', 'cancelled']);
export const tasks = pgTable('tasks', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
ownerUserId: uuid('owner_user_id').notNull(),
title: text('title').notNull(),
priority: integer('priority').notNull().default(3),
dueAt: timestamp('due_at'),
sourceEntityType: text('source_entity_type'),
sourceEntityId: uuid('source_entity_id'),
status: taskStatus('status').notNull().default('open'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
version: integer('version').notNull().default(1),
});
// --- Identity Engine (blueprint sectiunea 11, 8.2, 8.3) ---
@ -36,6 +113,92 @@ export const consentRecords = pgTable('consent_records', {
revokedAt: timestamp('revoked_at'),
});
// --- Life Engine (blueprint sectiunea 5): obiective personale, rutine, KPI ---
export const goalStatus = pgEnum('goal_status', ['not_started', 'on_track', 'at_risk', 'achieved', 'abandoned']);
export const goals = pgTable('goals', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
ownerUserId: uuid('owner_user_id').notNull(),
horizon: text('horizon').notNull(),
metric: text('metric').notNull(),
target: text('target').notNull(),
milestones: jsonb('milestones').notNull().default([]),
status: goalStatus('status').notNull().default('not_started'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
version: integer('version').notNull().default(1),
});
// --- Intelligence Engine (blueprint sectiunea 5): decizii, oportunitati, cereri AI ---
export const decisions = pgTable('decisions', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
ownerUserId: uuid('owner_user_id').notNull(),
context: text('context').notNull(),
options: jsonb('options').notNull().default([]),
assumptions: jsonb('assumptions').notNull().default([]),
evidence: jsonb('evidence').notNull().default([]),
selectedOption: text('selected_option'),
outcomeReview: text('outcome_review'),
reviewDueAt: timestamp('review_due_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const opportunities = pgTable('opportunities', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
source: text('source').notNull(),
entityType: text('entity_type'),
entityId: text('entity_id'),
valueRangeMin: numeric('value_range_min', { precision: 18, scale: 0 }),
valueRangeMax: numeric('value_range_max', { precision: 18, scale: 0 }),
probability: numeric('probability', { precision: 5, scale: 4 }),
nextAction: text('next_action'),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// context_manifest_hash leaga cererea de Context Manifest-ul trimis catre AI Gateway
// (blueprint sectiunea 14.1) -- payload-ul complet nu se stocheaza aici, doar hash-ul,
// pentru audit fara a pastra continut sensibil in Postgres
export const aiRequests = pgTable('ai_requests', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
requestedByUserId: uuid('requested_by_user_id').notNull(),
purpose: text('purpose').notNull(),
contextManifestHash: text('context_manifest_hash').notNull(),
model: text('model').notNull(),
templateVersion: text('template_version'),
costUsdMinorUnits: integer('cost_usd_minor_units'),
resultStatus: text('result_status').notNull().default('pending'),
createdAt: timestamp('created_at').defaultNow().notNull(),
completedAt: timestamp('completed_at'),
});
// --- Trust Engine (blueprint sectiunea 5): dovezi, observatii, reputation factors ---
// subiectul poate fi orice entitate (user, organization, device) -- identificat prin
// subjectType+subjectId in loc de FK rigid, ca sa ramana generic pe tot ce alimenteaza
// scoruri de incredere/reputatie
export const observations = pgTable('observations', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull(),
subjectType: text('subject_type').notNull(),
subjectId: text('subject_id').notNull(),
metric: text('metric').notNull(),
value: text('value').notNull(),
unit: text('unit'),
source: text('source').notNull(),
observedAt: timestamp('observed_at').defaultNow().notNull(),
confidence: numeric('confidence', { precision: 5, scale: 4 }),
});
// --- Event Fabric / Outbox (blueprint sectiunea 9.1, principiul 2.1 "Events before intelligence") ---
export const outboxEvents = pgTable('outbox_events', {