- Plannerstate: Complete
- Backend Builderstate: Complete
- Reviewstate: Complete · clean
- Fixstate: Complete
- Human gatestate: Waiting · ready for review (current step)
MP-691
Formulation - Formulas : use a pre-mix as an ingredient, with its price, its (+) detail and "Not included" (frontend + backend + db)
Human review gate- Jira status
- 👤 Ready for Fullstack Review
- Stage
- Human review
- Your move
- Ready for review — non-blocking recommendations are available.
Ready for your review in Jira (👤 Ready for Fullstack Review) since 12 hours.
- Jira
- Jira confirmed
- Jira read
- Lifecycle activity
- · Agent run
Next action
- Routine
- Backend Builder (backend-builder)
- Pull request
- #143
- Tests
- 10 passed · 0 failed · 4 not executable · 0 not run
- Review
- Clean · 3 recommendations
- Attempt
- Backend · stage attempt entered 2026-10-16T10:50:36.460+0200
- Open requirements
- 8 open (1 request, 7 findings)
Plan and implementation
Plan
Objective. A1: extend MP-690's composition aggregate in place. V035 lands the approved design's three columns, foreign key, five CHECKs and index on formula_phase_line with the matching entity fields; FormulaLineResponse loses kind - the work request's backend half, with the mapper constant and one test assertion - and gains premixFormulaId, premixCode, premixName, notIncluded and notIncludedLevel, as do the add and update requests; the six pre-mix rules, the mutually exclusive reference assignment and the single writer of formula_premix_use all live in the existing FormulaCompositionService; two reads are added, premix-candidates and premix-tree; then the mock pair is flipped to apiClient and its fixtures deleted. Reused unchanged: formula_premix_use and its daughters reader, the per-site price and INCI resolution, the client-side cost calculation and every pre-mix component. Nothing derived is stored and no replacement discriminator appears. Left out: a parallel pre-mix service or route (A2), stored prices or trees (A3), and the ticket's own exclusions.
Approach. TARGET, LAYER BY LAYER. Everything below is on the existing ticket branch; the approved design document on the branch is the normative source for the DDL and must not be redesigned (E25). 1. SCHEMA (slice 1, db-engineer). One Flyway file, V035__Add_Premix_Reference_And_Not_Included_To_Formula_Phase_Line.sql, transcribed from the design document's ALTER on formula_phase_line: premix_formula_id UUID NULL with a foreign key to formula(id) and an explicit ON DELETE RESTRICT; not_included BOOLEAN NOT NULL DEFAULT FALSE (the DEFAULT is what lets the ALTER run on populated rows); not_included_level VARCHAR(20) NULL. Five named CHECKs, each with its IS NOT NULL guard: never both references, no direct self-reference (premix_formula_id <> formula_id), no picked batch on a pre-mix line, the level restricted to THIS_PREMIX and FINAL_FORMULA, and a level only when the flag is set. One index, idx_formula_phase_line_premix_formula_id, which the RESTRICT check, the tree walk and the usage reconciliation all need. Nothing else: no kind column (the work request and design decision D2), no new table, no reference-data entity. V034 is the highest version at the indexed base (E30); the builder confirms the number against main before authoring. 2. ENTITIES (slice 1). FormulaPhaseLine gains premixFormula (@ManyToOne LAZY on premix_formula_id, to Formula), notIncluded (@Builder.Default false, nullable=false) and notIncludedLevel (@Enumerated(STRING), length 20), plus a new enum NotIncludedLevel {THIS_PREMIX, FINAL_FORMULA} in domain/formula - the two codes the mock already exchanges (E22). ddl-auto is validate, so the fields and the migration land together (E6). findCompositionByFormulaId adds LEFT JOIN FETCH on the referenced formula so mapping never lazy-loads (E32). 3. READ CONTRACT (slice 2). FormulaLineResponse drops kind and gains premixFormulaId (UUID, null on a raw-material or empty line), premixCode and premixName (String, "" when absent, matching the record's existing "" convention for commercialName), notIncluded (Boolean) and notIncludedLevel (String code or null). FormulaCompositionMapper loses LINE_KIND_RAW_MATERIAL and fills the pre-mix trio from line.getPremixFormula() (E3, E4). FormulaCompositionServiceTest loses the one kind assertion (E5). This completes the human work request's backend half; no replacement discriminator of any name is introduced anywhere - the reference field is the answer. 4. WRITE CONTRACT (slices 2-3). UpdateFormulaLineRequest gains premixFormulaId, notIncluded and notIncludedLevel as plain nullable components, following the record's documented full-replacement carve-out (E20). AddFormulaLineRequest gains the same three so a line can be created already holding a pre-mix. applyValues is extended to apply the two references mutually exclusively - assigning a pre-mix clears the raw material and the picked batch, assigning a raw material clears the pre-mix - so a mixed edit returns the rule's message instead of hitting the CHECK (E13). 5. RULES (slice 3), all in FormulaCompositionService, each a BusinessRuleException with a new messageKey: (a) a line holds at most one reference, with the preview's wording; (b) the referenced formula exists and is_premix is true; (c) nesting depth at most 5, walking the referenced formula's lines; (d) no cycle - a depth-first walk from the edited formula over premix references that accepts a shared sub-pre-mix reached twice by different paths and refuses only a genuine loop, which is the behaviour the mock review demanded; (e) on a formula that is itself a pre-mix, a ticked line requires a level, and on any other formula the level must be null; (f) a pre-mix line carries a typed lot only, never a picked batch. The depth, cycle and both-references messages reproduce the preview's text verbatim (E21) so the message does not change between the optimistic refusal and the server's, in messages.properties and messages_fr.properties next to the existing exception.formula.line.* keys (E29). Errors propagate to GlobalExceptionHandler as ProblemDetail; nothing is caught broadly. 6. USAGE RECORD (slice 4). One writer: a private reconcile(formulaId) in FormulaCompositionService that makes the formula's formula_premix_use rows equal the distinct non-null premixFormulaId values of its lines, using findByFormula_Id (E9). Called after addLine, updateLine, deleteLine and after copyComposition. copyLines' explicit column list gains premix_formula_id, not_included and not_included_level, keeping lot and batch deliberately uncopied (E12), and copyComposition then reconciles the duplicate (E14). GET /v1/formulas/{id}/daughters and the flag-off guard are untouched and start reporting the truth (E10, E11), which is AC10 end to end and what makes the alert count move from 2 to 1. 7. THE isPremix FLIP (slice 5), in FormulaService#applyPremixChange (E11): turning the flag off clears notIncludedLevel on that formula's lines, since the level is meaningless there; turning it on is refused, with its own message key, while a ticked line has no level. This is the behaviour the approved design states for its open item on the flip; it is adopted, not invented here. 8. READ ENDPOINTS (slices 6-7). - GET /v1/formulas/premix-candidates?q=&siteId= on FormulaController, served by FormulaService over formula.is_premix (E19, E31): no status filter, so a pre-mix in Development is offered (AC1); each row carries code, name, status code, label and colour and the pre-mix's own total as entered, computed as the sum of its filled non-QSP line percentages plus a filled QSP line's derived share. It mirrors the raw-material candidate endpoint's parameters and ranking (E17) so the grid merges two ranked lists exactly where the mock merges them today (E24); the live raw-material search is not touched. - GET /v1/formulas/{formulaId}/premix-tree?premixFormulaId=&siteId= on FormulaCompositionController (E15), served by FormulaCompositionService: a depth-first walk of the referenced formula's lines capped at 5 levels, each node carrying its path, depth, share in its own pre-mix, the referenced name, whether the node is itself a pre-mix, its aggregated INCI, its pricePerUnit, currencyCode and unitCode at the requested site, and its own notIncluded pair. {formulaId} is the formula being edited and siteId is the site it is being read at, so the same pre-mix prices differently at another site (AC2, AC3); prices and INCI come from RawMaterialSiteDataService, which already resolves exactly these fields per site (E18). The percentage in the final formula is the line's percentage multiplied along the path, as the mock computes it. - The server serves figures, not verdicts: the dash with its reason and the excluded count stay in the one client-side cost calculation that already owns MP-690's exclusion reasons (E26), so AC4's foreign-currency, no-price and wrong-unit cases are the existing reasons applied to pre-mix nodes, and AC5's not-100% warning stays a client-side reading of the served total. 9. THE FLIP (slice 9, frontend-engineer). api/formula-premix.ts becomes apiClient calls against the endpoints its TODO(MP-691) markers name, keeping its refused-result shape by mapping a 400 ProblemDetail detail into { outcome: "refused", message } so the hook's toast-and-revert path and every component stay as they are (E24). lib/mock-premix-data.ts is deleted with its three preview devices, which is what removes the three mock-stage limits the ticket requires gone: a line can be added after a pre-mix line, no demo lines are seeded into every formula, and the daughters alert lists only real formulas (E23, E27). api/formula-composition.ts's line mirror gains the five new fields and loses nothing else; use-formula-description reads the live daughters endpoint instead of the fixture. The single pre-mix predicate over premixFormulaId stays the only derivation site (E28). 10. TESTS (slice 8). FormulaCompositionServiceTest for each rule and for the reconciliation; FormulaServiceTest for the flip; a repository H2 IT for the migration and its five CHECKs, following the existing H2 IT naming; a Keycloak and Postgres controller IT for the two new endpoints covering 401, validation and the i18n of each new message. The gate is ./gradlew --no-daemon spotlessCheck and ./gradlew --no-daemon test, then npm run build and npm run lint after the flip (AC13). DATA FLOW. Grid cell search -> formula-candidates plus premix-candidates, merged client-side -> PUT or POST on the line with premixFormulaId -> service rules -> line row plus reconciled usage rows -> composition response with the pre-mix trio -> (+) opens premix-tree at the current site -> client cost calculation renders the price, the dash with its reason, the footer count and the not-100% warning. ERROR HANDLING. Rule refusals are BusinessRuleException (400) with a messageKey present in both message files; a missing formula, line or referenced pre-mix is ResourceNotFoundException (404); every composition write stays behind the existing locked-status refusal (E14). Nothing new is caught or swallowed in the api layer: a refusal becomes the mock's refused result, any other failure keeps the existing error path. — The code leaves little room for anything else and the two earlier stages fixed the rest. There is exactly one composition line table, one line entity, one mapper, one write service and one locked-formula guard (E6, E7, E13, E14, E15), so a second family (A2) would duplicate the ordering, locking and audit logic and split the single-reference rule across two writers. The approved DB design stores nothing derived and was approved on that basis (E25), prices are per-site and already resolved per site (E17, E18), and AC3 makes that site-dependence observable - so A3 would both contradict the approved schema and be wrong at the second site. The accepted mock is the API contract: two level codes (E22), a depth of 5 with three exact messages the backend must reproduce (E21), a refused-result error shape and one TODO(MP-691) marker per function naming its endpoint (E24); A1 is the only shape that lets the flip change no hook and no component. AC10 needs the usage record written, not redesigned: the table, its repository and its only reader already exist (E8, E9, E10) and the flag-off guard becomes effective the moment rows appear (E11). Finally, the human work request's backend half is unavoidably part of this stage: kind is still on the response, in the mapper constant and in one test assertion at the base (E1, E2, E3, E4, E5), and no routine before this one may touch backend/.
Implementation steps
- 1Migration V035 and the line entity (db-engineer)
Backend builder
backend/applications/cosmoreg-core/src/main/resources/db/migration/V035__Add_Premix_Reference_And_Not_Included_To_Formula_Phase_Line.sqlbackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/formula/FormulaPhaseLine.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/formula/NotIncludedLevel.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLineRepository.java
- 2Line contract: drop kind, add the pre-mix and Not included components (backend-engineer)
Backend builder · after step 1
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/FormulaLineResponse.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/mapper/formula/FormulaCompositionMapper.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/UpdateFormulaLineRequest.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/AddFormulaLineRequest.javabackend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionServiceTest.java
- 3The six write rules and their messages (backend-engineer)
Backend builder · after step 2
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionService.javabackend/applications/cosmoreg-core/src/main/resources/messages.propertiesbackend/applications/cosmoreg-core/src/main/resources/messages_fr.properties
- 4Pre-mix usage record: one writer and its reconciliation (backend-engineer)
Backend builder · after step 3
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionService.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLineRepository.java
- 5Not included level across the isPremix flip (backend-engineer)
Backend builder · after step 3
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaService.javabackend/applications/cosmoreg-core/src/main/resources/messages.propertiesbackend/applications/cosmoreg-core/src/main/resources/messages_fr.properties
- 6GET /v1/formulas/premix-candidates (backend-engineer)
Backend builder · after step 2
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/controller/formula/FormulaController.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaService.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixCandidateResponse.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaRepository.java
- 7GET /v1/formulas/{formulaId}/premix-tree (backend-engineer)
Backend builder · after step 2
backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/controller/formula/FormulaCompositionController.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionService.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixTreeResponse.javabackend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixTreeNodeResponse.java
- 8Tests per criterion and the backend gate (backend-engineer)
Backend builder · after step 4, 5, 6, 7
backend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionServiceTest.javabackend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaServiceTest.javabackend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLinePremixH2IT.javabackend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/controller/formula/FormulaPremixCompositionIT.java
- 9Flip the mock pair to the API and delete the fixtures (frontend-engineer)
Backend builder · after step 8
frontend/src/domains/formulation/features/formulas/api/formula-premix.tsfrontend/src/domains/formulation/features/formulas/lib/mock-premix-data.tsfrontend/src/domains/formulation/features/formulas/api/formula-composition.tsfrontend/src/domains/formulation/features/formulas/description/hooks/use-formula-composition.tsfrontend/src/domains/formulation/features/formulas/description/hooks/use-formula-description.ts
Implementation
- Latest accepted result
- Technically validated · Review ·
- Pull request
- PR #143 (opens in a new tab)
- Verified review commit
9049c1e1dd19Commit bound and verified by the Operator for the last review. Not necessarily the PR head.- Last attempted commit
9049c1e1dd19Commit used by the latest agent attempt. Not necessarily the PR head.
Output commits
- Local (attempted) commit — diagnostic only
a69ed7c92989- Published commit (Broker receipt push-1283)
9049c1e1dd19- Verified PR head
9049c1e1dd19
Test matrix
- 0 Failed
- 4 Not executable here
- 0 Not run
- 10 Passed
Gate reasons are reported by the agent. Agent-generated evidence — verify before acting
| Gate | Status | Command | Reason |
|---|---|---|---|
| backend: FormulaPremixCompositionIT (17 tests, new, written for CI) | Not executable here | cd /work/backend && ./gradlew --no-daemon test --tests "*FormulaPremixCompositionIT" | No Docker in this pod: @KeycloakPostgresIntegrationTest needs Keycloak and PostgreSQL Testcontainers. Covers 401 on both new endpoints, parameter validation, the localized refusal texts in English and French, AC2/AC3 pricing at two sites and AC10 end to end (daughters 2 then 1). |
| backend: full ./gradlew build | Not executable here | cd /work/backend && ./gradlew --no-daemon build | No Docker in this pod; the full build runs the Testcontainers-bound IT layers. CI runs it. |
| backend: RawmaterialSiteCodeHistoryH2IT (20 tests) | Not executable here | cd /work/backend && ./gradlew --no-daemon test --tests "*RawmaterialSiteCodeHistoryH2IT" | No Docker in this pod. Despite its H2IT name it is annotated @KeycloakBatchH2IntegrationTest and its ApplicationContext fails to load with Testcontainers' 'Could not find a valid Docker environment' from a KeycloakContainer. Pre-existing and unrelated: the class has zero pre-mix references. |
| db: V035 CHECK four-cases per backend/CLAUDE.md | Not executable here | cd /work/backend && ./gradlew --no-daemon test --tests "*FormulaPhaseLinePremixH2IT" | Not a Docker gap: no test harness here runs Flyway (h2 profile disables it with ddl-auto create-drop; PostgresApplicationContextInitializer forces ddl-auto=create), so the five named CHECKs exist in no test schema and V035 has never been executed. See deviation 6 in the commit message. |
| AC12 discharge: no TODO(MP-691) and no mock-premix-data left | Passed | grep -rn 'TODO(MP-691)' /work/frontend/src/ ; grep -rn 'mock-premix-data' /work/frontend/src/ | Both return no match; lib/mock-premix-data.ts is deleted. |
| AC17 discharge: no line kind or replacement discriminator under backend/ | Passed | grep -rn 'LINE_KIND\|String kind\|kind()' --include=*.java /work/backend/applications/cosmoreg-core/src | No match: the component, the mapper constant and the test assertion are gone and nothing replaced them. |
| backend: compileJava compileTestJava | Passed | cd /work/backend && ./gradlew --no-daemon compileJava compileTestJava | — |
| backend: spotlessCheck | Passed | cd /work/backend && ./gradlew --no-daemon spotlessCheck | — |
| backend: unit + H2 tests (1432 executed, 0 failed) | Passed | cd /work/backend && ./gradlew --no-daemon cleanTest test --tests "*ServiceTest" --tests "*RawMaterialTestingStatusControllerH2IT" --tests "*CarbonFootprintUnitServiceH2IT" --tests "*ProductTypeServiceH2IT" --tests "*BatchAnalysisServiceH2IT" --tests "*RawMaterialCreationWithDescriptionH2IT" --tests "*UsageAndStorageConditionsServiceH2IT" --tests "*DocumentStatusCalculatorServiceH2IT" --tests "*Com… | Every H2 IT class except RawmaterialSiteCodeHistoryH2IT, which is reported separately because it cannot start without Docker. |
| flip assertion: changed api/ files import the real client | Passed | git diff --staged --name-only 3830b223cba49f2cf0cbffc1547d46f031e0d747 | grep -E 'frontend/.*/api/.*\.ts$' | xargs -r grep -nE "from ['\"]@/shared/lib/api-client" | — |
| frontend: npm run build | Passed | cd /work/frontend && npm run build | Compiled successfully in 28.6s, 42/42 static pages. |
| frontend: npm run lint | Passed | cd /work/frontend && npm run lint | 13 problems, 0 errors; all 13 are pre-existing warnings and none is in a file this stage touched. |
| i18n: every new message key in both properties files | Passed | grep -c '^exception.formula.line.premix' /work/backend/applications/cosmoreg-core/src/main/resources/messages.properties /work/backend/applications/cosmoreg-core/src/main/resources/messages_fr.properties | All 11 new keys present in both files; the depth, cycle and both-references texts match the preview's wording verbatim. |
| migration hygiene: exactly one new .sql, version free on main | Passed | git diff --name-only HEAD~1 HEAD | grep -c '\.sql$' | One file, V035; V034 is the highest on origin/main and on the branch, and no other .sql changed. |
Review findings and fixer
Ready for review — non-blocking recommendations are available.
Recommendations did not trigger an automatic Fixer.
Agent-generated evidence — verify before acting
Blocking 0
No blocking finding.
Recommendations 3
| Id | Severity | File | Finding |
|---|---|---|---|
| F1 | MINOR | backend/applications/cosmoreg-[redacted].java | wouldContainItself's javadoc still points at lib/premix-rules.ts#wouldCycle, which this same commit deleted |
| F2 | ADVISORY | backend/applications/cosmoreg-[redacted].java | heightAbove issues one query per ancestor formula, so saving a line on a widely reused pre-mix costs a query per daughter |
| F3 | ADVISORY | backend/applications/cosmoreg-[redacted].java | The two PostgreSQL-only statements this commit changed were not executed here: no Docker, so CI is their first run |
No fix needed.
Agent work requests
Agents are working on your request.
remove kind from the composition line The line's kind (RAW_MATERIAL / PREMIX) is redundant: whether premixFormulaId is set already tells you what the line is. Remove it everywhere it describes a line. Frontend (frontend/src/domains/formulation/features/formulas/) Remove kind from FormulaLine and PremixIngredientNode in types/index.ts, and remove the FormulaLineKind type. Remove it from the mock layer (api/formula-composition.ts, api/formula-premix.ts, lib/mock-premix-data.ts),lib/composition-math.ts, and description/hooks/use-formula-composition.ts. Replace every row.kind === "PREMIX" check with row.premixFormulaId !== null, and every RAW_MATERIAL check withrow.premixFormulaId === null. Update the TODO(MP-691) markers that list kind in the future request or response body. Backend (this is already on main, so it changes a merged API response) Remove kind from FormulaLineResponse. Remove LINE_KIND_RAW_MATERIAL from FormulaCompositionMapper. Update FormulaCompositionServiceTest, which asserts kind() == "RAW_MATERIAL". DB design doc (docs/superpowers/specs/2026-10-13-MP-691-db-schema.md) There is no kind column, and that stays. Remove the §2 row and the migration comment that say kind is "derived on read". Reference rule: a line references rawMaterialId or premixFormulaId, never both. An empty line, with neither, stays legal. This is chk_formula_phase_line_single_reference, already in the design. Don't add any new constraint. Excluded: the kind on search-suggestion candidates (RawMaterialCandidate & { kind: … } in types/index.ts). It's a picker label that's never saved, so leave it. Also leave the kind in lib/formula-status.ts, which is unrelated..
What changes in the plan
Agent-generated evidence — verify before acting
Drop the line-level `kind` discriminator (RAW_MATERIAL / PREMIX) from MP-691; a line's nature is derived from `premixFormulaId` (set = pre-mix, null = raw material). 1. Mock/frontend contract (frontend/src/domains/formulation/features/formulas/): remove `kind` from `FormulaLine` and `PremixIngredientNode` and delete `FormulaLineKind` in types/index.ts; remove it from api/formula-composition.ts, api/formula-premix.ts, lib/mock-premix-data.ts, lib/composition-math.ts, description/hooks/use-formula-composition.ts; replace every `row.kind === "PREMIX"` with `row.premixFormulaId !== null` and every RAW_MATERIAL test with `row.premixFormulaId === null`; update the TODO(MP-691) markers listing `kind` in a future request/response body. This supersedes the accepted mock plan's constraint fixing `FormulaLineKind`. 2. Backend (already merged on main, so a shipped API response changes): remove `kind` from FormulaLineResponse, remove LINE_KIND_RAW_MATERIAL from FormulaCompositionMapper, update FormulaCompositionServiceTest (asserts kind() == "RAW_MATERIAL"). The accepted design's apiImpact for GET /v1/formulas/{id}/composition must be restated without the kind component. 3. DB design doc (docs/superpowers/specs/2026-10-13-MP-691-db-schema.md): no kind column, and that stays; delete the §2 row and the migration comment describing kind as "derived on read". Person's constraints: a line references rawMaterialId or premixFormulaId, never both; an empty line with neither stays legal (existing chk_formula_phase_line_single_reference); add no new constraint. Excluded from the removal: `kind` on search-suggestion candidates (RawMaterialCandidate & { kind: … } in types/index.ts), a never-saved picker label, and the unrelated `kind` in lib/formula-status.ts. Targeted at mock: the frontend data contract must change first and the db design is bound to it, so the design doc and apiImpact are re-derived in the same revision.
Next: Nothing is needed from you: the agents continue on their own.
Accepted baseline ddb518c7 · current head 8a2f48e1 (not approved)
What changed since the accepted baseline9
- Changed paths (PR's own change)
- 9 · complete
- Impact paths
backend/applications/cosmoreg-[redacted].javabackend/applications/cosmoreg-[redacted].javafrontend/src/domains/formulation/features/formulas/api/formulas.ts- Main drift
- 36 paths ·
main-ahead-of-pr-base·main-critical-path-since-pr-base
frontend/src/domains/formulation/features/formulas/CLAUDE.md[redacted]-grid.tsx[redacted]-row.tsx[redacted]-tab.tsx[redacted]-included-level-dialog.tsx[redacted]-detail-rows.tsx[redacted]-description-skeleton.tsx[redacted]-description-tabs.tsxfrontend/src/domains/formulation/features/formulas/description/pages/formula-description-page.tsx
- Routing
REPLAN→ decisionREPLAN- Human finding
- —
- Fix
- —
- Plan revision
sha256:7a6dd27a36e2df264a63cb562a7304770dfc6e9411718ac2555b27c34314b42b· accepted planfeature-contract-4da80112ec4ee78cdd47b7b0adfd4337·cf0a69c8d24e- Bound PR / SHA
- #143 @
8a2f48e12979 - Target stage
mock- Lifecycle
- Running · 2026-10-15 12:56Z
- Triage RAG context
- revision
6ff9aee4994f· indexed3830b223cba4· freshnessEXACT· triage started 2026-10-15 13:09Z
Triage rationale
Agent-generated evidence — verify before acting
The request removes the line discriminator `kind` from the feature's data contract. That contradicts, rather than fails, the accepted deliverables, so it is a requirement/design change and not a missed criterion. Evidence at the current head 8a2f48e1: the accepted db design (db/design.json, apiImpact for GET /v1/formulas/{id}/composition) states "The existing kind component finally varies between RAW_MATERIAL and PREMIX" and "kind is derived on read from premix_formula_id, so no column stores it"; the design doc docs/superpowers/specs/2026-10-13-MP-691-db-schema.md (added by this PR) carries the §2 row and migration comment the request wants deleted. The accepted mock plan fixes the type as a global constraint ("`FormulaLineKind` is `\"RAW_MATERIAL\" | \"PREMIX\"`", docs/superpowers/plans/2026-10-07-MP-691-premix-composition-line.md) and the type is live at head (frontend/.../types/index.ts, `export type FormulaLineKind`, with the comment "The discriminator is served by the backend and nothing infers it from the other fields" — the request inverts exactly that). On main the field is in the merged API response: FormulaLineResponse.kind and FormulaCompositionMapper.LINE_KIND_RAW_MATERIAL. No acceptance criterion in the ticket snapshot (AC1-AC13) mentions kind, so nothing accepted is unmet; conversely the removal touches the merged backend contract and the mock-stage plan's own constraints, which only a re-plan can revise. The nine PR changes since the accepted baseline (composition grid/row/tab, premix detail rows, not-included dialog, tabs/skeleton/page, CLAUDE.md) are the db-stage head's frontend state and do not remove kind; they do not conflict with this assessment. No requirement is named for withdrawal by the request text.
Your request was not accepted.
The request text was empty. request-empty
- Routing
- not triaged yet → decision
REFUSED - Human finding
- —
- Fix
- —
- Plan revision
- —
- Bound PR / SHA
- — @ —
- Triage RAG context
- not collected
Your request was not accepted.
The request did not come from a person allowed to request agent work. request-unauthorised-author
- Routing
- not triaged yet → decision
REFUSED - Human finding
- —
- Fix
- —
- Plan revision
- —
- Bound PR / SHA
- — @ —
- Triage RAG context
- not collected
Your request was not accepted.
The request text was empty. request-empty
- Routing
- not triaged yet → decision
REFUSED - Human finding
- —
- Fix
- —
- Plan revision
- —
- Bound PR / SHA
- — @ —
- Triage RAG context
- not collected
Your request was not accepted.
The request did not come from a person allowed to request agent work. request-unauthorised-author
- Routing
- not triaged yet → decision
REFUSED - Human finding
- —
- Fix
- —
- Plan revision
- —
- Bound PR / SHA
- — @ —
- Triage RAG context
- not collected
Your request was not accepted.
remove kind from the composition line The line's kind (RAW_MATERIAL / PREMIX) is redundant: whether premixFormulaId is set already tells you what the line is. Remove it everywhere it describes a line. Frontend (frontend/src/domains/formulation/features/formulas/) * Remove kind from FormulaLine and PremixIngredientNode in types/index.ts, and remove the FormulaLineKind type. * Remove it from the mock layer (api/formula-composition.ts, api/formula-premix.ts, lib/mock-premix-data.ts), lib/composition-math.ts, and description/hooks/use-formula-composition.ts. * Replace every row.kind === "PREMIX" check with row.premixFormulaId !== null, and every RAW_MATERIAL check with row.premixFormulaId === null. * Update the TODO(MP-691) markers that list kind in the future request or response body. Backend (this is already on main, so it changes a merged API response) * Remove kind from FormulaLineResponse. * Remove LINE_KIND_RAW_MATERIAL from FormulaCompositionMapper. * Update FormulaCompositionServiceTest, which asserts kind() == "RAW_MATERIAL". DB design doc (docs/superpowers/specs/2026-10-13-MP-691-db-schema.md) * There is no kind column, and that stays. * Remove the §2 row and the migration comment that say kind is "derived on read". Reference rule: a line references rawMaterialId or premixFormulaId, never both. An empty line, with neither, stays legal. This is chk_formula_phase_line_single_reference, already in the design. Don't add any new constraint. Excluded: the kind on search-suggestion candidates (RawMaterialCandidate & { kind: … } in types/index.ts). It's a picker label that's never saved, so leave it. Also leave the kind in lib/formula-status.ts, which is unrelated.
The pull request changed after the last verified step. request-stale-pr-head
- Routing
- not triaged yet → decision
REFUSED - Human finding
- —
- Fix
- —
- Plan revision
sha256:7a6dd27a36e2df264a63cb562a7304770dfc6e9411718ac2555b27c34314b42b· accepted planfeature-contract-4da80112ec4ee78cdd47b7b0adfd4337·cf0a69c8d24e- Bound PR / SHA
- #143 @
ddb518c70882 - Triage RAG context
- not collected
Accepted handoffs and binding
| When | Step | Result | Record |
|---|---|---|---|
| 2026-10-16 23:17Z | review (backend) | CLEAN | workflow-handoff-e618a68b44225fbfa9a16bd6b1b95394 |
| 2026-10-16 20:35Z | fix (backend) | ISSUES | workflow-handoff-aab824efb51245b925fa3839df42db6a |
| 2026-10-16 19:19Z | review (backend) | ISSUES | workflow-handoff-65076730aaecc5c7ad02265ce3f024c9 |
| 2026-10-16 18:36Z | build (backend) | CLEAN | workflow-handoff-3c937d797e609c73dfeacb38b28418d0 |
| 2026-10-16 16:46Z | plan (backend) | CLEAN | workflow-handoff-ea36e38f7ce6ce312b5c0e0ab30d8df6 |
| 2026-10-15 14:01Z | review (mock) | CLEAN | workflow-handoff-f316cf6009dbf6b3d711f627c3460022 |
| 2026-10-15 13:49Z | build (mock) | CLEAN | workflow-handoff-6caf3a41a050772df52101c5cd48ff07 |
| 2026-10-15 13:35Z | plan (mock) | CLEAN | workflow-handoff-8e18e15d851a4b49bd6950798af0483a |
| 2026-10-15 13:11Z | plan (db) | CLEAN | workflow-handoff-b0aa7425652ca2a3e23a5fe1a842c569 |
| 2026-10-13 16:37Z | design (db) | CLEAN | workflow-handoff-02b958cf7c2e360cefd3557a693faaef |
| 2026-10-13 15:40Z | plan (db) | CLEAN | workflow-handoff-4da80112ec4ee78cdd47b7b0adfd4337 |
- Current binding
workflow-handoff-e618a68b44225fbfa9a16bd6b1b95394·35c7549e8d36- Branch / last attempted commit
claude/mock-MP-691-premix-composition-line@9049c1e1dd19
Historical attempts
- Earlier stage Fix: Complete · 2026-10-07 22:00Z
- Earlier stage Review: Complete · clean · 2026-10-15 14:01Z
- History Handoff: Technically validated · Review · 2026-10-16 23:17Z
- History Handoff: Handoff not accepted · Review · 2026-10-16 22:43Z
- History Stop record: Technical failure, no work lost · Review · 2026-10-16 22:43Z
- History Stop record: Human answer needed · Review · 2026-10-16 22:04Z
- History Stop record: Human answer needed · Review · 2026-10-16 20:56Z
- History Handoff: Corrections requested · Review · 2026-10-16 20:35Z
- History Handoff: Corrections requested · Review · 2026-10-16 19:19Z
- History Handoff: Technically validated · Backend · 2026-10-16 18:36Z
- History Handoff: Technically validated · Plan · 2026-10-16 16:46Z
- History Handoff: Handoff not accepted · Plan · 2026-10-16 10:45Z
- History Stop record: Technical failure, no work lost · Plan · 2026-10-16 10:45Z
- History Handoff: Handoff not accepted · Plan · 2026-10-16 09:16Z
- History Stop record: Technical failure, no work lost · Plan · 2026-10-16 09:16Z
- History Handoff: Handoff not accepted · Plan · 2026-10-15 21:21Z
- History Stop record: Technical failure, no work lost · Plan · 2026-10-15 21:21Z
- History Handoff: Technically validated · Review · 2026-10-15 14:01Z
- History Handoff: Technically validated · Mock · 2026-10-15 13:49Z
- History Handoff: Technically validated · Plan · 2026-10-15 13:35Z
- History Handoff: Technically validated · Plan · 2026-10-15 13:11Z
- History Stop record: Decision or dependency required · Database · 2026-10-14 19:00Z
Raw records and logs
Raw
Raw records and logs are never loaded on this page. Open them on the technical views: