Skip to content
Agent Delivery Studio

MP-701 · Back to the workflow

Plan · Raw Materials - Multisourcing - Ticket 1 : dedicated tab and group management

Objective

Three new tables (A1): multisourcing_group with code, name, comment, active and audit columns and a unique index on LOWER(code), so a duplicate code is refused in any letter case across active and inactive groups; multisourcing_group_member as a junction with UNIQUE (rawmaterial_id) - the house way to say at most one group per raw material - and with no order column; and multisourcing_group_composition holding the group's identity as one (substance_id, percentage NUMERIC(10,6)) row per substance, written once at creation from the founding material's active composition so the identity survives the group losing every member. The legacy rawmaterial_multisourcing table is dropped in the same V035 migration. Reused unchanged: rawmaterial, the three composition tables, rawmaterial_manufacturer, the AuditableEntity audit columns and the junction pattern. Left out: a column on rawmaterial, a fingerprint hash column, any ordering column, the Flyway file and the entities (Backend stage), and every rule the service owns.

Current state

Ready for review

Ready for your review in Jira (DB Review) since 3 days.

Next action

A person: Ready for review — non-blocking recommendations are available.

Accepted route

Database stage · approach A1 · next: Database designer
accepted

Scope and acceptance criteria

What changes for the product and how we know it is done.

User interface

  • None at this stage: no file under frontend/ is touched, and the delivered mock is treated as the contract the schema must serve rather than as something to revise
  • The schema keeps every field the mock exposes reachable without a UI change at the flip: code, name, comment, active, member count, and a page of all groups ordered by code ignoring case
  • Two mock-only constructs disappear at the flip, not here: the in-browser store and the eligibility arguments the mock api takes; the swap markers already enumerate those caller edits
  • One known frontend question stays open and is not schema work: whether an inactive group's identity card should show its status (raised on the ticket on 2026-10-11, awaiting the product owner); active is stored either way

Database

  • Three new tables (multisourcing_group, multisourcing_group_member, multisourcing_group_composition) and one table dropped (rawmaterial_multisourcing); no existing table is altered, so no populated-table compatibility step is needed
  • One new design document on the ticket branch: docs/superpowers/specs/2026-10-14-mp-701-db-schema.md; no SQL and no Java is written at this stage
  • The Backend stage inherits a ready-to-author migration: V035__Create_Multisourcing_Group_Tables.sql, which also drops the legacy table, plus three entities whose fields must match it column for column (ddl-auto is validate)
  • Existing readers are untouched: rawmaterial, rawmaterial_composition, ingredient_composition, allergen_composition, rawmaterial_manufacturer and every pricing table keep their shape, which is why leaving a group cannot affect pricing data (AC4)
  • The only reader removed is the never-mounted legacy stack (entity, DTOs, mapper, repository, service, controller), swept with its table at the Backend stage

API and backend

  • None at this stage: nothing under backend/src is written or changed, and no migration file is added
  • The Backend stage inherits the canonical migration to author, V035__Create_Multisourcing_Group_Tables.sql, with the three CREATE TABLEs, the three named indexes and the DROP of rawmaterial_multisourcing
  • It inherits three entities that must match that migration column for column because ddl-auto is validate: MultisourcingGroup, MultisourcingGroupMember, MultisourcingGroupComposition, each extending AuditableEntity with a UUID primary key
  • It inherits the rules the database cannot hold: the compatibility decision over the identity rows, the manufacturer and composition preconditions, the deactivation of an emptied group, and the field-level duplicate-code refusal that the unique index backs
  • It also owns the backend half of the dead stack - the legacy entity, its two request DTOs, its mapper, repository, service and controller - swept with its table in the same change
  • The endpoint shapes are already fixed by the approved mock's swap markers (a paged group list sorted by code ignoring case, and an identity update carrying code, name and comment); this stage adds no new endpoint requirement

