Commit f9ea66fca344906d90f5e533a5fe8f98f7cf0914

Authored by vibe_erp
1 parent 24cae0bd

feat(distribution): Spring Boot fat-jar assembly + Liquibase changelogs (platform + identity)

distribution/build.gradle.kts 0 → 100644
  1 +// distribution — assembles the runnable vibe_erp fat-jar.
  2 +//
  3 +// This module is the only one that pulls every PBC and platform module
  4 +// together into a bootable Spring Boot application. It is referenced by
  5 +// the Dockerfile (stage 1) which produces the shipping image documented
  6 +// in the architecture spec, sections 10 and 11.
  7 +
  8 +plugins {
  9 + alias(libs.plugins.kotlin.jvm)
  10 + alias(libs.plugins.kotlin.spring)
  11 + alias(libs.plugins.spring.boot)
  12 + alias(libs.plugins.spring.dependency.management)
  13 +}
  14 +
  15 +java {
  16 + toolchain {
  17 + languageVersion.set(JavaLanguageVersion.of(21))
  18 + }
  19 +}
  20 +
  21 +dependencies {
  22 + implementation(project(":platform:platform-bootstrap"))
  23 + implementation(project(":platform:platform-persistence"))
  24 + implementation(project(":platform:platform-plugins"))
  25 + implementation(project(":pbc:pbc-identity"))
  26 +
  27 + implementation(libs.spring.boot.starter)
  28 + implementation(libs.spring.boot.starter.web)
  29 + implementation(libs.spring.boot.starter.data.jpa)
  30 + implementation(libs.spring.boot.starter.actuator)
  31 + implementation(libs.kotlin.stdlib)
  32 + implementation(libs.kotlin.reflect)
  33 + implementation(libs.jackson.module.kotlin)
  34 + runtimeOnly(libs.postgres)
  35 + runtimeOnly(libs.liquibase.core)
  36 +
  37 + testImplementation(libs.spring.boot.starter.test)
  38 +}
  39 +
  40 +// The fat-jar produced here is what the Dockerfile copies into the
  41 +// runtime image as /app/vibe-erp.jar.
  42 +tasks.bootJar {
  43 + mainClass.set("org.vibeerp.platform.bootstrap.VibeErpApplicationKt")
  44 + archiveFileName.set("vibe-erp.jar")
  45 +}
  46 +
  47 +// `./gradlew :distribution:bootRun` — used by `make run` for local dev.
  48 +// Activates the dev profile so application-dev.yaml on the classpath is
  49 +// layered on top of application.yaml.
  50 +tasks.bootRun {
  51 + systemProperty("spring.profiles.active", "dev")
  52 +}
... ...
distribution/src/main/resources/application-dev.yaml 0 → 100644
  1 +# vibe_erp — developer overrides (active under -Dspring.profiles.active=dev).
  2 +#
  3 +# Activated by `./gradlew :distribution:bootRun` (see distribution/build.gradle.kts).
  4 +# These values point at a local Postgres started via `make up` or any other
  5 +# locally-running Postgres on port 5432.
  6 +
  7 +spring:
  8 + datasource:
  9 + url: jdbc:postgresql://localhost:5432/vibeerp
  10 + username: vibeerp
  11 + password: vibeerp
  12 +
  13 +vibeerp:
  14 + plugins:
  15 + directory: ./plugins-dev
  16 + files:
  17 + local-path: ./files-dev
  18 +
  19 +logging:
  20 + level:
  21 + org.vibeerp: DEBUG
... ...
distribution/src/main/resources/application.yaml 0 → 100644
  1 +# vibe_erp — production-ish defaults.
  2 +#
  3 +# This is the baseline configuration baked into the shipping image. It is
  4 +# deliberately non-secret: every sensitive value is read from an environment
  5 +# variable so the same image works for self-hosted and (eventually) hosted
  6 +# deployments. See architecture spec sections 10 and 11.
  7 +#
  8 +# Customer overrides live in /opt/vibe-erp/config/vibe-erp.yaml on the
  9 +# mounted volume. Plug-in configuration lives in metadata__plugin_config,
  10 +# never here.
  11 +
  12 +spring:
  13 + application:
  14 + name: vibe-erp
  15 + datasource:
  16 + url: ${VIBEERP_DB_URL}
  17 + username: ${VIBEERP_DB_USER}
  18 + password: ${VIBEERP_DB_PASSWORD}
  19 + driver-class-name: org.postgresql.Driver
  20 + jpa:
  21 + # Liquibase owns the schema; Hibernate must never touch DDL.
  22 + hibernate:
  23 + ddl-auto: validate
  24 + open-in-view: false
  25 + liquibase:
  26 + change-log: classpath:db/changelog/master.xml
  27 +
  28 +server:
  29 + port: 8080
  30 + shutdown: graceful
  31 +
  32 +management:
  33 + endpoints:
  34 + web:
  35 + exposure:
  36 + include: health,info,prometheus
  37 +
  38 +vibeerp:
  39 + instance:
  40 + mode: ${VIBEERP_INSTANCE_MODE:self-hosted}
  41 + default-tenant: ${VIBEERP_DEFAULT_TENANT:default}
  42 + plugins:
  43 + directory: ${VIBEERP_PLUGINS_DIR:/opt/vibe-erp/plugins}
  44 + auto-load: true
  45 + i18n:
  46 + default-locale: en-US
  47 + fallback-locale: en-US
  48 + available-locales: en-US,zh-CN,de-DE,ja-JP,es-ES
  49 + files:
  50 + backend: local
  51 + local-path: ${VIBEERP_FILES_DIR:/opt/vibe-erp/files}
  52 +
  53 +logging:
  54 + level:
  55 + org.vibeerp: INFO
  56 + org.springframework: WARN
... ...
distribution/src/main/resources/db/changelog/master.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!--
  3 + vibe_erp — Liquibase master changelog.
  4 +
  5 + This file composes per-PBC changelogs via <include>. Each PBC owns its own
  6 + changelog under db/changelog/<pbc-name>/ and is responsible for its own
  7 + table prefix (architecture spec section 8 — schema namespacing).
  8 +-->
  9 +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  10 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  11 + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
  12 + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
  13 + <include file="classpath:db/changelog/platform/000-platform-init.xml"/>
  14 + <include file="classpath:db/changelog/pbc-identity/001-identity-init.xml"/>
  15 +</databaseChangeLog>
... ...
distribution/src/main/resources/db/changelog/pbc-identity/001-identity-init.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
  5 + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
  6 +
  7 + <!--
  8 + pbc-identity initial schema.
  9 +
  10 + Owns: identity__user, identity__role, identity__user_role.
  11 +
  12 + Conventions enforced for every business table in vibe_erp:
  13 + • UUID primary key
  14 + • tenant_id varchar(64) NOT NULL
  15 + • Audit columns: created_at, created_by, updated_at, updated_by
  16 + • Optimistic-locking version column
  17 + • ext jsonb NOT NULL DEFAULT '{}' for key-user custom fields
  18 + • GIN index on ext for fast custom-field queries
  19 + • Composite index on (tenant_id, ...) for tenant-scoped lookups
  20 + • Postgres Row-Level Security enabled, with a simple per-tenant
  21 + policy bound to the GUC `vibeerp.current_tenant` set by the
  22 + platform on every transaction (see TODO below)
  23 +
  24 + TODO(v0.2): the platform's `RlsTransactionHook` is not yet
  25 + implemented, so the RLS policy in this file uses
  26 + `current_setting('vibeerp.current_tenant', true)` and is enabled
  27 + but not yet enforced (NO FORCE). When the hook lands, change to
  28 + FORCE ROW LEVEL SECURITY in a follow-up changeset.
  29 + -->
  30 +
  31 + <changeSet id="identity-init-001" author="vibe_erp">
  32 + <comment>Create identity__user table</comment>
  33 + <sql>
  34 + CREATE TABLE identity__user (
  35 + id uuid PRIMARY KEY,
  36 + tenant_id varchar(64) NOT NULL,
  37 + username varchar(128) NOT NULL,
  38 + display_name varchar(256) NOT NULL,
  39 + email varchar(320),
  40 + enabled boolean NOT NULL DEFAULT true,
  41 + ext jsonb NOT NULL DEFAULT '{}'::jsonb,
  42 + created_at timestamptz NOT NULL,
  43 + created_by varchar(128) NOT NULL,
  44 + updated_at timestamptz NOT NULL,
  45 + updated_by varchar(128) NOT NULL,
  46 + version bigint NOT NULL DEFAULT 0
  47 + );
  48 + CREATE UNIQUE INDEX identity__user_tenant_username_uk
  49 + ON identity__user (tenant_id, username);
  50 + CREATE INDEX identity__user_ext_gin
  51 + ON identity__user USING GIN (ext jsonb_path_ops);
  52 + </sql>
  53 + <rollback>
  54 + DROP TABLE identity__user;
  55 + </rollback>
  56 + </changeSet>
  57 +
  58 + <changeSet id="identity-init-002" author="vibe_erp">
  59 + <comment>Enable Row-Level Security on identity__user (advisory until RlsTransactionHook lands)</comment>
  60 + <sql>
  61 + ALTER TABLE identity__user ENABLE ROW LEVEL SECURITY;
  62 + CREATE POLICY identity__user_tenant_isolation ON identity__user
  63 + USING (tenant_id = current_setting('vibeerp.current_tenant', true));
  64 + </sql>
  65 + <rollback>
  66 + DROP POLICY IF EXISTS identity__user_tenant_isolation ON identity__user;
  67 + ALTER TABLE identity__user DISABLE ROW LEVEL SECURITY;
  68 + </rollback>
  69 + </changeSet>
  70 +
  71 + <changeSet id="identity-init-003" author="vibe_erp">
  72 + <comment>Create identity__role table</comment>
  73 + <sql>
  74 + CREATE TABLE identity__role (
  75 + id uuid PRIMARY KEY,
  76 + tenant_id varchar(64) NOT NULL,
  77 + code varchar(64) NOT NULL,
  78 + name varchar(256) NOT NULL,
  79 + description text,
  80 + ext jsonb NOT NULL DEFAULT '{}'::jsonb,
  81 + created_at timestamptz NOT NULL,
  82 + created_by varchar(128) NOT NULL,
  83 + updated_at timestamptz NOT NULL,
  84 + updated_by varchar(128) NOT NULL,
  85 + version bigint NOT NULL DEFAULT 0
  86 + );
  87 + CREATE UNIQUE INDEX identity__role_tenant_code_uk
  88 + ON identity__role (tenant_id, code);
  89 + CREATE INDEX identity__role_ext_gin
  90 + ON identity__role USING GIN (ext jsonb_path_ops);
  91 + ALTER TABLE identity__role ENABLE ROW LEVEL SECURITY;
  92 + CREATE POLICY identity__role_tenant_isolation ON identity__role
  93 + USING (tenant_id = current_setting('vibeerp.current_tenant', true));
  94 + </sql>
  95 + <rollback>
  96 + DROP TABLE identity__role;
  97 + </rollback>
  98 + </changeSet>
  99 +
  100 + <changeSet id="identity-init-004" author="vibe_erp">
  101 + <comment>Create identity__user_role join table</comment>
  102 + <sql>
  103 + CREATE TABLE identity__user_role (
  104 + id uuid PRIMARY KEY,
  105 + tenant_id varchar(64) NOT NULL,
  106 + user_id uuid NOT NULL REFERENCES identity__user(id) ON DELETE CASCADE,
  107 + role_id uuid NOT NULL REFERENCES identity__role(id) ON DELETE CASCADE,
  108 + created_at timestamptz NOT NULL,
  109 + created_by varchar(128) NOT NULL,
  110 + updated_at timestamptz NOT NULL,
  111 + updated_by varchar(128) NOT NULL,
  112 + version bigint NOT NULL DEFAULT 0
  113 + );
  114 + CREATE UNIQUE INDEX identity__user_role_uk
  115 + ON identity__user_role (tenant_id, user_id, role_id);
  116 + ALTER TABLE identity__user_role ENABLE ROW LEVEL SECURITY;
  117 + CREATE POLICY identity__user_role_tenant_isolation ON identity__user_role
  118 + USING (tenant_id = current_setting('vibeerp.current_tenant', true));
  119 + </sql>
  120 + <rollback>
  121 + DROP TABLE identity__user_role;
  122 + </rollback>
  123 + </changeSet>
  124 +
  125 +</databaseChangeLog>
