Per-decorator migration kit / prereq surface #12

Closed
opened 2026-04-29 16:40:20 +02:00 by uwe.admin · 10 comments
Owner

Each decorator in oatpp-authkit/repo/ carries an implicit prerequisite that the consumer's schema (or runtime environment) has to satisfy. Today these prereqs live only in header doc-comments, which means a consumer who stacks the wrong decorator on the wrong table only finds out at runtime via UNIQUE-violation crashes, missing-table errors, or silently wrong audit data.

This issue tracks adding a small, uniform "decorator prereq" surface — both human-readable and machine-checkable — so consumers can wire the stack up safely.

Concrete shortcomings today

  • TemporalRepository: needs three oatpp::String fields registered via OATPP_AUTHKIT_REGISTER_TEMPORAL, AND a multi-version-tolerant unique index — i.e. UNIQUE(entity_id) WHERE valid_until = SENTINEL rather than UNIQUE(entity_id). Discovered while wiring the fewo-webapp Person pilot (uwe.admin/fewo-webapp#457): the schema's whole-column unique index makes every save attempt fail because TemporalRepository's "close prior + insert new" emits two rows per entity_id.
  • AuditLogRepository: needs an IAuditSink implementation, but says nothing about the storage shape that sink writes to.
  • ScopeGuardRepository: no schema needs, but no compile-time check that the predicate is non-null either.
  • TemporalFieldTraits: forgetting to register is a compile error today (good), but the error message points at the wrong line and doesn't tell the user which fields to register.

Scope

Add a repo/Prereq.hpp header with two parts:

  1. Static doc constant per decorator. Each decorator declares a static constexpr const char* PREREQ_SQL = ...; (or empty string for runtime-only decorators) listing the SQL it expects the consumer's schema to satisfy. Consumers can #include and emit it in their schema.sql via concatenation, or paste it directly.

  2. Runtime probe. Each decorator gains an optional void verifyPrereq(DbClient&) method that runs at construction time (or via a separate ensurePrereq() call) to check the schema actually matches — e.g. introspect SQLite's sqlite_master for the expected partial unique index. Throws a clear, decorator-specific exception if not.

Each decorator's exception message names itself, lists the failed expectation, and suggests the SQL to fix it. No more "UNIQUE constraint failed" with no hint of why.

Constraints

  • Pure additive — no breaking changes to existing decorators.
  • Optional — consumers that don't want runtime probing don't pay for it.
  • Decorator-local — no global registry; each decorator owns its own prereq.

Out of scope

  • Generating migrations automatically. Consumers still write their own schema.sql / migration scripts; this issue just gives them a canonical SQL snippet to paste.
  • Cross-decorator interactions (e.g. "if you stack Audit on Temporal, you also need ..."). Current decorators are independent; revisit if that changes.

Acceptance

  • Prereq.hpp defines a small IDecoratorPrereq interface (or just a free-function convention) and a decoratorPrereqOk(...) helper that runs all prereqs in a stack at startup.
  • TemporalRepository<T> exposes static constexpr const char* PREREQ_SQL with the partial-unique-index template (with a {table} placeholder).
  • AuditLogRepository<T> documents the expected IAuditSink storage shape (text, schema-free — sinks vary too much to enforce SQL).
  • ScopeGuardRepository<T> adds a static_assert (or runtime null-check) that the predicate is non-null.
  • Test verifies the runtime probe throws a clear error when the schema is wrong.
  • README updated with a "decorator prereqs" section listing each decorator's expectations.

Decision deferred to implementation time

Whether the prereq probe is mandatory at construction time or opt-in via a separate call. Recommend opt-in — keeps construction cheap, lets consumers choose where the schema check happens.

Each decorator in `oatpp-authkit/repo/` carries an implicit prerequisite that the consumer's schema (or runtime environment) has to satisfy. Today these prereqs live only in header doc-comments, which means a consumer who stacks the wrong decorator on the wrong table only finds out at runtime via UNIQUE-violation crashes, missing-table errors, or silently wrong audit data. This issue tracks adding a small, uniform "decorator prereq" surface — both human-readable and machine-checkable — so consumers can wire the stack up safely. ## Concrete shortcomings today - **TemporalRepository**: needs three `oatpp::String` fields registered via `OATPP_AUTHKIT_REGISTER_TEMPORAL`, AND a multi-version-tolerant unique index — i.e. `UNIQUE(entity_id) WHERE valid_until = SENTINEL` rather than `UNIQUE(entity_id)`. Discovered while wiring the fewo-webapp Person pilot (uwe.admin/fewo-webapp#457): the schema's whole-column unique index makes every save attempt fail because TemporalRepository's "close prior + insert new" emits two rows per entity_id. - **AuditLogRepository**: needs an `IAuditSink` implementation, but says nothing about the storage shape that sink writes to. - **ScopeGuardRepository**: no schema needs, but no compile-time check that the predicate is non-null either. - **TemporalFieldTraits**: forgetting to register is a compile error today (good), but the error message points at the wrong line and doesn't tell the user *which* fields to register. ## Scope Add a `repo/Prereq.hpp` header with two parts: 1. **Static doc constant per decorator.** Each decorator declares a `static constexpr const char* PREREQ_SQL = ...;` (or empty string for runtime-only decorators) listing the SQL it expects the consumer's schema to satisfy. Consumers can `#include` and emit it in their schema.sql via concatenation, or paste it directly. 2. **Runtime probe.** Each decorator gains an optional `void verifyPrereq(DbClient&)` method that runs at construction time (or via a separate `ensurePrereq()` call) to check the schema actually matches — e.g. introspect SQLite's `sqlite_master` for the expected partial unique index. Throws a clear, decorator-specific exception if not. Each decorator's exception message names itself, lists the failed expectation, and suggests the SQL to fix it. No more "UNIQUE constraint failed" with no hint of why. ## Constraints - Pure additive — no breaking changes to existing decorators. - Optional — consumers that don't want runtime probing don't pay for it. - Decorator-local — no global registry; each decorator owns its own prereq. ## Out of scope - Generating migrations automatically. Consumers still write their own schema.sql / migration scripts; this issue just gives them a canonical SQL snippet to paste. - Cross-decorator interactions (e.g. "if you stack Audit on Temporal, you also need ..."). Current decorators are independent; revisit if that changes. ## Acceptance - `Prereq.hpp` defines a small `IDecoratorPrereq` interface (or just a free-function convention) and a `decoratorPrereqOk(...)` helper that runs all prereqs in a stack at startup. - `TemporalRepository<T>` exposes `static constexpr const char* PREREQ_SQL` with the partial-unique-index template (with a `{table}` placeholder). - `AuditLogRepository<T>` documents the expected `IAuditSink` storage shape (text, schema-free — sinks vary too much to enforce SQL). - `ScopeGuardRepository<T>` adds a static_assert (or runtime null-check) that the predicate is non-null. - Test verifies the runtime probe throws a clear error when the schema is wrong. - README updated with a "decorator prereqs" section listing each decorator's expectations. ## Decision deferred to implementation time Whether the prereq probe is mandatory at construction time or opt-in via a separate call. Recommend opt-in — keeps construction cheap, lets consumers choose where the schema check happens.
Author
Owner

Agent Evaluation

Feasibility: Medium. Two distinct surfaces (static doc constants + optional runtime probes) but each is small per decorator. The runtime probe is the hardest piece — it requires SQLite-specific introspection of sqlite_master, which couples a header-only authkit to a specific database engine. Mitigation: keep the probe optional and behind a #ifdef OATPP_AUTHKIT_SQLITE guard, or accept a std::function<bool(const std::string& expectedSql)> that the consumer wires up to its own DbClient.

Impact: High for adopters. Discovered while wiring fewo-webapp#457 — without a prereq surface, every consumer rediscovers the same gotchas (UNIQUE-violation crashes, silently wrong audit data, missing tables) at runtime. Static PREREQ_SQL snippets are a 5-LOC win for documentation that pays out on every adoption. Runtime probes are a stronger guarantee but cost more.

Effort: Medium. ~50 LOC per decorator (a constant + a virtual verifyPrereq member + tests), plus a small driver in Prereq.hpp (~30 LOC). Total ~250 LOC including tests.

Recommendation: Accept, but scope down to the static PREREQ_SQL constants only for v1. Defer runtime probes (verifyPrereq) to a follow-up if they prove necessary — they add real complexity (DB-engine coupling, when to run them, what to do on failure) and the static constants alone solve the documentation problem that motivated this issue.

Implementation plan (v1, static-only)

  1. New header include/oatpp-authkit/repo/Prereq.hpp — small struct DecoratorPrereq with name, requiresSql (string, may contain {table} placeholder), requiresRuntime (free-form text).
  2. Each decorator that has a schema prereq exposes static constexpr DecoratorPrereq PREREQ; — TemporalRepository emits the partial unique index template, others stay empty / runtime-only.
  3. Free function instantiatePrereqSql(const DecoratorPrereq&, std::string_view tableName) — replaces {table} and returns ready-to-paste SQL.
  4. README adds a "Decorator prereqs" table with one row per decorator, showing the SQL the consumer should add to their schema.
  5. Test verifies the templated SQL substitutes correctly and that each decorator with a schema prereq exposes a non-empty PREREQ_SQL.

Decision needed

Check one (edit this comment):

  • Option A — Accept v1 (static PREREQ_SQL only). Defer runtime probes to a follow-up.
  • Option B — Accept full scope (static + runtime probes). Larger effort, stronger guarantee, accepts SQLite coupling.
  • Option C — Defer entirely. Wait until a second decorator-adoption pain point shows up before formalising the surface.
## Agent Evaluation **Feasibility:** Medium. Two distinct surfaces (static doc constants + optional runtime probes) but each is small per decorator. The runtime probe is the hardest piece — it requires SQLite-specific introspection of `sqlite_master`, which couples a header-only authkit to a specific database engine. Mitigation: keep the probe optional and behind a `#ifdef OATPP_AUTHKIT_SQLITE` guard, or accept a `std::function<bool(const std::string& expectedSql)>` that the consumer wires up to its own DbClient. **Impact:** High for adopters. Discovered while wiring fewo-webapp#457 — without a prereq surface, every consumer rediscovers the same gotchas (UNIQUE-violation crashes, silently wrong audit data, missing tables) at runtime. Static `PREREQ_SQL` snippets are a 5-LOC win for documentation that pays out on every adoption. Runtime probes are a stronger guarantee but cost more. **Effort:** Medium. ~50 LOC per decorator (a constant + a virtual `verifyPrereq` member + tests), plus a small driver in `Prereq.hpp` (~30 LOC). Total ~250 LOC including tests. **Recommendation:** Accept, but **scope down to the static `PREREQ_SQL` constants only** for v1. Defer runtime probes (`verifyPrereq`) to a follow-up if they prove necessary — they add real complexity (DB-engine coupling, when to run them, what to do on failure) and the static constants alone solve the documentation problem that motivated this issue. ### Implementation plan (v1, static-only) 1. New header `include/oatpp-authkit/repo/Prereq.hpp` — small struct `DecoratorPrereq` with `name`, `requiresSql` (string, may contain `{table}` placeholder), `requiresRuntime` (free-form text). 2. Each decorator that has a schema prereq exposes `static constexpr DecoratorPrereq PREREQ;` — TemporalRepository emits the partial unique index template, others stay empty / runtime-only. 3. Free function `instantiatePrereqSql(const DecoratorPrereq&, std::string_view tableName)` — replaces `{table}` and returns ready-to-paste SQL. 4. README adds a "Decorator prereqs" table with one row per decorator, showing the SQL the consumer should add to their schema. 5. Test verifies the templated SQL substitutes correctly and that each decorator with a schema prereq exposes a non-empty `PREREQ_SQL`. ### Decision needed Check one (edit this comment): - [x] **Option A — Accept v1 (static `PREREQ_SQL` only).** Defer runtime probes to a follow-up. - [ ] **Option B — Accept full scope (static + runtime probes).** Larger effort, stronger guarantee, accepts SQLite coupling. - [ ] **Option C — Defer entirely.** Wait until a second decorator-adoption pain point shows up before formalising the surface.
uwe.admin added the
evaluated
label 2026-04-29 16:41:30 +02:00
Author
Owner

Evaluated #12 (Medium) — recommend Accept v1 (static PREREQ_SQL only); defer runtime probes.

Evaluated #12 (Medium) — recommend Accept v1 (static PREREQ_SQL only); defer runtime probes.
Author
Owner

Re-evaluation

Owner selected Option A — Accept v1 (static PREREQ_SQL only); defer runtime probes.

That matches the recommendation, so the implementation plan from the original evaluation stands unchanged:

  1. New header include/oatpp-authkit/repo/Prereq.hppDecoratorPrereq struct with name, requiresSql (with {table} placeholder), requiresRuntime doc string
  2. Each decorator that has a schema prereq exposes static constexpr DecoratorPrereq PREREQ; — TemporalRepository carries the partial unique index template; ScopeGuard / Audit stay empty or runtime-only
  3. Free function instantiatePrereqSql(const DecoratorPrereq&, std::string_view tableName) substitutes {table} and returns ready-to-paste SQL
  4. README "Decorator prereqs" section listing each decorator's expectations
  5. Test verifies templated SQL substitutes correctly and each decorator with a schema prereq exposes a non-empty PREREQ_SQL

No runtime verifyPrereq member, no SQLite introspection, no DB-engine coupling. The requiresRuntime field on DecoratorPrereq becomes a free-form string the README can render — purely documentation.

Awaiting accepted label to proceed.

## Re-evaluation Owner selected **Option A — Accept v1 (static `PREREQ_SQL` only); defer runtime probes.** That matches the recommendation, so the implementation plan from the original evaluation stands unchanged: 1. New header `include/oatpp-authkit/repo/Prereq.hpp` — `DecoratorPrereq` struct with `name`, `requiresSql` (with `{table}` placeholder), `requiresRuntime` doc string 2. Each decorator that has a schema prereq exposes `static constexpr DecoratorPrereq PREREQ;` — TemporalRepository carries the partial unique index template; ScopeGuard / Audit stay empty or runtime-only 3. Free function `instantiatePrereqSql(const DecoratorPrereq&, std::string_view tableName)` substitutes `{table}` and returns ready-to-paste SQL 4. README "Decorator prereqs" section listing each decorator's expectations 5. Test verifies templated SQL substitutes correctly and each decorator with a schema prereq exposes a non-empty `PREREQ_SQL` No runtime `verifyPrereq` member, no SQLite introspection, no DB-engine coupling. The `requiresRuntime` field on `DecoratorPrereq` becomes a free-form string the README can render — purely documentation. Awaiting `accepted` label to proceed.
Author
Owner

Re-evaluated #12 — owner picked Option A (static PREREQ_SQL only); plan unchanged from initial evaluation; awaiting accept.

Re-evaluated #12 — owner picked Option A (static PREREQ_SQL only); plan unchanged from initial evaluation; awaiting accept.
Author
Owner

Re-evaluation — scope extension

Owner asked to extend scope so each decorator owns its idempotent DB migration alongside its prereq surface, keeping aspects (temporal, audit, scope) independent but bundled with their schema. v1 stays small but adds a thin migration runner so consumers don't hand-paste SQL.

Extended scope

Two migration kinds, both decorator-local, both idempotent, both run at startup:

1. Additive prereqs — PREREQ_SQL (already in v1)

For decorators that need new tables/columns/indexes alongside the entity table. Pure CREATE TABLE IF NOT EXISTS …, CREATE INDEX IF NOT EXISTS …. Re-running is a no-op. AuditLogRepository's audit_log table is the model case. Each decorator exposes static constexpr DecoratorPrereq PREREQ; with a {table} placeholder.

2. Reshape migrations — RESHAPE_STEPS (new)

For decorators that need to change the entity's own table shape. TemporalRepository is the only current case: composite UNIQUE on (entity_id, valid_until), valid_from/valid_until columns, FK reshape on every child. Not safely idempotent in the naive sense — you can't ADD COLUMN valid_until twice. Solution: each step is a {detect, apply} pair where detect is a SQL probe (PRAGMA table_info(...), sqlite_master query) that returns true iff the step has already been applied. Runner skips applied steps; re-running is a no-op.

struct ReshapeStep {
    const char* name;        // "add_temporal_columns"
    const char* detectSql;   // "SELECT 1 FROM pragma_table_info(...) WHERE name='valid_from'"
    const char* applySql;    // "ALTER TABLE {table} ADD COLUMN valid_from TEXT NOT NULL DEFAULT ..."
};
static constexpr std::array<ReshapeStep, N> RESHAPE_STEPS = { ... };

3. Migration runner — applyDecoratorMigrations<Stack>(DbClient&, tableName)

Walks the wired decorator stack at startup, executes every PREREQ_SQL (idempotent by construction) and every RESHAPE_STEPS entry whose detectSql returns false. Records applied step names in a oatpp_authkit_schema_migrations(decorator, table, step, applied_at) table for observability — but the detect-probe is the source of truth, so even if that table is wiped the runner is still safe.

Consumer wiring becomes:

auto stack = ScopeGuard(Temporal(Audit(Concrete(personDb))));
applyDecoratorMigrations(stack, "persons");  // safe to call on every startup

Why this fits the original aim

  • Aspects independent: TemporalRepository ships its temporal columns + composite unique. AuditLogRepository ships its audit_log table. ScopeGuardRepository ships nothing. Stacking them composes the migrations automatically.
  • Bundled with code: the SQL lives next to the decorator, can't drift, ships with the same tag.
  • Idempotent: PREREQ_SQL via IF NOT EXISTS; RESHAPE_STEPS via per-step detect probes.

Effort

Moves from Medium to Large:

  • v1 PREREQ_SQL surface: ~150 LOC (as before)
  • ReshapeStep struct + per-decorator arrays: ~50 LOC + populate Temporal's array (~80 LOC across detect+apply pairs)
  • Migration runner with stack traversal: ~80 LOC
  • schema_migrations bookkeeping table: ~40 LOC
  • Tests (apply twice, verify no-op; apply on already-shaped table; partial application recovery): ~150 LOC
  • README expansion: docs

Total ~550–650 LOC.

Interaction with fewo-webapp#459

#459 becomes "wire applyDecoratorMigrations into App.cpp at startup, then remove the now-redundant SQL from sql/schema.sql" instead of "write a bespoke schema migration script". Cleaner — and the same machinery serves every future temporal table.

Revised acceptance

  • Prereq.hpp defines DecoratorPrereq (with PREREQ_SQL) and ReshapeStep
  • Each decorator exposes both as appropriate
  • applyDecoratorMigrations<Stack>(DbClient&, tableName) walks the stack, runs PREREQ_SQL, runs RESHAPE_STEPS gated on detect probes, records applied steps in oatpp_authkit_schema_migrations
  • Tests cover: fresh-table apply, re-apply (no-op), partial-state recovery
  • README "Decorator migrations" section per decorator

Decision needed

Check one (edit this comment):

  • Accept extended scope as written. Bundles idempotent migrations with each decorator; closes #459 by reuse.
  • Extend, but defer reshape migrations. Keep v1 = static PREREQ_SQL only; reshape migrations get their own follow-up issue once a second reshape case appears.
  • Revert to v1. The reshape complexity outweighs the bundling benefit for now.
## Re-evaluation — scope extension Owner asked to extend scope so each decorator owns its **idempotent DB migration** alongside its prereq surface, keeping aspects (temporal, audit, scope) independent but bundled with their schema. v1 stays small but adds a thin migration runner so consumers don't hand-paste SQL. ### Extended scope Two migration kinds, both decorator-local, both idempotent, both run at startup: **1. Additive prereqs — `PREREQ_SQL`** (already in v1) For decorators that need *new* tables/columns/indexes alongside the entity table. Pure `CREATE TABLE IF NOT EXISTS …`, `CREATE INDEX IF NOT EXISTS …`. Re-running is a no-op. AuditLogRepository's audit_log table is the model case. Each decorator exposes `static constexpr DecoratorPrereq PREREQ;` with a `{table}` placeholder. **2. Reshape migrations — `RESHAPE_STEPS`** (new) For decorators that need to *change* the entity's own table shape. TemporalRepository is the only current case: composite UNIQUE on `(entity_id, valid_until)`, valid_from/valid_until columns, FK reshape on every child. Not safely idempotent in the naive sense — you can't `ADD COLUMN valid_until` twice. Solution: each step is a `{detect, apply}` pair where `detect` is a SQL probe (`PRAGMA table_info(...)`, `sqlite_master` query) that returns true iff the step has already been applied. Runner skips applied steps; re-running is a no-op. ```cpp struct ReshapeStep { const char* name; // "add_temporal_columns" const char* detectSql; // "SELECT 1 FROM pragma_table_info(...) WHERE name='valid_from'" const char* applySql; // "ALTER TABLE {table} ADD COLUMN valid_from TEXT NOT NULL DEFAULT ..." }; static constexpr std::array<ReshapeStep, N> RESHAPE_STEPS = { ... }; ``` **3. Migration runner — `applyDecoratorMigrations<Stack>(DbClient&, tableName)`** Walks the wired decorator stack at startup, executes every `PREREQ_SQL` (idempotent by construction) and every `RESHAPE_STEPS` entry whose `detectSql` returns false. Records applied step names in a `oatpp_authkit_schema_migrations(decorator, table, step, applied_at)` table for observability — but the detect-probe is the source of truth, so even if that table is wiped the runner is still safe. Consumer wiring becomes: ```cpp auto stack = ScopeGuard(Temporal(Audit(Concrete(personDb)))); applyDecoratorMigrations(stack, "persons"); // safe to call on every startup ``` ### Why this fits the original aim - **Aspects independent**: TemporalRepository ships its temporal columns + composite unique. AuditLogRepository ships its audit_log table. ScopeGuardRepository ships nothing. Stacking them composes the migrations automatically. - **Bundled with code**: the SQL lives next to the decorator, can't drift, ships with the same tag. - **Idempotent**: PREREQ_SQL via `IF NOT EXISTS`; RESHAPE_STEPS via per-step detect probes. ### Effort Moves from Medium to **Large**: - v1 PREREQ_SQL surface: ~150 LOC (as before) - ReshapeStep struct + per-decorator arrays: ~50 LOC + populate Temporal's array (~80 LOC across detect+apply pairs) - Migration runner with stack traversal: ~80 LOC - schema_migrations bookkeeping table: ~40 LOC - Tests (apply twice, verify no-op; apply on already-shaped table; partial application recovery): ~150 LOC - README expansion: docs Total ~550–650 LOC. ### Interaction with fewo-webapp#459 #459 becomes "wire `applyDecoratorMigrations` into App.cpp at startup, then remove the now-redundant SQL from `sql/schema.sql`" instead of "write a bespoke schema migration script". Cleaner — and the same machinery serves every future temporal table. ### Revised acceptance - `Prereq.hpp` defines `DecoratorPrereq` (with `PREREQ_SQL`) **and** `ReshapeStep` - Each decorator exposes both as appropriate - `applyDecoratorMigrations<Stack>(DbClient&, tableName)` walks the stack, runs PREREQ_SQL, runs RESHAPE_STEPS gated on detect probes, records applied steps in `oatpp_authkit_schema_migrations` - Tests cover: fresh-table apply, re-apply (no-op), partial-state recovery - README "Decorator migrations" section per decorator ### Decision needed Check one (edit this comment): - [x] **Accept extended scope as written.** Bundles idempotent migrations with each decorator; closes #459 by reuse. - [ ] **Extend, but defer reshape migrations.** Keep v1 = static PREREQ_SQL only; reshape migrations get their own follow-up issue once a second reshape case appears. - [ ] **Revert to v1.** The reshape complexity outweighs the bundling benefit for now.
Author
Owner

Re-evaluated #12 — extended scope to bundle idempotent migrations (PREREQ_SQL + ReshapeStep) per decorator; effort revised Medium → Large; awaiting decision.

Re-evaluated #12 — extended scope to bundle idempotent migrations (PREREQ_SQL + ReshapeStep) per decorator; effort revised Medium → Large; awaiting decision.
Author
Owner

Re-evaluation

Owner selected Accept extended scope as written — bundles idempotent migrations with each decorator; closes #459 by reuse.

The plan stands as drafted in the previous re-evaluation:

  1. Prereq.hpp defines DecoratorPrereq (with PREREQ_SQL + {table} placeholder) and ReshapeStep ({name, detectSql, applySql})
  2. Each decorator exposes both as appropriate — TemporalRepository ships RESHAPE_STEPS for composite unique + valid_from/valid_until columns; AuditLogRepository ships PREREQ_SQL for the audit_log table; ScopeGuard ships nothing
  3. applyDecoratorMigrations<Stack>(DbClient&, tableName) walks the stack at startup, runs PREREQ_SQL (idempotent via IF NOT EXISTS), runs RESHAPE_STEPS gated on detectSql probes, records applied steps in oatpp_authkit_schema_migrations(decorator, table, step, applied_at) for observability
  4. Tests cover: fresh-table apply, re-apply (no-op), partial-state recovery
  5. README adds "Decorator migrations" section per decorator

Effort: Large (~550–650 LOC including tests). #459 reduces to "wire applyDecoratorMigrations into fewo-webapp App.cpp at startup, drop the redundant temporal-schema SQL from sql/schema.sql" once this lands.

Awaiting accepted label to proceed.

## Re-evaluation Owner selected **Accept extended scope as written — bundles idempotent migrations with each decorator; closes #459 by reuse.** The plan stands as drafted in the previous re-evaluation: 1. `Prereq.hpp` defines `DecoratorPrereq` (with `PREREQ_SQL` + `{table}` placeholder) **and** `ReshapeStep` (`{name, detectSql, applySql}`) 2. Each decorator exposes both as appropriate — TemporalRepository ships RESHAPE_STEPS for composite unique + valid_from/valid_until columns; AuditLogRepository ships PREREQ_SQL for the audit_log table; ScopeGuard ships nothing 3. `applyDecoratorMigrations<Stack>(DbClient&, tableName)` walks the stack at startup, runs `PREREQ_SQL` (idempotent via `IF NOT EXISTS`), runs `RESHAPE_STEPS` gated on `detectSql` probes, records applied steps in `oatpp_authkit_schema_migrations(decorator, table, step, applied_at)` for observability 4. Tests cover: fresh-table apply, re-apply (no-op), partial-state recovery 5. README adds "Decorator migrations" section per decorator Effort: Large (~550–650 LOC including tests). #459 reduces to "wire `applyDecoratorMigrations` into fewo-webapp App.cpp at startup, drop the redundant temporal-schema SQL from `sql/schema.sql`" once this lands. Awaiting `accepted` label to proceed.
Author
Owner

Re-evaluated #12 — owner accepted extended scope (PREREQ_SQL + ReshapeStep + applyDecoratorMigrations runner); plan unchanged from prior re-eval; awaiting accept.

Re-evaluated #12 — owner accepted extended scope (PREREQ_SQL + ReshapeStep + applyDecoratorMigrations runner); plan unchanged from prior re-eval; awaiting accept.
u.schuster added the
accepted
label 2026-04-29 21:40:54 +02:00
Author
Owner

Implemented in commit b5e1ea1include/oatpp-authkit/repo/Prereq.hpp defines DecoratorPrereq + ReshapeStep + applyDecoratorMigrations<...>(table, probe, exec, recorder). TemporalRepository ships add_valid_from/add_valid_until/drop_unique_entity_id (consumer-overridable noop)/composite_unique reshape steps. AuditLogRepository ships the audit_log CREATE TABLE as PREREQ. ScopeGuardRepository ships empty PREREQ + zero-length RESHAPE_STEPS. New test test_decorator_migrations.cpp covers fresh apply, idempotent re-apply (no-op), partial-state recovery, variadic stack ordering, and ScopeGuard's clean no-op — all 10 ctest cases pass. Version bumped 0.6.1 → 0.7.0. README expanded with a 'Decorator migrations' section. fewo-webapp#459 reduces to wiring applyDecoratorMigrations into App.cpp + dropping the redundant SQL from sql/schema.sql.

Implemented in commit b5e1ea1 — `include/oatpp-authkit/repo/Prereq.hpp` defines `DecoratorPrereq` + `ReshapeStep` + `applyDecoratorMigrations<...>(table, probe, exec, recorder)`. TemporalRepository ships add_valid_from/add_valid_until/drop_unique_entity_id (consumer-overridable noop)/composite_unique reshape steps. AuditLogRepository ships the audit_log CREATE TABLE as PREREQ. ScopeGuardRepository ships empty PREREQ + zero-length RESHAPE_STEPS. New test test_decorator_migrations.cpp covers fresh apply, idempotent re-apply (no-op), partial-state recovery, variadic stack ordering, and ScopeGuard's clean no-op — all 10 ctest cases pass. Version bumped 0.6.1 → 0.7.0. README expanded with a 'Decorator migrations' section. fewo-webapp#459 reduces to wiring `applyDecoratorMigrations` into App.cpp + dropping the redundant SQL from sql/schema.sql.
Author
Owner

Implemented #12 — per-decorator migration kit (Prereq.hpp + applyDecoratorMigrations runner) shipped in commit b5e1ea1, tagged v0.7.0; all 10 tests pass.

Implemented #12 — per-decorator migration kit (Prereq.hpp + applyDecoratorMigrations runner) shipped in commit b5e1ea1, tagged v0.7.0; all 10 tests pass.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: uwe.admin/oatpp-authkit#12
No description provided.