Acceptance criteria

  • The document covers the three tables with every column, constraint and index and the named query behind each, the mapping to the approved mock, the V035 strategy with the legacy drop and the AC9 sentence, the entity mapping and the open items; the DB critics are clean and the ticket is at DB Review.
  • id: RC1 · check: The design document specifies all three tables with every column inline - multisourcing_group, multisourcing_group_member, multisourcing_group_composition - and no existing table is altered.
  • id: RC2 · check: Group Code: VARCHAR(50) NOT NULL with the length justified as a business rule, a CHECK that it is trimmed and non-empty, and a unique index on LOWER(code) that holds across active and inactive groups - so reusing an emptied group's code in a different letter case is refused by the database, not only by the service.
  • id: RC3 · check: Membership is a junction row with UNIQUE (rawmaterial_id), which is how at most one group per raw material is guaranteed, and it carries no order, priority or rank column of any kind.
  • id: RC4 · check: The group's composition identity is stored as one (substance_id, percentage) row per substance with percentage NUMERIC(10,6) NOT NULL, unique per (group_id, substance_id), and the document says the comparison is on substance_id and never on any substance name.
  • id: RC5 · check: The writer of the identity rows is named exactly once (group creation, same transaction, from the founding material's ACTIVE composition), nothing recomputes them, and the document states that a later composition change of a member is the ticket's excluded interception.
  • id: RC6 · check: active BOOLEAN NOT NULL DEFAULT TRUE is declared inside the CREATE TABLE, and the document states that the last member leaving sets it FALSE while the row and its identity rows survive, and that nothing deletes a group.
  • id: RC7 · check: name VARCHAR(150) and comment VARCHAR(500) are nullable, and the document says a cleared value is stored as NULL.
  • id: RC8 · check: Every FK states an explicit ON DELETE with a reason (group RESTRICT because a group is never deleted; rawmaterial CASCADE; substance RESTRICT), and every constraint and index is named in the house form.
  • id: RC9 · check: Each index traces to a query the document names - the duplicate-code refusal and the catalog's case-insensitive order, the member count and last-member check, the candidate match by (substance, percentage) - and speculative indexes sit in a 'consider later' list.
  • id: RC10 · check: The migration strategy names V035__Create_Multisourcing_Group_Tables.sql as the next free version (V034 is the highest at the base, main added none), states that no backfill or populated-table step is needed because the tables are created empty, keeps names unqualified because Flyway runs once per tenant schema, and records that a migration landing on main first would move the nu…
  • id: RC11 · check: The legacy sweep is specified: DROP TABLE rawmaterial_multisourcing in the same migration, nothing references it, and the document states that the DROP statement is the single allowed occurrence of that identifier so the AC9 gate reads as 'no live code references it'.
  • id: RC12 · check: The document maps every field of the approved mock's catalog entry and identity request to a column (code, name, comment, active, member count, the identity entries) and shows that nothing in the mock requires a column the schema lacks - and nothing maps to an order.
  • id: RC13 · check: The entity mapping is given field by field for the three entities (AuditableEntity, UUID generation, the auditing listener, FK names matching the migration), since ddl-auto is validate and the two must land together; no entity, field or table name contains an AC9 identifier.
  • id: RC14 · check: The document states which rules stay in the service and why: the manufacturer precondition, the no-active-composition and blank-percentage refusals, the compatibility decision, the deactivation of an emptied group, and the field-level duplicate-code message.

Assumptions and constraints

  • This stage produces one design document under docs/superpowers/specs/ on the ticket branch; it writes no SQL file and no Java
  • Illustrative SQL only: the canonical Flyway migration is authored at the Backend stage, as V035 (V034 is the highest at the base, nothing landed on main)
  • Nothing under frontend/ changes: the approved mock is the data contract the schema must serve, not something to revise
  • No ordering, priority or rank column anywhere - the ticket forbids recording it in this ticket
  • No new reader is broken: the three tables are new and the only existing object removed is the never-mounted legacy stack
  • Group Code: mandatory, at most 50 characters, no imposed format, unique case- and whitespace-insensitively across active and inactive groups
  • Name and Comment are nullable so that a cleared value is stored as NULL (AC-E3)
  • Percentages are NUMERIC(10,6), the scale the composition tables and the mock both use
  • Names must contain none of the AC9 identifiers; the only permitted occurrence of rawmaterial_multisourcing is the statement that drops it
  • Every FK states ON DELETE, every index names its query, and the service keeps the contextual rules (manufacturer, composition, deactivation)

Risks, dependencies and expected result

  • R1
    The migration that drops rawmaterial_multisourcing necessarily spells an AC9 identifier, so the Backend stage's own sweep can read as a gate failure.

    Mitigation: The design document states that AC9 means no live code references the identifier and that the removing DROP statement is its single allowed occurrence; RC11 checks that sentence exists.

  • R2
    The stored group identity disagrees with a member whose INCI composition is edited after the group was formed, leaving a group whose members are no longer interchangeable.

    Mitigation: The ticket excludes interception of a composition change, so the design names the single writer (creation) and records the hole explicitly as an open item for DB Review rather than implying a refresh that nothing performs.

  • R3
    V035 may be taken by another ticket landing a migration on main before this ticket's Backend stage runs, which Flyway would only surface later.

    Mitigation: Main carries no migration since the index (E20) and V034 is the highest at the base (E9); the design records the version as next free at design time and asks the Backend stage to re-check it.

  • R4
    Percentages compared at a different scale or rounding than the mock's would make two identical compositions look different and empty the candidate list.

    Mitigation: percentage is NUMERIC(10,6), the exact type the composition tables use and the scale the mock rounds to (E4, E5, E15), and the design states the comparison is on that typed value.

  • R5
    Flyway runs once per tenant schema, so the legacy drop and the three creations run per tenant and a tenant whose schema has drifted could fail the migration.

    Mitigation: Names stay unqualified as V034 prescribes (E9), the three tables are new and empty, and the design notes the drop as the one statement touching an existing object.

  • R6
    A reviewer could read the identity table as a second home for composition data, or ask for a member order now that a member table exists.

    Mitigation: The design states that the rows are the group's declared identity, not a copy of any material's composition, and that no ordering column exists because the ticket gives a group members, not a ranking (MP-703 owns the priority).

Expected result

Database designer takes over the Database stage from this plan.

Milestones

The ordered steps and how each one is known to be done. File-level detail is under Technical details.

  1. 1The Multisourcing group schema design document

    Database designer

    Done when: The document covers the three tables with every column, constraint and index and the named query behind each, the mapping to the approved mock, the V035 strategy with the legacy drop and the AC9 sentence, the entity mapping and the open items; the DB critics are clean and the ticket is at DB Review.

Technical details

For developers and platform engineers: expand a section to see the exact technical content.

Decision, design and alternatives4
Selected
A1
Architecture
Target: three new tables in the cosmoreg-core schema plus the removal of the legacy stack's table. Illustrative DDL only - this stage produces a design document; the canonical Flyway file is authored at the Backend stage as V035__Create_Multisourcing_Group_Tables.sql (V034 is the highest version at the base and main added none, E9, E20), and entity and migration land together because ddl-auto is validate (E10). 1. multisourcing_group - the aggregate. id UUID PRIMARY KEY DEFAULT gen_random_uuid(); code VARCHAR(50) NOT NULL - the length is a named business rule (AC-E2: any text up to 50 characters), which is why it is not TEXT (E10); name VARCHAR(150), comment VARCHAR(500) - both nullable: AC-E3's blank values are stored as NULL, which is what the mock's write path does (E19); active BOOLEAN NOT NULL DEFAULT TRUE - declared inside the CREATE TABLE per the convention (E11); set FALSE when the last member leaves, and the row is never deleted (AC8); audit columns created_date/modified_date TIMESTAMPTZ and created_by/modified_by VARCHAR(100), nullable, supplied by AuditableEntity and SystemAwareAuditingEntityListener (E13); CONSTRAINT chk_multisourcing_group_code_trimmed CHECK (code = BTRIM(code) AND code <> '') - makes the whitespace-insensitivity of the uniqueness rule a property of the data rather than of one service method, and keeps the mandatory code from being blank; CREATE UNIQUE INDEX uq_multisourcing_group_code_lower ON multisourcing_group (LOWER(code)) - the AC-E2 and AC6 refusal, case-insensitive across active and inactive rows, and it also serves the catalog's ORDER BY LOWER(code) (the mock's sort=code,asc,ignorecase, E14). No second plain UNIQUE (code): the functional index subsumes it. An index on active is deliberately left to 'consider later' - the catalog does not filter on it and the candidate query is already narrowed by the identity rows. 2. multisourcing_group_member - membership, one row per member. id UUID PRIMARY KEY DEFAULT gen_random_uuid(); group_id UUID NOT NULL, CONSTRAINT fk_multisourcing_group_member_group FOREIGN KEY (group_id) REFERENCES multisourcing_group (id) ON DELETE RESTRICT - a group is never deleted, so a cascade would describe something that must not happen; rawmaterial_id UUID NOT NULL, CONSTRAINT fk_multisourcing_group_member_rawmaterial FOREIGN KEY (rawmaterial_id) REFERENCES rawmaterial (id) ON DELETE CASCADE - a deleted raw material has no membership; audit columns as above; CONSTRAINT uk_multisourcing_group_member_rawmaterial UNIQUE (rawmaterial_id) - this is the 'at most one group per raw material' rule, enforced by the database exactly as rawmaterial_manufacturer enforces its own (E7), and it serves the lookup 'which group does this material belong to'; CREATE INDEX idx_multisourcing_group_member_group ON multisourcing_group_member (group_id) - serves the member count shown on the identity card and in the catalog, and the 'did the last member just leave' check of AC8; No order, priority or rank column: the ticket states this ticket gives a group members, not a ranking, and that nothing here should record it; the sourcing priority arrives with MP-703. 3. multisourcing_group_composition - the group's identity, one row per substance. id UUID PRIMARY KEY DEFAULT gen_random_uuid(); group_id UUID NOT NULL, CONSTRAINT fk_multisourcing_group_composition_group FOREIGN KEY (group_id) REFERENCES multisourcing_group (id) ON DELETE CASCADE; substance_id UUID NOT NULL, CONSTRAINT fk_multisourcing_group_composition_substance FOREIGN KEY (substance_id) REFERENCES substance (id) ON DELETE RESTRICT; percentage NUMERIC(10,6) NOT NULL - the same type and scale as ingredient_composition.percentage_used and allergen_composition.percentage_used (E4, E5) and as the mock's 6-decimal rounding (E15), so two equal compositions always compare equal; NOT NULL is what makes the blank-percentage refusal structural (E6); audit columns as above; CONSTRAINT uk_multisourcing_group_composition_substance UNIQUE (group_id, substance_id) - one row per substance, which is sound because a substance cannot sit in both the ingredient and the allergen list of one composition (stated on the ticket, s17) and the rule merges the two lists (E15); CONSTRAINT chk_multisourcing_group_composition_percentage CHECK (percentage >= 0); CREATE INDEX idx_multisourcing_group_composition_substance ON multisourcing_group_composition (substance_id, percentage) - leading column first, serving the candidate query's lookup by (substance, percentage) pair; Writer, named once: the service inserts these rows in the same transaction as the group, from the founding material's ACTIVE composition (E3). Nothing else writes them and nothing recomputes them; interception of a later composition change is the ticket's excluded scope, and the design document says so where the derivable-data rule of backend/CLAUDE.md asks (E10). Named queries the indexes exist for. Q1 which group does this raw material belong to - multisourcing_group_member by rawmaterial_id (uk_...). Q2 the All groups catalog page - multisourcing_group ORDER BY LOWER(code) with limit and offset plus the total, and the member count per group from multisourcing_group_member (idx_..._group); the search narrows code and name (ILIKE); a trigram index is 'consider later', not core. Q3 join candidates (AC4) - the joiner's N identity pairs are matched against multisourcing_group_composition: the groups whose rows all match and whose own row count is also N, intersected with active = TRUE and with the absence of a member row for this material. The (substance_id, percentage) index serves the match, uk_..._substance guarantees the counts are comparable, and the equality is on the substance id, never on a name (E15). Q4 duplicate code (AC6, AC-E2) - uq_multisourcing_group_code_lower, plus a service pre-check so the user gets the field-level DUPLICATE_GROUP_CODE message rather than a raw constraint error (E14, and the invariant-placement table of E10). Q5 the last member left (AC8) - count members by group_id, then set active = FALSE; the row stays. Entities the Backend stage must land with the migration (ddl-auto validate, E10): MultisourcingGroup, MultisourcingGroupMember and MultisourcingGroupComposition, each extending AuditableEntity with @GeneratedValue(GenerationType.UUID) and SystemAwareAuditingEntityListener, FK joins named exactly as the migration names them (E13). None of the new names contains any AC9 identifier. Legacy sweep. DROP TABLE rawmaterial_multisourcing (no other table references it, E1) together with its entity, DTOs, mapper, repository, service and controller, in the same V035 migration and the same Backend-stage change (ticket s6; inherited Mock decision D7). The design document must state the one awkward consequence: that DROP statement necessarily spells the identifier AC9 greps for, so the gate has to read AC9 as 'no live code references it' with the removing migration as its single allowed occurrence. What the schema deliberately does not express, because the service owns it (E10's invariant table): the no-manufacturer refusal (read from rawmaterial_manufacturer, E7), the no-active-composition and blank-percentage refusals (read from rawmaterial_composition and its entry tables, E3, E6), the compatibility decision itself, and the choice to deactivate a group when its last member leaves. Multi-tenancy: names stay unqualified and no tenant column is added; Flyway runs the migration once per tenant schema, and the legacy drop therefore runs per tenant too (E9).
Rationale
The evidence decides three things. First, nothing to reuse exists: the catalog's only multisourcing table is a per-material code plus priority order (E1) and the only other group table is a substance classification with neither code nor comment (E2), so group storage is net-new - which is also what the inherited Mock contract's db-plan concluded. Second, membership is a solved shape in this repository: rawmaterial_manufacturer expresses at most one per raw material as a junction with UNIQUE (rawmaterial_id), named FKs and an index (E7), mapped by an AuditableEntity with a UUID PK (E13), and rawmaterial_supplier and rawmaterial_internal_status repeat it; a nullable column on rawmaterial would be the one deviation from that pattern on the domain's busiest table, so A1 keeps the junction. Third, the group's identity must outlive its members: the ticket states that a group's identity is the composition its members share and that a group losing its last member becomes inactive and is never deleted (s5, restated on the branch in E21), which rules out A2, and the mock already stores that identity on the group record rather than deriving it (E16). Between stored rows (A1) and a stored fingerprint (A3) the rows win on auditability and exactness: the comparison the rule names is substance-by-substance at a 6-decimal scale (E15), which NUMERIC(10,6) columns express exactly as the composition tables already do (E4, E5), while a hash hides what a group is and loses the substance FK; backend/CLAUDE.md's derivable-column rule is satisfied by naming the single writer (creation) rather than by refusing to store it (E10). A4 is refused on grain and on AC9 (E1, E12). Group Code uniqueness is case-insensitive across active and inactive groups by AC-E2 and by the product owner's own test (reusing an emptied group's code in lower case must still be refused), which a unique index on LOWER(code) guarantees for every writer, not only for the service; functional and partial unique indexes are already house style (E9). No ordering column appears anywhere, because the ticket says this ticket gives a group members, not a ranking.

Design by area

User interface

Decided at the Mock stage and already delivered on this branch; no frontend change belongs to the DB stage. What this stage fixes for the frontend is only that nothing in the schema contradicts the approved shapes: name and comment are nullable so a cleared value round-trips as nothing, the code is unique case-insensitively across active and inactive groups so the existing field-level refusal keeps its meaning, the catalog's order is served by the LOWER(code) index so the paged read needs no client-side sort, and the member count is a COUNT rather than a stored number so it cannot disagree with the member list MP-703 will show.

Database

The design document the db-designer writes must cover, in this order, and may not leave a row of it unstated.

1. Scope and inputs. The approved mock on this branch is the data contract (code, name, comment, active, member count, paged catalog over all groups: E14, E17, E18, E19) and the ticket's five rules are the invariants (E21). State explicitly that this document specifies the schema only and that the Flyway file is authored at the Backend stage.

2. The three tables, every column inline, exactly as the architecture section specifies: multisourcing_group (id UUID PK DEFAULT gen_random_uuid(); code VARCHAR(50) NOT NULL; name VARCHAR(150); comment VARCHAR(500); active BOOLEAN NOT NULL DEFAULT TRUE; the four audit columns, TIMESTAMPTZ and VARCHAR(100), nullable; CHECK (code = BTRIM(code) AND code <> '')), multisourcing_group_member (id; group_id NOT NULL FK ON DELETE RESTRICT; rawmaterial_id NOT NULL FK ON DELETE CASCADE; audit; UNIQUE (rawmaterial_id)) and multisourcing_group_composition (id; group_id NOT NULL FK ON DELETE CASCADE; substance_id NOT NULL FK ON DELETE RESTRICT; percentage NUMERIC(10,6) NOT NULL; audit; UNIQUE (group_id, substance_id); CHECK (percentage >= 0)). Each FK is named fk_<table>_<target> and states its ON DELETE; each unique constraint is named uk_<table>_<columns>.

