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:
parent
0a4ffc41e6
commit
5656f1cc1c
4 changed files with 1522 additions and 1 deletions
187
drizzle/0000_ancient_silver_surfer.sql
Normal file
187
drizzle/0000_ancient_silver_surfer.sql
Normal 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
|
||||
);
|
||||
1158
drizzle/meta/0000_snapshot.json
Normal file
1158
drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1784655760242,
|
||||
"tag": "0000_ancient_silver_surfer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
165
src/db/schema.ts
165
src/db/schema.ts
|
|
@ -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', {
|
||||
|
|
|
|||
Loading…
Reference in a new issue