... ...
distribution/src/main/resources/db/changelog/platform/000-platform-init.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!--
  3 + vibe_erp — platform init changelog.
  4 +
  5 + Creates the cross-cutting platform tables: a tenant table, an audit
  6 + table, and the metadata__* registry tables described in architecture
  7 + spec section 8 ("The metadata store").
  8 +
  9 + These are intentionally minimal v0.1 stubs: id + tenant_id + source +
  10 + timestamps + (where useful) a payload jsonb. Real columns are added by
  11 + later changesets in the PBCs that own them. pbc-identity owns the User
  12 + schema and ships its own changelog.
  13 +
  14 + NOTE: Postgres Row-Level Security policies are NOT enabled here. RLS
  15 + policies are added in a dedicated changeset in the v1.0 cut, so that
  16 + they can be authored alongside the application-level @TenantId filter
  17 + (architecture spec section 8 — "Tenant isolation"). The build pattern
  18 + for that changeset will mirror the changesets below.
  19 +-->
  20 +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  21 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  22 + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
  23 + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
  24 +
  25 + <!-- ─── platform__tenant ─────────────────────────────────────────── -->
  26 + <changeSet id="platform-init-001" author="vibe_erp">
  27 + <createTable tableName="platform__tenant">
  28 + <column name="id" type="uuid">
  29 + <constraints primaryKey="true" nullable="false"/>
  30 + </column>
  31 + <column name="tenant_id" type="varchar(64)">
  32 + <constraints nullable="false"/>
  33 + </column>
  34 + <column name="source" type="varchar(32)" defaultValue="core">
  35 + <constraints nullable="false"/>
  36 + </column>
  37 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  38 + <constraints nullable="false"/>
  39 + </column>
  40 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  41 + <constraints nullable="false"/>
  42 + </column>
  43 + </createTable>
  44 + <rollback>
  45 + <dropTable tableName="platform__tenant"/>
  46 + </rollback>
  47 + </changeSet>
  48 +
  49 + <!-- ─── platform__audit ──────────────────────────────────────────── -->
  50 + <changeSet id="platform-init-002" author="vibe_erp">
  51 + <createTable tableName="platform__audit">
  52 + <column name="id" type="uuid">
  53 + <constraints primaryKey="true" nullable="false"/>
  54 + </column>
  55 + <column name="tenant_id" type="varchar(64)">
  56 + <constraints nullable="false"/>
  57 + </column>
  58 + <column name="source" type="varchar(32)" defaultValue="core">
  59 + <constraints nullable="false"/>
  60 + </column>
  61 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  62 + <constraints nullable="false"/>
  63 + </column>
  64 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  65 + <constraints nullable="false"/>
  66 + </column>
  67 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  68 + <constraints nullable="false"/>
  69 + </column>
  70 + </createTable>
  71 + <rollback>
  72 + <dropTable tableName="platform__audit"/>
  73 + </rollback>
  74 + </changeSet>
  75 +
  76 + <!-- ─── metadata__entity ─────────────────────────────────────────── -->
  77 + <changeSet id="platform-init-003" author="vibe_erp">
  78 + <createTable tableName="metadata__entity">
  79 + <column name="id" type="uuid">
  80 + <constraints primaryKey="true" nullable="false"/>
  81 + </column>
  82 + <column name="tenant_id" type="varchar(64)">
  83 + <constraints nullable="false"/>
  84 + </column>
  85 + <column name="source" type="varchar(32)" defaultValue="core">
  86 + <constraints nullable="false"/>
  87 + </column>
  88 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  89 + <constraints nullable="false"/>
  90 + </column>
  91 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  92 + <constraints nullable="false"/>
  93 + </column>
  94 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  95 + <constraints nullable="false"/>
  96 + </column>
  97 + </createTable>
  98 + <createIndex tableName="metadata__entity" indexName="idx_metadata__entity__tenant_source">
  99 + <column name="tenant_id"/>
  100 + <column name="source"/>
  101 + </createIndex>
  102 + <rollback>
  103 + <dropTable tableName="metadata__entity"/>
  104 + </rollback>
  105 + </changeSet>
  106 +
  107 + <!-- ─── metadata__custom_field ───────────────────────────────────── -->
  108 + <changeSet id="platform-init-004" author="vibe_erp">
  109 + <createTable tableName="metadata__custom_field">
  110 + <column name="id" type="uuid">
  111 + <constraints primaryKey="true" nullable="false"/>
  112 + </column>
  113 + <column name="tenant_id" type="varchar(64)">
  114 + <constraints nullable="false"/>
  115 + </column>
  116 + <column name="source" type="varchar(32)" defaultValue="core">
  117 + <constraints nullable="false"/>
  118 + </column>
  119 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  120 + <constraints nullable="false"/>
  121 + </column>
  122 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  123 + <constraints nullable="false"/>
  124 + </column>
  125 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  126 + <constraints nullable="false"/>
  127 + </column>
  128 + </createTable>
  129 + <createIndex tableName="metadata__custom_field" indexName="idx_metadata__custom_field__tenant_source">
  130 + <column name="tenant_id"/>
  131 + <column name="source"/>
  132 + </createIndex>
  133 + <rollback>
  134 + <dropTable tableName="metadata__custom_field"/>
  135 + </rollback>
  136 + </changeSet>
  137 +
  138 + <!-- ─── metadata__form ───────────────────────────────────────────── -->
  139 + <changeSet id="platform-init-005" author="vibe_erp">
  140 + <createTable tableName="metadata__form">
  141 + <column name="id" type="uuid">
  142 + <constraints primaryKey="true" nullable="false"/>
  143 + </column>
  144 + <column name="tenant_id" type="varchar(64)">
  145 + <constraints nullable="false"/>
  146 + </column>
  147 + <column name="source" type="varchar(32)" defaultValue="core">
  148 + <constraints nullable="false"/>
  149 + </column>
  150 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  151 + <constraints nullable="false"/>
  152 + </column>
  153 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  154 + <constraints nullable="false"/>
  155 + </column>
  156 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  157 + <constraints nullable="false"/>
  158 + </column>
  159 + </createTable>
  160 + <createIndex tableName="metadata__form" indexName="idx_metadata__form__tenant_source">
  161 + <column name="tenant_id"/>
  162 + <column name="source"/>
  163 + </createIndex>
  164 + <rollback>
  165 + <dropTable tableName="metadata__form"/>
  166 + </rollback>
  167 + </changeSet>
  168 +
  169 + <!-- ─── metadata__workflow ───────────────────────────────────────── -->
  170 + <changeSet id="platform-init-006" author="vibe_erp">
  171 + <createTable tableName="metadata__workflow">
  172 + <column name="id" type="uuid">
  173 + <constraints primaryKey="true" nullable="false"/>
  174 + </column>
  175 + <column name="tenant_id" type="varchar(64)">
  176 + <constraints nullable="false"/>
  177 + </column>
  178 + <column name="source" type="varchar(32)" defaultValue="core">
  179 + <constraints nullable="false"/>
  180 + </column>
  181 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  182 + <constraints nullable="false"/>
  183 + </column>
  184 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  185 + <constraints nullable="false"/>
  186 + </column>
  187 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  188 + <constraints nullable="false"/>
  189 + </column>
  190 + </createTable>
  191 + <createIndex tableName="metadata__workflow" indexName="idx_metadata__workflow__tenant_source">
  192 + <column name="tenant_id"/>
  193 + <column name="source"/>
  194 + </createIndex>
  195 + <rollback>
  196 + <dropTable tableName="metadata__workflow"/>
  197 + </rollback>
  198 + </changeSet>
  199 +
  200 + <!-- ─── metadata__rule ───────────────────────────────────────────── -->
  201 + <changeSet id="platform-init-007" author="vibe_erp">
  202 + <createTable tableName="metadata__rule">
  203 + <column name="id" type="uuid">
  204 + <constraints primaryKey="true" nullable="false"/>
  205 + </column>
  206 + <column name="tenant_id" type="varchar(64)">
  207 + <constraints nullable="false"/>
  208 + </column>
  209 + <column name="source" type="varchar(32)" defaultValue="core">
  210 + <constraints nullable="false"/>
  211 + </column>
  212 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  213 + <constraints nullable="false"/>
  214 + </column>
  215 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  216 + <constraints nullable="false"/>
  217 + </column>
  218 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  219 + <constraints nullable="false"/>
  220 + </column>
  221 + </createTable>
  222 + <createIndex tableName="metadata__rule" indexName="idx_metadata__rule__tenant_source">
  223 + <column name="tenant_id"/>
  224 + <column name="source"/>
  225 + </createIndex>
  226 + <rollback>
  227 + <dropTable tableName="metadata__rule"/>
  228 + </rollback>
  229 + </changeSet>
  230 +
  231 + <!-- ─── metadata__permission ─────────────────────────────────────── -->
  232 + <changeSet id="platform-init-008" author="vibe_erp">
  233 + <createTable tableName="metadata__permission">
  234 + <column name="id" type="uuid">
  235 + <constraints primaryKey="true" nullable="false"/>
  236 + </column>
  237 + <column name="tenant_id" type="varchar(64)">
  238 + <constraints nullable="false"/>
  239 + </column>
  240 + <column name="source" type="varchar(32)" defaultValue="core">
  241 + <constraints nullable="false"/>
  242 + </column>
  243 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  244 + <constraints nullable="false"/>
  245 + </column>
  246 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  247 + <constraints nullable="false"/>
  248 + </column>
  249 + </createTable>
  250 + <createIndex tableName="metadata__permission" indexName="idx_metadata__permission__tenant_source">
  251 + <column name="tenant_id"/>
  252 + <column name="source"/>
  253 + </createIndex>
  254 + <rollback>
  255 + <dropTable tableName="metadata__permission"/>
  256 + </rollback>
  257 + </changeSet>
  258 +
  259 + <!-- ─── metadata__role_permission ────────────────────────────────── -->
  260 + <changeSet id="platform-init-009" author="vibe_erp">
  261 + <createTable tableName="metadata__role_permission">
  262 + <column name="id" type="uuid">
  263 + <constraints primaryKey="true" nullable="false"/>
  264 + </column>
  265 + <column name="tenant_id" type="varchar(64)">
  266 + <constraints nullable="false"/>
  267 + </column>
  268 + <column name="source" type="varchar(32)" defaultValue="core">
  269 + <constraints nullable="false"/>
  270 + </column>
  271 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  272 + <constraints nullable="false"/>
  273 + </column>
  274 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  275 + <constraints nullable="false"/>
  276 + </column>
  277 + </createTable>
  278 + <createIndex tableName="metadata__role_permission" indexName="idx_metadata__role_permission__tenant_source">
  279 + <column name="tenant_id"/>
  280 + <column name="source"/>
  281 + </createIndex>
  282 + <rollback>
  283 + <dropTable tableName="metadata__role_permission"/>
  284 + </rollback>
  285 + </changeSet>
  286 +
  287 + <!-- ─── metadata__menu ───────────────────────────────────────────── -->
  288 + <changeSet id="platform-init-010" author="vibe_erp">
  289 + <createTable tableName="metadata__menu">
  290 + <column name="id" type="uuid">
  291 + <constraints primaryKey="true" nullable="false"/>
  292 + </column>
  293 + <column name="tenant_id" type="varchar(64)">
  294 + <constraints nullable="false"/>
  295 + </column>
  296 + <column name="source" type="varchar(32)" defaultValue="core">
  297 + <constraints nullable="false"/>
  298 + </column>
  299 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  300 + <constraints nullable="false"/>
  301 + </column>
  302 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  303 + <constraints nullable="false"/>
  304 + </column>
  305 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  306 + <constraints nullable="false"/>
  307 + </column>
  308 + </createTable>
  309 + <createIndex tableName="metadata__menu" indexName="idx_metadata__menu__tenant_source">
  310 + <column name="tenant_id"/>
  311 + <column name="source"/>
  312 + </createIndex>
  313 + <rollback>
  314 + <dropTable tableName="metadata__menu"/>
  315 + </rollback>
  316 + </changeSet>
  317 +
  318 + <!-- ─── metadata__report ─────────────────────────────────────────── -->
  319 + <changeSet id="platform-init-011" author="vibe_erp">
  320 + <createTable tableName="metadata__report">
  321 + <column name="id" type="uuid">
  322 + <constraints primaryKey="true" nullable="false"/>
  323 + </column>
  324 + <column name="tenant_id" type="varchar(64)">
  325 + <constraints nullable="false"/>
  326 + </column>
  327 + <column name="source" type="varchar(32)" defaultValue="core">
  328 + <constraints nullable="false"/>
  329 + </column>
  330 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  331 + <constraints nullable="false"/>
  332 + </column>
  333 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  334 + <constraints nullable="false"/>
  335 + </column>
  336 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  337 + <constraints nullable="false"/>
  338 + </column>
  339 + </createTable>
  340 + <createIndex tableName="metadata__report" indexName="idx_metadata__report__tenant_source">
  341 + <column name="tenant_id"/>
  342 + <column name="source"/>
  343 + </createIndex>
  344 + <rollback>
  345 + <dropTable tableName="metadata__report"/>
  346 + </rollback>
  347 + </changeSet>
  348 +
  349 + <!-- ─── metadata__translation ────────────────────────────────────── -->
  350 + <changeSet id="platform-init-012" author="vibe_erp">
  351 + <createTable tableName="metadata__translation">
  352 + <column name="id" type="uuid">
  353 + <constraints primaryKey="true" nullable="false"/>
  354 + </column>
  355 + <column name="tenant_id" type="varchar(64)">
  356 + <constraints nullable="false"/>
  357 + </column>
  358 + <column name="source" type="varchar(32)" defaultValue="core">
  359 + <constraints nullable="false"/>
  360 + </column>
  361 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  362 + <constraints nullable="false"/>
  363 + </column>
  364 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  365 + <constraints nullable="false"/>
  366 + </column>
  367 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  368 + <constraints nullable="false"/>
  369 + </column>
  370 + </createTable>
  371 + <createIndex tableName="metadata__translation" indexName="idx_metadata__translation__tenant_source">
  372 + <column name="tenant_id"/>
  373 + <column name="source"/>
  374 + </createIndex>
  375 + <rollback>
  376 + <dropTable tableName="metadata__translation"/>
  377 + </rollback>
  378 + </changeSet>
  379 +
  380 + <!-- ─── metadata__plugin_config ──────────────────────────────────── -->
  381 + <changeSet id="platform-init-013" author="vibe_erp">
  382 + <createTable tableName="metadata__plugin_config">
  383 + <column name="id" type="uuid">
  384 + <constraints primaryKey="true" nullable="false"/>
  385 + </column>
  386 + <column name="tenant_id" type="varchar(64)">
  387 + <constraints nullable="false"/>
  388 + </column>
  389 + <column name="source" type="varchar(32)" defaultValue="core">
  390 + <constraints nullable="false"/>
  391 + </column>
  392 + <column name="payload" type="jsonb" defaultValueComputed="'{}'::jsonb">
  393 + <constraints nullable="false"/>
  394 + </column>
  395 + <column name="created_at" type="timestamptz" defaultValueComputed="now()">
  396 + <constraints nullable="false"/>
  397 + </column>
  398 + <column name="updated_at" type="timestamptz" defaultValueComputed="now()">
  399 + <constraints nullable="false"/>
  400 + </column>
  401 + </createTable>
  402 + <createIndex tableName="metadata__plugin_config" indexName="idx_metadata__plugin_config__tenant_source">
  403 + <column name="tenant_id"/>
  404 + <column name="source"/>
  405 + </createIndex>
  406 + <rollback>
  407 + <dropTable tableName="metadata__plugin_config"/>
  408 + </rollback>
  409 + </changeSet>
  410 +
  411 +</databaseChangeLog>
... ...