3. Indexes, each with the query it serves: uq_multisourcing_group_code_lower ON multisourcing_group (LOWER(code)) for the duplicate-code refusal across active and inactive groups and for the catalog's case-insensitive order by code; idx_multisourcing_group_member_group ON (group_id) for the member count and the last-member check; idx_multisourcing_group_composition_substance ON (substance_id, percentage) for the candidate match. Anything else - an index on active, a trigram index for the catalog search - goes in a 'consider later' list with the reason it is not needed yet.

4. Mapping to the mock, field by field. A table that maps each field of the mock's catalog entry and identity request to its column: code -> multisourcing_group.code, name -> name (NULL when cleared), comment -> comment (NULL when cleared), active -> active, memberCount -> COUNT over multisourcing_group_member, the group's fingerprint entries -> multisourcing_group_composition rows (substanceId -> substance_id, percentage -> percentage NUMERIC(10,6)), and the membership itself -> one multisourcing_group_member row. State that nothing maps to an order or priority, because the ticket records none.

5. The identity rows' writer, once, in prose: the service inserts them with the group, from the founding material's ACTIVE composition (ingredient_composition and allergen_composition merged, one row per substance, every percentage present); nothing recomputes them; a later composition change of a member is the ticket's excluded interception and therefore a stated, accepted hole rather than an oversight.

6. The compatibility query, written out as illustrative SQL (the set-equality of Q3 in the architecture section), with a sentence on why the equality is on substance_id and never on a name.

7. Migration strategy. V035__Create_Multisourcing_Group_Tables.sql at the Backend stage: the three CREATE TABLEs, the three indexes, then DROP TABLE rawmaterial_multisourcing. V034 is the highest version at the base and main added no migration, so V035 is free; record that a migration landing on main before the Backend stage would move the number. Names stay unqualified because Flyway runs once per tenant schema, and note that the drop therefore runs per tenant. No backfill, no NOT NULL on existing rows, no new UNIQUE over existing data - the three tables are empty at creation; say so, since that is what makes the compatibility section short.

8. Entity mapping for the Backend stage: MultisourcingGroup, MultisourcingGroupMember, MultisourcingGroupComposition, each extending AuditableEntity with @GeneratedValue(GenerationType.UUID), @EntityListeners(SystemAwareAuditingEntityListener.class) and FK names identical to the migration's, following RawmaterialManufacturer. None of the three names, nor any field, may contain an AC9 identifier.

9. What the database does not enforce, and who does: the manufacturer precondition (rawmaterial_manufacturer), the active-composition and blank-percentage preconditions, the compatibility decision, the deactivation of an emptied group, and the field-level duplicate-code message - all service, per the invariant-placement table of backend/CLAUDE.md.

10. AC9 and the drop: the DROP statement necessarily names rawmaterial_multisourcing, so the document states that the identifier gate means no live code references it and that the removing migration is its one allowed occurrence. Without this sentence the Backend stage's own sweep looks like a gate failure.

11. Open items for DB Review, listed rather than guessed: whether the identity rows should be reconstructed when MP-703 or a later ticket intercepts a composition change, and whether the catalog search will need a trigram index once the catalog grows.

API and backend

Decided at the Backend stage. What this stage fixes for it: the three tables and their constraints, the names of every table, column, FK, unique constraint and index, the NUMERIC(10,6) scale the compatibility comparison must use, the set-equality query shape for the join-candidate list, the fact that the member count is derived by COUNT and never stored, the single writer of the identity rows (group creation, in the same transaction, from the founding material's ACTIVE composition), and the sentence the AC9 gate needs about the DROP statement. Nothing about controllers, DTOs, mappers or services is decided here beyond where an invariant lives.

Alternatives considered

  • A1 — Three new tables: multisourcing_group (code, name, comment, active, audit), multisourcing_group_member as a junction with UNIQUE (rawmaterial_id), and multisourcing_group_composition holding the group's own identity as one (substance_id, percentage) row per substance, written once at creation from the founding material's active composition. The legacy rawmaterial_multisourcing table is dropped in the same migration. Compatibility is a set-equality query over the identity rows; code uniqueness is a unique index on LOWER(code).

    Pros: Mirrors the ticket's own model: the group's identity is a composition, and it survives the group losing every member; Set equality over typed (substance_id, percentage NUMERIC(10,6)) rows is exact and indexable, with no opaque hash and no scale ambiguity; FK on substance keeps the identity referentially sound and auditable: one can always read what a group's composition is; Membership follows the house junction pattern (E7, E13), so no core table changes and MP-703 can hang member data off it later; No column records any ordering, which the ticket forbids. Cons: Three new tables rather than two, and the identity rows are derived data whose writer must be named (one write at creation, never recomputed); A member's INCI composition changing later makes the stored identity disagree with that member - the interception the ticket excludes.

  • A2 — Two new tables only (group plus member junction); the group has no stored identity and compatibility is derived per request by joining every member's ACTIVE composition and comparing the substance/percentage sets.

    Pros: No derived data at all, so nothing can go stale; Smallest schema. Cons: A group with zero members has no identity, contradicting the ticket's rule that a group survives losing all its members and keeps the composition that defines it; The candidate query must unfold every member's composition on every open of the assign dialog, with no index that can serve the equality; The founding composition is unrecoverable once the last member leaves, so nothing records what the group ever was.

  • A3 — Two new tables, with the group carrying a single denormalised fingerprint column (a canonical string or hash of the substance/percentage pairs) exactly as the mock store does, matched by equality.

    Pros: Cheapest candidate lookup: one equality on one indexed column; Closest to the mock's own record shape (E16). Cons: An opaque value: no one can read which substances define a group, and the backend/CLAUDE.md derivable-column smell applies with no reader able to verify it; The canonicalisation (ordering, trimming, the 6-decimal scale) becomes an undocumented contract that two independent writers could implement differently; Loses the substance FK, so a deleted or merged substance leaves a fingerprint nobody can resolve.

  • A4 — Repurpose the legacy rawmaterial_multisourcing table by ALTER: rename or reuse its code column as the group code and add the group columns, instead of creating a group table.

    Pros: No new table for the group; Reuses a table that already references rawmaterial. Cons: Its row is one sourcing option of one raw material with a priority order (E1, E12); a group is a row shared by several materials, so the grain is wrong; AC9 requires the identifier rawmaterial_multisourcing to be gone from source, which keeping the table contradicts; Its unique key (raw_material_id, multisourcing_order) and NOT NULL order column would have to be dropped, and the ticket forbids recording an order at all.

API0

API contract

No API contract in this plan.

Backend design

API and backend

Decided at the Backend stage. What this stage fixes for it: the three tables and their constraints, the names of every table, column, FK, unique constraint and index, the NUMERIC(10,6) scale the compatibility comparison must use, the set-equality query shape for the join-candidate list, the fact that the member count is derived by COUNT and never stored, the single writer of the identity rows (group creation, in the same transaction, from the founding material's ACTIVE composition), and the sentence the AC9 gate needs about the DROP statement. Nothing about controllers, DTOs, mappers or services is decided here beyond where an invariant lives.

Backend changes

No file changes listed.

Data and database2

Data contract

Database

multisourcing_group(id UUID PK, code VARCHAR(50) NOT NULL, name VARCHAR(150) NULL, comment VARCHAR(500) NULL, active BOOLEAN NOT NULL DEFAULT TRUE, created_date/modified_date TIMESTAMPTZ NULL, created_by/modified_by VARCHAR(100) NULL); unique on LOWER(code) across active and inactive rows; code trimmed and non-empty by CHECK. multisourcing_group_member(id UUID PK, group_id UUID NOT NULL -> multisourcing_group ON DELETE RESTRICT, rawmaterial_id UUID NOT NULL -> rawmaterial ON DELETE CASCADE, audit) with UNIQUE (rawmaterial_id) and no order column. multisourcing_group_composition(id UUID PK, group_id UUID NOT NULL -> multisourcing_group ON DELETE CASCADE, substance_id UUID NOT NULL -> substance ON DELETE RESTRICT, percentage NUMERIC(10,6) NOT NULL, audit) with UNIQUE (group_id, substance_id). Member count is derived by COUNT, never stored. Dropped: rawmaterial_multisourcing.

Database design

Database

The design document the db-designer writes must cover, in this order, and may not leave a row of it unstated.

1. Scope and inputs. The approved mock on this branch is the data contract (code, name, comment, active, member count, paged catalog over all groups: E14, E17, E18, E19) and the ticket's five rules are the invariants (E21). State explicitly that this document specifies the schema only and that the Flyway file is authored at the Backend stage.

2. The three tables, every column inline, exactly as the architecture section specifies: multisourcing_group (id UUID PK DEFAULT gen_random_uuid(); code VARCHAR(50) NOT NULL; name VARCHAR(150); comment VARCHAR(500); active BOOLEAN NOT NULL DEFAULT TRUE; the four audit columns, TIMESTAMPTZ and VARCHAR(100), nullable; CHECK (code = BTRIM(code) AND code <> '')), multisourcing_group_member (id; group_id NOT NULL FK ON DELETE RESTRICT; rawmaterial_id NOT NULL FK ON DELETE CASCADE; audit; UNIQUE (rawmaterial_id)) and multisourcing_group_composition (id; group_id NOT NULL FK ON DELETE CASCADE; substance_id NOT NULL FK ON DELETE RESTRICT; percentage NUMERIC(10,6) NOT NULL; audit; UNIQUE (group_id, substance_id); CHECK (percentage >= 0)). Each FK is named fk_<table>_<target> and states its ON DELETE; each unique constraint is named uk_<table>_<columns>.

3. Indexes, each with the query it serves: uq_multisourcing_group_code_lower ON multisourcing_group (LOWER(code)) for the duplicate-code refusal across active and inactive groups and for the catalog's case-insensitive order by code; idx_multisourcing_group_member_group ON (group_id) for the member count and the last-member check; idx_multisourcing_group_composition_substance ON (substance_id, percentage) for the candidate match. Anything else - an index on active, a trigram index for the catalog search - goes in a 'consider later' list with the reason it is not needed yet.

4. Mapping to the mock, field by field. A table that maps each field of the mock's catalog entry and identity request to its column: code -> multisourcing_group.code, name -> name (NULL when cleared), comment -> comment (NULL when cleared), active -> active, memberCount -> COUNT over multisourcing_group_member, the group's fingerprint entries -> multisourcing_group_composition rows (substanceId -> substance_id, percentage -> percentage NUMERIC(10,6)), and the membership itself -> one multisourcing_group_member row. State that nothing maps to an order or priority, because the ticket records none.

5. The identity rows' writer, once, in prose: the service inserts them with the group, from the founding material's ACTIVE composition (ingredient_composition and allergen_composition merged, one row per substance, every percentage present); nothing recomputes them; a later composition change of a member is the ticket's excluded interception and therefore a stated, accepted hole rather than an oversight.

6. The compatibility query, written out as illustrative SQL (the set-equality of Q3 in the architecture section), with a sentence on why the equality is on substance_id and never on a name.

7. Migration strategy. V035__Create_Multisourcing_Group_Tables.sql at the Backend stage: the three CREATE TABLEs, the three indexes, then DROP TABLE rawmaterial_multisourcing. V034 is the highest version at the base and main added no migration, so V035 is free; record that a migration landing on main before the Backend stage would move the number. Names stay unqualified because Flyway runs once per tenant schema, and note that the drop therefore runs per tenant. No backfill, no NOT NULL on existing rows, no new UNIQUE over existing data - the three tables are empty at creation; say so, since that is what makes the compatibility section short.

8. Entity mapping for the Backend stage: MultisourcingGroup, MultisourcingGroupMember, MultisourcingGroupComposition, each extending AuditableEntity with @GeneratedValue(GenerationType.UUID), @EntityListeners(SystemAwareAuditingEntityListener.class) and FK names identical to the migration's, following RawmaterialManufacturer. None of the three names, nor any field, may contain an AC9 identifier.

9. What the database does not enforce, and who does: the manufacturer precondition (rawmaterial_manufacturer), the active-composition and blank-percentage preconditions, the compatibility decision, the deactivation of an emptied group, and the field-level duplicate-code message - all service, per the invariant-placement table of backend/CLAUDE.md.

10. AC9 and the drop: the DROP statement necessarily names rawmaterial_multisourcing, so the document states that the identifier gate means no live code references it and that the removing migration is its one allowed occurrence. Without this sentence the Backend stage's own sweep looks like a gate failure.

11. Open items for DB Review, listed rather than guessed: whether the identity rows should be reconstructed when MP-703 or a later ticket intercepts a composition change, and whether the catalog search will need a trigram index once the catalog grows.

Database changes

FileChangeWhy
docs/superpowers/specs/2026-10-14-mp-701-db-schema.mdcreateThe DB stage's only output: tables, columns, constraints, indexes, the mapping to the approved mock, the migration strategy for V035 including the legacy drop, the entity mapping, and the open items for DB Review
Files and components1
AreaFileChangeWhy
Databasedocs/superpowers/specs/2026-10-14-mp-701-db-schema.mdcreateThe DB stage's only output: tables, columns, constraints, indexes, the mapping to the approved mock, the migration strategy for V035 including the legacy drop, the entity mapping, and the open items for DB Review

Files named by the steps

  • docs/superpowers/specs/2026-10-14-mp-701-db-schema.md

Components the review expects

  • kind: create · path: docs/superpowers/specs/2026-10-14-mp-701-db-schema.md
Tests and review checks14

Review checks

  • id: RC1 · check: The design document specifies all three tables with every column inline - multisourcing_group, multisourcing_group_member, multisourcing_group_composition - and no existing table is altered.
  • id: RC2 · check: Group Code: VARCHAR(50) NOT NULL with the length justified as a business rule, a CHECK that it is trimmed and non-empty, and a unique index on LOWER(code) that holds across active and inactive groups - so reusing an emptied group's code in a different letter case is refused by the database, not only by the service.
  • id: RC3 · check: Membership is a junction row with UNIQUE (rawmaterial_id), which is how at most one group per raw material is guaranteed, and it carries no order, priority or rank column of any kind.
  • id: RC4 · check: The group's composition identity is stored as one (substance_id, percentage) row per substance with percentage NUMERIC(10,6) NOT NULL, unique per (group_id, substance_id), and the document says the comparison is on substance_id and never on any substance name.
  • id: RC5 · check: The writer of the identity rows is named exactly once (group creation, same transaction, from the founding material's ACTIVE composition), nothing recomputes them, and the document states that a later composition change of a member is the ticket's excluded interception.
  • id: RC6 · check: active BOOLEAN NOT NULL DEFAULT TRUE is declared inside the CREATE TABLE, and the document states that the last member leaving sets it FALSE while the row and its identity rows survive, and that nothing deletes a group.
  • id: RC7 · check: name VARCHAR(150) and comment VARCHAR(500) are nullable, and the document says a cleared value is stored as NULL.
  • id: RC8 · check: Every FK states an explicit ON DELETE with a reason (group RESTRICT because a group is never deleted; rawmaterial CASCADE; substance RESTRICT), and every constraint and index is named in the house form.
  • id: RC9 · check: Each index traces to a query the document names - the duplicate-code refusal and the catalog's case-insensitive order, the member count and last-member check, the candidate match by (substance, percentage) - and speculative indexes sit in a 'consider later' list.
  • id: RC10 · check: The migration strategy names V035__Create_Multisourcing_Group_Tables.sql as the next free version (V034 is the highest at the base, main added none), states that no backfill or populated-table step is needed because the tables are created empty, keeps names unqualified because Flyway runs once per tenant schema, and records that a migration landing on main first would move the nu…
  • id: RC11 · check: The legacy sweep is specified: DROP TABLE rawmaterial_multisourcing in the same migration, nothing references it, and the document states that the DROP statement is the single allowed occurrence of that identifier so the AC9 gate reads as 'no live code references it'.
  • id: RC12 · check: The document maps every field of the approved mock's catalog entry and identity request to a column (code, name, comment, active, member count, the identity entries) and shows that nothing in the mock requires a column the schema lacks - and nothing maps to an order.
  • id: RC13 · check: The entity mapping is given field by field for the three entities (AuditableEntity, UUID generation, the auditing listener, FK names matching the migration), since ddl-auto is validate and the two must land together; no entity, field or table name contains an AC9 identifier.
  • id: RC14 · check: The document states which rules stay in the service and why: the manufacturer precondition, the no-active-composition and blank-percentage refusals, the compatibility decision, the deactivation of an emptied group, and the field-level duplicate-code message.

Test files

No test files named.

Reuse to verify

  • area: db · element: multisourcing_group, multisourcing_group_member, multisourcing_group_composition · action: create · why: Must be justified by the absence of any table holding a cross-material equivalence group: the legacy multisourcing table is a per-material code plus order and the only other group table is a substance classification. A fourth table, or a second home for a group's identity, w…
  • area: db · element: rawmaterial_multisourcing table and its entity · action: remove · why: The dead stack's table is dropped in the same migration; it must not be repurposed, renamed or left standing, and the design must not re-introduce its priority order column.
  • area: db · element: rawmaterial_composition, ingredient_composition, allergen_composition, rawmaterial_manufacturer · action: reuse · why: The preconditions and the composition the identity rule reads come from these tables unchanged; a copy of composition data, or a stored 'has manufacturer' flag, would be duplication.
  • area: db · element: A nullable multisourcing_group_id column on rawmaterial · action: create · why: Rejected alternative A4/A1-variant: membership must not be a column on the core rawmaterial table; it is the junction with UNIQUE (rawmaterial_id), as the three sibling junction tables do.
  • area: db · element: A denormalised composition fingerprint or hash column on the group · action: create · why: Rejected alternative A3: the identity is stored as typed substance/percentage rows with a substance FK, not as an opaque string whose canonicalisation two writers could implement differently.
  • area: backend · element: Multisourcing group entities, repositories and endpoints · action: unknown · why: Decided at the Backend stage; this stage only fixes the columns, names and query shapes they must match.
  • area: frontend · element: The delivered mock data layer and types · action: reuse · why: The approved mock is the data contract; the schema must serve it without asking for a frontend change.
Handoff and bindings20

Database designer takes over the Database stage from this plan.

RAG revision
9a774150b6f4
Catalog
89259674c242
Base SHA (RAG indexed)
5e62a467fa51
Feature base
5e62a467fa51
Branch head
e07342b83f73
Main
deb0851f0ad7
Main overlay
46d9b1be6a66
Branch overlay
a5f58ff1fdf9
Branch
claude/mock-MP-701-multisourcing-groups
PR
#132
Freshness
OVERLAY
RAG mode / profile
required
Stage binding
bb18c1b76e13
Context manifest
b50556c08711
Policy binding
cf104e3246a2
Writes
disabled
TicketSnapshot schema
ticket-snapshot/v2
TicketSnapshot hash
6bfae314e9f7
FeatureContract schema
feature-contract/v2.1
FeatureContract hash
46f9da7f4aa4
Evidence and provenance21
Contract
feature-contract/v2.1 · 46f9da7f4aa42d4bb12146f5298602202edc05f166a85bb94155c6315e576ec4
Aggregate
46f9da7f4aa42d4bb12146f5298602202edc05f166a85bb94155c6315e576ec4
RAG
RAG · OVERLAY

Cited evidence

IdSourceWhereClaim
E1catalogbackend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql:696-705 @ 5e62a46Catalog db.table rawmaterial_multisourcing, read in the migration at the base: the legacy table holds one code plus a priority order per raw material and knows no group, so it cannot carry a multi-material equivalence group.
E2catalogbackend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql:939-946 @ 5e62a46Catalog db.table substance_allergen_group, read in the migration at the base: the only other table named group is a substance classification carrying name and active, with no code, comment or membership.
E3git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql:631-643 @ 5e62a46rawmaterial_composition is versioned per raw material with a status CHECK of DRAFT, ACTIVE and SUPERSEDED, so the active composition the identity rule reads is the ACTIVE row.
E4git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql:300-314 @ 5e62a46ingredient_composition stores one row per substance of a composition with percentage_used NUMERIC(10,6) and a substance_id, which is the pair the compatibility rule compares.
E5git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql:24-38 @ 5e62a46allergen_composition has the same shape as ingredient_composition (composition_id, substance_id, percentage_used NUMERIC(10,6)), so merging ingredients and allergens is a union of two identically shaped tables.
E6git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V004__Make_Percentage_Used_Optional.sql:1-11 @ 5e62a46percentage_used was made nullable in both composition tables, so a composition entry with no percentage exists in real data and the blank-percentage refusal cannot be assumed away.
E7git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V010__Add_Manufacturer_Currency_And_Site_Supplier.sql:9-32 @ 5e62a46rawmaterial_manufacturer is the house shape for at most one X per raw material: a junction table with UNIQUE (rawmaterial_id), named FKs to rawmaterial and company, audit columns and two indexes.
E8git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V028__Create_Formula_Status_And_Formula_Tables.sql:113-152 @ 5e62a46The newest table-creating migration declares every column inline with a UUID PK defaulting to gen_random_uuid(), named UNIQUE and FK constraints with explicit ON DELETE, a CHECK, indexes that each name their query, and a comment stating which rules stay in the service.
E9git-basebackend/applications/cosmoreg-core/src/main/resources/db/migration/V034__Add_Formula_And_Default_Flags_To_Internal_Status.sql:49-65 @ 5e62a46V034, the highest migration version at the base, records the re-run convention, a partial unique index and a CHECK, and states that Flyway runs each migration once per tenant schema with unqualified names.
E10git-basebackend/CLAUDE.md:497-536 @ 5e62a46Convention: TIMESTAMPTZ, NUMERIC for quantities, UUID primary keys, TEXT unless a length is a real business rule, every FK with an explicit ON DELETE, a CHECK on a nullable column guarded by IS NOT NULL, every index traceable to a named query, and the rules for migrating a populated table.
E11git-basebackend/CLAUDE.md:560-595 @ 5e62a46Convention: default to reference data over an enum, and declare active BOOLEAN NOT NULL DEFAULT true inside the CREATE TABLE rather than as a later ALTER.
E12git-basebackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/rawmaterial/RawMaterialMultiSourcing.java:32-70 @ 5e62a46The legacy entity still maps rawmaterial_multisourcing at the base, with a unique constraint on (raw_material_id, multisourcing_order) and a priority order field - the ranking this ticket must not record.
E13ragbackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/rawmaterial/RawmaterialManufacturer.java:40-80 @ 5e62a46The junction entity pattern: extends AuditableEntity, SystemAwareAuditingEntityListener, UUID PK with GenerationType.UUID, the unique constraint declared on the entity and FK joins named to match the migration.
E14branch-overlayfrontend/src/domains/raw-materials/api/multisourcing-groups.ts:1-40 @ e07342bBranch content: the approved mock states that the group resource's ids, bodies, returned shapes and rule codes are final, that the rules it enforces (including Group Code uniqueness) move server-side at the flip, and that the catalog's paginated shape is final.
E15branch-overlayfrontend/src/domains/raw-materials/types/multisourcing.ts:1-40 @ e07342bBranch content: a composition identity entry is a substance id plus a percentage rounded to the backend's 6-decimal BigDecimal scale, ingredients and allergens merged one row per substance, and order carries no meaning.
E16branch-overlayfrontend/src/domains/raw-materials/lib/multisourcing-mock-data.ts:1-40 @ e07342bBranch content: a stored group record carries the composition fingerprint that defines the group's identity and the member list the count is derived from, both of which the comment says the server owns in the real system.
E17branch-overlayfrontend/src/domains/raw-materials/hooks/multisourcing/use-all-groups.ts:1-40 @ e07342bBranch content: the catalog is every group the system holds, active and inactive, with members or none, read one server-side page at a time with the totals, and it applies no eligibility filter.
E18branch-overlayfrontend/src/domains/raw-materials/features/multisourcing/components/multisourcing-group-card.tsx:1-40 @ e07342bBranch content: a group's identity is code (mandatory, no imposed format), name and comment (both clearable to nothing) plus a member count rather than a member list, so nothing in the card implies an order.
E19branch-overlayfrontend/src/domains/raw-materials/hooks/multisourcing/use-group-identity.ts:1-40 @ e07342bBranch content: one write path carries code, name and comment, and the two rules it names are Group Code uniqueness across active and inactive groups and blank clears the field.
E20main-overlay.github/workflows/frontend-ci-cd.yml:282-289 @ deb0851Main content since the index: the only commit added a sparse-checkout guard to three CI workflows, so no Flyway migration landed on main and the next free version is not contested there.
E21branch-overlaydocs/superpowers/specs/2026-10-02-mp-701-multisourcing-groups-design.md:1-40 @ e07342bBranch content: the approved design doc names the later stages (DB design doc, backend, flip) and restates the five constraints - at most one group per material, identity by same substances at same percentages compared on the substance id, no active composition disables the section, no manufacturer refuses join and create, and a group losing its last member becomes inactive and is never deleted.

Reuse and anti-duplication

AreaElementActionJustification
dbmultisourcing_group tablecreateCatalog db.table queried for 'multisourcing' and for 'group': the only hits are the legacy per-material code-plus-order table (E1) and substance_allergen_group with its assignment table, a substance classification carrying name and active and neither code nor comment (E2). No table holds a code, a name, a comment and an active flag for a group shared by several raw materials, so extension has noth…
dbmultisourcing_group_member junction tablecreateThe legacy table's grain is one sourcing option of one material with a mandatory priority order and a unique key on (raw_material_id, multisourcing_order) (E1, E12), so it cannot carry membership of a shared group, and AC9 removes its name anyway. The new junction copies the shape rawmaterial_manufacturer already uses for at most one per raw material (E7).
dbmultisourcing_group_composition identity tablecreateingredient_composition and allergen_composition hold a raw material's own composition (E4, E5) and cannot express the identity of a group, which must outlive its members (E21, ticket s5); the mock already stores that identity on the group rather than deriving it (E16). No existing table has a (group, substance, percentage) grain.
dbUnique index on LOWER(code) for Group CodecreateNo case-insensitive unique index exists at the base - internal_status.code is unique case-sensitively, which V034 itself records - while AC-E2 and the product owner's own test require a lower-case reuse of an emptied group's code to be refused. Functional and partial unique indexes are already house style (E9), so this is the established mechanism rather than a new one.
dbrawmaterial_multisourcing table and its mapping entity (db.table:backend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql#rawmaterial_multisourcing)removeThe ticket's sweep lists the multisourcing table with the entity, DTOs, mapper, repository, service and controller (s6); the table still stands at the base with its entity (E1, E12) and nothing references it, so the drop is safe and belongs to the same migration.
dbrawmaterial_composition, ingredient_composition, allergen_composition (db.table:backend/applications/cosmoreg-core/src/main/resources/db/migration/V002__Create_Application_Tables.sql#rawmaterial_composition)reuseThe active composition and its substance/percentage entries are read as they are for the precondition and for the identity the group is created with; nothing is altered and no composition data is copied.
dbrawmaterial_manufacturer (db.table:backend/applications/cosmoreg-core/src/main/resources/db/migration/V010__Add_Manufacturer_Currency_And_Site_Supplier.sql#rawmaterial_manufacturer)reuseAC5's eligibility is the presence of that row; storing a 'has manufacturer' flag on the group or the member would duplicate it and could go stale.
dbrawmaterial tablereuseMembership lives in its own junction row rather than in a new nullable column on this core table, so no existing reader of rawmaterial changes; the three sibling junction tables show that is the pattern for a one-to-one attribute.
dbAuditableEntity audit columns and the active reference-data conventionreusecreated_date, modified_date, created_by and modified_by come from the shared auditable base with its listener, and active BOOLEAN NOT NULL DEFAULT TRUE is declared inside the CREATE TABLE as the convention requires; no audit or status columns are invented.
backendMultisourcing group entities, repositories, services and endpointsunknownDecided at the Backend stage. No backend route or entity serves multisourcing groups at the base except the legacy one this ticket deletes (E12); this stage fixes only the columns, constraint names and query shapes they must match, and the endpoint contracts are already fixed by the mock's swap markers (E14).
frontendThe delivered mock data layer, types and hooks of the Multisourcing tabreuseThe approved mock is the data contract the schema serves: code, nullable name and comment, active, member count and a paged catalog of every group (E14, E17, E18, E19). Nothing in frontend/ is changed at this stage, and the schema asks for no revision of it.

Bound to

RAG revision
9a774150b6f4
Catalog
89259674c242
RAG indexed base
5e62a467fa51
Feature base
5e62a467fa51
Branch head
e07342b83f73
Main
deb0851f0ad7
Ticket snapshot
6bfae314e9f7

Contract sections

  • architecture · 14913 B · 3f139e3b5034 · shown
  • frontend-plan · 1583 B · 2ea64b06f473 · shown
  • db-plan · 7482 B · 8f2fb1201249 · shown
  • backend-plan · 2049 B · 56f739178db8 · shown
  • evidence · 11431 B · b1a10c376b4b · shown
  • review-plan · 6840 B · aa3e417eec13 · shown
Raw logsRaw

Logs are not loaded on this page. Open the Planner run to read them.