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', {
|
export const organizations = pgTable('organizations', {
|
||||||
id: uuid('id').defaultRandom().primaryKey(),
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
tenantId: uuid('tenant_id').notNull(),
|
tenantId: uuid('tenant_id').notNull(),
|
||||||
name: text('name').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(),
|
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) ---
|
// --- Identity Engine (blueprint sectiunea 11, 8.2, 8.3) ---
|
||||||
|
|
@ -36,6 +113,92 @@ export const consentRecords = pgTable('consent_records', {
|
||||||
revokedAt: timestamp('revoked_at'),
|
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") ---
|
// --- Event Fabric / Outbox (blueprint sectiunea 9.1, principiul 2.1 "Events before intelligence") ---
|
||||||
|
|
||||||
export const outboxEvents = pgTable('outbox_events', {
|
export const outboxEvents = pgTable('outbox_events', {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue