Skip to content
Agent Delivery Studio
  1. Plannerstate: Complete
  2. DB Designerstate: Complete
  3. Reviewstate: Not started · no automated review recorded for this stage
  4. Fixstate: Not started
  5. Human gatestate: Waiting · ready for review (current step)

MP-723

Formulation - Formulas - Operating procedures - Ticket 1 : free-text procedures (general, per phase, per line) and phase rename (frontend + db + backend)

Human review gate
Jira status
DB Review
Stage
Human review
Your move
Ready for review — non-blocking recommendations are available.

Ready for your review in Jira (DB Review) since 1 hour.

Current: Agent run on 2026-10-17 11:00Z (1 hour ago).

Jira
Jira confirmed
Jira read
Lifecycle activity
· Agent run

Database design to review

What was produced: the DB Designer’s deliverable. The plan further down is what the Planner requested.

Migrations
Design only — no migration included

Why no schema change is needed (the DB Designer)

Agent-generated evidence — verify before acting

migrations is empty because no migration FILE belongs to this stage, not because no schema change is needed. This repository's lifecycle has the designer produce the design document, with illustrative SQL only; the canonical migration is authored by the Backend Builder AFTER a human approves it at DB Review. The stage plan makes it a hard limit ('Design document only - no Flyway file, no entity, no backend or frontend code'; 'Next free Flyway version is V035; name it, do not create it'). The schema change proposed, for that migration to carry: ONE file at V035 (V034 is the highest at the base; out-of-order is false, so re-check before merge) with three ADD COLUMN statements - formula.operating_procedure TEXT (AC1), formula_phase.operating_procedure TEXT (AC2), formula_phase_line.instruction TEXT (AC3). Each nullable, no default, no backfill, so it is metadata-only and cannot abort on populated tables. TEXT not VARCHAR(n) because AC13 forbids a length limit. No new table, index, constraint, FK, enum or reference-data row; no existing column changed. One String entity field per table lands in the same change, mapped columnDefinition = "TEXT" with no length. Four criteria need no schema, recorded so the Backend stage invents none: AC6/AC7 reuse formula_phase.label and its per-formula unique constraint on trimmed labels, AC9 the composition lock guard, AC11 formula_audit_event with its seeded ATTRIBUTE_MODIFIED type. AC4, AC5 and AC10 are client view state, persisted nowhere. Gate: four read-only critics, five rounds, one anonymous peer round. No BLOCKER and no schema error in any round. Round 5's three fixes landed after the bound was reached and are not themselves critic-verified; all three correct prose contradictions and are listed in section 12. context7 was down all run, so four version claims are marked unverified upstream and grounded in-repo instead. Five open questions are flagged for DB Review; only OQ2 and OQ1 could change V035.

API impact
8 API impacts described
Published commit
e4c9973a34fa · Broker receipt push-1343
API impacts8

Agent-generated evidence — verify before acting

EndpointChangeReason
GET /v1/formulas/{formulaId}/proceduresNew endpoint. Returns { formulaId, general, byPhaseId, byLineId } read from the three new columns in one pass: formula.operating_procedure, plus the per-phase and per-line projections keyed by formula_phase.id and formula_phase_line.id.The approved mock's getFormulaProcedures pins this signature. Served by the formula PK read plus the composition read that idx_formula_phase_line_formula_id and uq_formula_phase_formula_label's leading formula_id already serve, so no new index.
PUT /v1/formulas/{formulaId}/procedures/generalNew endpoint. Writes formula.operating_procedure. NULL means not set and the response serves ""; a blank or whitespace-only write is stored as NULL.AC1. The empty convention follows FormulaCompositionService#normalizeLot so that 'not set' has one spelling in the database; this differs from formula.notes, which stores '' (disclosed as OQ3).
PUT /v1/formulas/{formulaId}/phases/{phaseId}/procedureNew endpoint. Writes formula_phase.operating_procedure WHERE id = phaseId. Refused on a locked composition by the existing loadUnlockedFormula guard.AC2 and AC9. Keyed on the phase id, not the label, so AC6's rename keeps the procedure; no new lock mechanism and no new column for the lock.
PUT /v1/formulas/{formulaId}/lines/{lineId}/instructionNew endpoint. Writes formula_phase_line.instruction WHERE id = lineId. Must NOT be refused when the line holds no raw material.AC3. The instruction rides the line row's identity, so a reorder, a phase-cell change, a drag and a phase rename all leave it on its line. rawmaterial_id is nullable (V032:67) and the approved mock renders an editable area for a material-less line.
PATCH /v1/formulas/{formulaId}/phases/{phaseId}New route. Updates formula_phase.label on one row, keeping its id and sort_order. Must apply normalizePhase's strip() and MAX_LABEL_LENGTH refusal before its uniqueness pre-check and before the UPDATE.AC6 and AC7, with no schema change: uq_formula_phase_formula_label (formula_id, label) is already the 'already used' rule. The constraint compares raw strings, so without the same trimming a rename to "Phase A " would sit beside "Phase A" - the artefact V032:27-30 exists to prevent.
POST /v1/formulas (duplicate modes) - FormulaService#create, FormulaPhaseRepository#copyPhases, FormulaPhaseLineRepository#copyLinesBoth native INSERT ... SELECT statements must add the new column to their enumerated column list and SELECT (p.operating_procedure, l.instruction - NOT NULL, unlike lot and batch). FormulaService#create must set operatingProcedure from source.getOperatingProcedure() when duplicating.AC12, and the one way this design fails silently: an unlisted column is not copied, with no error anywhere. copyComposition never touches the formula row and create builds the entity from request.notes(), so the general procedure is not copied by the composition path. Needs a duplication test.
GET /v1/formulas/{id}/audit-events (existing read; new entries written on every procedure write and rename)No schema change. Raise ATTRIBUTE_MODIFIED events through FormulaAuditService#recordAttributeModified, with a fixed field key in attribute, the texts in previous_value / new_value (both TEXT, so AC11 and AC13 compose), and the subject in detail (TEXT).AC11 asks for how attribute changes are recorded today, so no event type is seeded and V035 stays pure DDL. attribute is VARCHAR(100) and a label can be 255 chars, so the identifier goes in detail, carrying the line's id as text (a name can be NULL, a position is rewritten by renumber()). See OQ2.
All five procedure routes, on a locked compositionNo schema change. Every procedure write and the rename go through the existing FormulaCompositionService#loadUnlockedFormula guard, which throws BusinessRuleException("exception.formula.composition.locked") when the formula's status row has locksComposition.AC9 asks the server to refuse these writes the same way it refuses composition writes today. No new column, flag or constraint. Collapse, expand and show/hide-all never write, so the lock does not apply to them.
ProvenanceTechnical
Repository
cosmocomply/monorepo
Published commit
e4c9973a34fa
Push receipt
push-1343
Design document blob
feda0bfa8d4d
Design artifact
workflow-artifact-ee646aab73f213d94edd6466fa5291b05af30390 · ee646aab73f2 · 2026-10-17 11:00Z
Accepted by handoff
workflow-handoff-14c6836068a10c18260514067095a80c · CLEAN · 76dcb6ea034b · 2026-10-17 11:00Z
Validated against contract
manifest.json · bc3106ab2915
Produced by
db-designer-mp-723-14c68360

Next action

Routine
DB Designer (db-designer)
Pull request
#151
Tests
8 passed · 0 failed · 0 not executable · 0 not run
Review
Human DB review only; no automated review recorded
Attempt
Database · stage attempt entered 2026-10-17T11:52:37.508+0200
Open requirements
0 open

You

Review needed

Ready for review — non-blocking recommendations are available.

Plan and implementation

Plan

What was requested: the Planner’s plan for this stage. What the DB Designer produced is under “Database design to review”.

Objective. A1: extend the three tables that already own the rows the texts describe - one nullable TEXT column each on formula (AC1), formula_phase (AC2) and formula_phase_line (AC3). No new table, no new index, no reference-data seed, no change to an existing column. Four criteria need no schema at all: the rename and its refusal reuse formula_phase.label and its per-formula unique constraint (AC6, AC7), the lock reuses the existing composition guard (AC9), and History reuses the append-only audit event with the seeded ATTRIBUTE_MODIFIED type (AC11). AC12 needs no schema change but does need the two enumerated duplication copy statements extended, and the general procedure copied by the formula creation path - the document says both. One Flyway file planned at the next free version V035: three ADD COLUMN statements, nullable with no default, metadata-only on populated tables. Rejected: a polymorphic procedures table (A2) and 1:1 child tables (A3). This stage delivers the design document only; the migration, entity fields and endpoints belong to the Backend stage.

Approach. Target: three nullable TEXT columns on three existing tables, one Flyway migration at the next free version, three entity fields, and no other schema object. This stage produces the design document only; the migration and the entities are authored at the Backend stage, which is the lifecycle of this repository (Planner, DB Designer, human DB Review, then the Backend Builder writes the canonical migration). 1. formula.operating_procedure - TEXT, nullable, no default. AC1's general procedure. It sits beside formula.notes rather than inside it: V029 deliberately split the formula's free text by lifetime and purpose, notes being the formulator's lab notebook (E4, E5). TEXT and not VARCHAR(n) because AC13 forbids a length limit and the repository reserves VARCHAR(n) for limits that are business rules. 2. formula_phase.operating_procedure - TEXT, nullable, no default. AC2's phase procedure, on the phase row. A rename writes only label, so the procedure survives it (AC6); the phase's cascade and the service's delete of an emptied phase destroy the procedure with the phase, which is precisely the loss AC8 warns about (E1). 3. formula_phase_line.instruction - TEXT, nullable, no default. AC3's line instruction, on the line row, so a reorder, a phase-cell change, a drag into another phase and a phase rename all leave it on its line with no extra mechanism (E2). Empty and absent: NULL means 'not set'. The mock's contract serves '' for a text that is not set (E19, E21), so the mapping is NULL on read becomes '', and a blank or whitespace-only write becomes NULL. The design document states this in one line so the Backend stage does not end up with two spellings of empty. No CHECK constraint is added: '' and NULL would mean the same thing and the service normalises. Keys: nothing keys on the phase label. The mock keys the texts by phase id and line id (E20, E22, E23) and these columns sit on those very rows, so the key is each table's primary key and the Mock contract's phaseId/lineId decision is honoured by construction. Phase rename (AC6, AC7) - no schema change. formula_phase.label is already the stored name, at most 255 characters and trimmed by the service (E1, E17), and uq_formula_phase_formula_label on (formula_id, label) is already AC7's 'a name already used by another phase of the same formula', with the comparison the server applies today. A rename is a label update on one row that keeps id and sort_order. Renaming the 'No phase' group is the same update on the row whose label is '', and a collision is refused by the same constraint. The design document must say explicitly that no new uniqueness rule, no case-insensitive index and no label history table is added. History (AC11) - no schema change. formula_audit_event is append-only, cascades with its formula and already carries attribute with previous_value and new_value as TEXT (E6); ATTRIBUTE_MODIFIED is already seeded (E7); recordAttributeModified already writes a field key with resolved display strings (E8). AC11 asks for exactly 'how attribute changes are recorded today', so the Backend stage raises ATTRIBUTE_MODIFIED events with attribute keys for the general procedure, the procedure of a named phase, the instruction of the line holding a given raw material, and a phase rename. No new formula_audit_event_type row is seeded, so V035 carries no data step. Duplication (AC12) - no schema change, but two existing statements must learn the new columns. copyPhases and copyLines are native INSERT ... SELECT statements with explicit column lists (E9, E10): a new column is silently not copied until it is listed there, which is the one way this design can fail quietly. The general procedure is on the formula row, which copyComposition never touches (E11), so it is copied by the formula creation path instead. Both facts belong in the design document as impact on the Backend stage, with the note that the lot and the batch stay deliberately uncopied. Lock (AC9) - no schema change. Every procedure write goes through the existing guard that refuses a composition write when the formula's status row locks the composition (E12). Migration: one Flyway file, V035, three ADD COLUMN statements, one per table. V034 is the highest version at the base, the branch adds no migration and main has not moved since the index, so there is no version collision (E18). Each column is nullable with no default, which is a metadata-only change in PostgreSQL and cannot abort on populated tables - the compatibility reasoning V029 already wrote down for this repository's preview databases restored from production-shaped data (E5). No backfill, no narrowing, no new UNIQUE and no NOT NULL, so no existing row can violate anything and the migration is reversible by dropping three columns. Entities: ddl-auto is validate, so the entity field and the column must land together at the Backend stage - one String field on Formula, on FormulaPhase and on FormulaPhaseLine, each mapped to its column with no length attribute (E15, E16, E17). The design document gives the column-to-field mapping so that pairing is unambiguous. Indexes: none. A procedure text is never filtered, joined or sorted on; it is read only as part of a composition read, which is already served by idx_formula_phase_line_formula_id and by the leading column of uq_formula_phase_formula_label (E1, E3). A speculative index belongs in the document's 'consider later' section, not in the design. Readers: nothing existing changes, so every current entity, DTO, specification and frontend type is unaffected - an added nullable column is invisible until the Backend stage maps it. The frontend needs no change from this stage: the mock's five signatures stay the contract and the flip replaces their bodies at the Backend stage. Deliverable: one design document on the ticket branch, docs/superpowers/specs/2026-10-17-MP-723-operating-procedures-db-schema.md, covering the three columns with types and nullability, the illustrative DDL, the column-to-field and column-to-mock-field mappings, the four criteria that need no schema change (AC6, AC7, AC9, AC11), the AC12 copy-statement impact, the compatibility reasoning, and the open questions it leaves to the Backend stage. — The code decides this one. Each of the three texts describes exactly one existing row, and that row's lifecycle is already the behaviour the acceptance criteria ask for: a phase is a row with a stored label that a rename updates in place (E1, E17), so AC6's 'the phase keeps its position and its procedure' is free; the phase row cascades to its lines and is deleted once its last line leaves (E1, E2), so AC8's 'its procedure will be deleted with it' is the row disappearing rather than a cleanup rule; and a line row keeps its identity through reorders, phase changes and drags (E2, E22), so AC3's 'the instruction stays on its line' needs no work at all. A2 and A3 would have to re-create each of those three guarantees with foreign keys, an exclusive-arc CHECK and extra copy statements, and would add one or two queries to every composition read, which is already indexed to fetch a whole composition in one pass (E3). The DB guidance's own order - reuse, then extend a column, then create a table - points the same way, and no second home for the concept is created. TEXT with no length is settled by AC13 and by the repository's rule that VARCHAR(n) is for a real business limit, with formula.notes as the live precedent; the general procedure is a separate column from notes because V029 split the formula's free text by purpose on purpose, and folding manufacturing instructions back into the lab notebook would undo that (E4, E5). Two criteria need no schema at all and are recorded so the designer does not invent any: AC7's 'already used' is exactly uq_formula_phase_formula_label on trimmed labels (E1), and AC11 is the existing append-only formula_audit_event with the already-seeded ATTRIBUTE_MODIFIED type and the recordAttributeModified mechanism the ticket names (E6, E7, E8) - which keeps V035 pure DDL with no reference-data seed. The one place where 'just add a column' is not enough is AC12: copyPhases and copyLines enumerate their column lists and copyComposition never touches the formula row (E9, E10, E11), so the design document must say both that the two statements gain the new columns and that the general procedure is copied by the formula creation path. A2's only real advantage is extensibility towards MP-725, and MP-725 is explicitly out of this ticket and blocked by it, so paying its cost now is speculative.

Implementation steps

  1. 1DB design document for the three operating-procedure columns

    Database designer

    • docs/superpowers/specs/2026-10-17-MP-723-operating-procedures-db-schema.md

Implementation

Latest accepted result
Technically validated · Database ·
Verified review commit
ed0ae3200407Commit bound and verified by the Operator for the last review. Not necessarily the PR head.
Last attempted commit
ed0ae3200407Commit used by the latest agent attempt. Not necessarily the PR head.

Output commits

Local (attempted) commit — diagnostic only
a79cef523105
Published commit (Broker receipt push-1343)
e4c9973a34fa
Verified PR head
e4c9973a34fa

Test matrix

  • 0 Failed
  • 0 Not executable here
  • 0 Not run
  • 8 Passed

From build report workflow-artifact-e25f4f7d62f3f6012651d2f951233400a6dd5e74 · 2026-10-15 20:07Z · accepted by a handoff

Gate reasons are reported by the agent. Agent-generated evidence — verify before acting

GateStatusCommandReason
composition stays on the real backend: 10 apiClient calls in api/formula-composition.ts, unchangedPassedgrep -c 'apiClient\.' frontend/src/domains/formulation/features/formulas/api/formula-composition.ts—
frontend: npm run buildPassedcd frontend && npm run build—
frontend: npm run lintPassedcd frontend && npm run lint0 errors; 13 pre-existing react-hooks/incompatible-library warnings in src/shared, none in features/formulas
layer limit: no change under backend/, etl/, infra/, k8s/ or db/migrationPassedgit diff --name-only origin/main HEAD | grep -E '^(backend/|etl/|infra/|k8s/)|db/migration'—
mock-first: no new @/shared/lib/api-client import in the diffPassedgit diff --staged origin/main -- '*.ts' '*.tsx' | grep -E "^\+.*from ['\"]@/shared/lib/api-client"No new import added; the only match among changed files is the pre-existing line 40 of api/formula-composition.ts
mock-first: TODO(MP-723) marker on every function of api/formula-procedures.tsPassedgrep -c 'TODO(MP-723)' frontend/src/domains/formulation/features/formulas/api/formula-procedures.ts6 markers for the 5 exported functions plus the file header; 0 apiClient imports in that file
no new grid column and no length cap: composition-columns.ts and composition-bounds.ts unchangedPassedgit diff --name-only origin/main HEAD | grep -E 'composition-(columns|bounds)\.ts'—
single readOnly derivation and single lock banner in composition-tab.tsxPassedgrep -nE 'const readOnly' [redacted]-tab.tsx && grep -n 'Composition locked' [redacted]-tab.tsx—

Review findings and fixer

This gate concerns the database design: no automated review is recorded for it, and the human DB review is the check. Accepting the design establishes no implementation or testing outcome.

Agent work requests

Agent work request · Completed2026-10-16 14:37Z · requested by admin systemease

The agent work for your request has finished.

Human request text — verbatim from Jira (secrets masked)
Resume work on PR #151 (branch claude/mock-MP-723-operating-procedures). Apply the PO test finding of 2026-10-16. # Composition grid, line instruction area (line-instruction-row.tsx) and phase procedure area (phase-procedure-row.tsx): the "Edit" link is right-aligned in a full-width row and is off-screen when the grid scrolls horizontally. Expected: the "Edit" link sits immediately after the area title on the left, visible without horizontal scrolling, in both areas. (AC2, AC3, PO decision 2026-10-16) Do not change the general "Operating procedure" panel, the edit/read-mode rules, or anything else in the PR.

Next: Nothing to do.

Routing
FIX_WITHIN_SCOPE → decision FIX_WITHIN_SCOPE
Human finding
H1 MAJOR acceptance-criterion H2 MAJOR acceptance-criterion
Fix
2 applied (H1, H2), 0 unresolved
Plan revision
sha256:8159c3f7c2feaca2be2a944830feaa58a6dd280a144e14b0acb15d22f0c39821 · accepted plan feature-contract-c82c4c11c7f81001e5883484e719cefd · c89d0fd2da71
Bound PR / SHA
#151 @ 2bbc7d928c7b
Lifecycle
Completed · 2026-10-16 15:03Z · Mock · request attempt (request event 17792)
Triage RAG context
revision 6ff9aee4994f · indexed 3830b223cba4 · freshness EXACT · triage started 2026-10-16 14:47Z

Triage rationale

Agent-generated evidence — verify before acting

The request applies PO test decision C1 of 2026-10-16 (ticket comment 14074), which is already written into the ticket snapshot as an accepted criterion: AC2 states "The 'Edit' control of the phase procedure area and of the line instruction area (AC3) sits next to the area title, visible without horizontal scrolling of the grid. (PO decision 2026-10-16)". So this is an accepted criterion that is not met at head 2bbc7d92, not a new requirement. It needs no change to the accepted plan: the mock contract's frontend-plan already creates phase-procedure-row.tsx ("a sibling TableRow with COMPOSITION_COLUMN_COUNT colSpan ... shows the text and, in edit mode, the shared editor") and line-instruction-row.tsx ("a sibling colSpan TableRow ... editable in edit mode"), and nowhere fixes where the Edit control sits inside those rows. Placing it immediately after the area title is a layout detail inside two components the plan already owns, so a Fixer can satisfy it within the accepted plan. Two findings, one per named component, both acceptance-criterion (never MINOR). MAJOR rather than BLOCKER: editing still works, but only after horizontal scrolling of a grid wider than the screen, which AC2 now forbids. The request's fence (do not touch the general "Operating procedure" panel, the edit/read-mode rules, or anything else in the PR) is carried into each finding. Note on evidence: this run can read only the base at workSha 3830b223; the PR head is not mounted (no overlay, object 2bbc7d92 absent from /work), and neither row component exists at the base. The defect is taken from the PO's own test of the PR #151 preview, which is first-hand evidence at the bound head; the mock review of that same head was CLEAN and reported nothing on this placement, so no finding is withdrawn or superseded.

Human finding (as triaged)

Agent-generated evidence — verify before acting

H1: Phase procedure area: the "Edit" control is right-aligned in the full-width row and off-screen when the grid scrolls horizontally (AC2) H2: Line instruction area: the "Edit" control is right-aligned in the full-width row and off-screen when the grid scrolls horizontally (AC2/AC3)

Accepted handoffs and binding

WhenStepResultRecord
2026-10-17 11:00Zdesign (db)CLEANworkflow-handoff-14c6836068a10c18260514067095a80c
2026-10-17 10:14Zplan (db)CLEANworkflow-handoff-91ca7a8a7fe6e56a987ba8bac5727da8
2026-10-16 15:03Zreview (mock)CLEANworkflow-handoff-532c67a6c56042a6d09c2907540ec7d4
2026-10-16 14:54Zfix (mock)CLEANworkflow-handoff-a4263396fd5fd74539dc89ce3f798635
2026-10-16 14:48Zplan (mock)CLEANworkflow-handoff-11030ece72702d16adc50543164e856e
2026-10-15 20:20Zreview (mock)CLEANworkflow-handoff-edfba2f39704cbd3f0ae3ae6f9761a21
2026-10-15 20:07Zbuild (mock)CLEANworkflow-handoff-c40069123986d992abe27875fe36e209
2026-10-15 19:24Zplan (mock)CLEANworkflow-handoff-c82c4c11c7f81001e5883484e719cefd
Current binding
workflow-handoff-14c6836068a10c18260514067095a80c · 76dcb6ea034b
Branch / last attempted commit
claude/mock-MP-723-operating-procedures @ ed0ae3200407

Historical attempts

  • Earlier stage Fix: Complete · 2026-10-16 14:54Z
  • Earlier stage Review: Complete · clean · 2026-10-16 15:03Z
  • History Handoff: Technically validated · Database · 2026-10-17 11:00Z
  • History Handoff: Technically validated · Plan · 2026-10-17 10:14Z
  • History Handoff: Technically validated · Review · 2026-10-16 15:03Z
  • History Handoff: Technically validated · Review · 2026-10-16 14:54Z
  • History Handoff: Technically validated · Plan · 2026-10-16 14:48Z
  • History Handoff: Technically validated · Review · 2026-10-15 20:20Z
  • History Handoff: Technically validated · Mock · 2026-10-15 20:07Z
  • History Handoff: Technically validated · Plan · 2026-10-15 19:24Z
  • History Stop record: Technical failure, no work lost · Plan · 2026-10-15 17:52Z
  • History Stop record: Technical failure, no work lost · Plan · 2026-10-15 15:21Z

Raw records and logs

Raw

Projected 2026-10-17 12:02Z · Delivery view · All workflows