Skip to content
Agent Delivery Studio
  1. Plannerstate: Complete
  2. Backend Builderstate: Complete
  3. Reviewstate: Complete · clean
  4. Fixstate: Complete
  5. 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.

Current: Agent run on 2026-10-16 23:17Z (12 hours ago).

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)

You

Review needed

Ready for review — non-blocking recommendations are available.

Recommendations did not trigger an automatic Fixer.

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

  1. 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.sql
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/formula/FormulaPhaseLine.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/domain/formula/NotIncludedLevel.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLineRepository.java
  2. 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.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/mapper/formula/FormulaCompositionMapper.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/UpdateFormulaLineRequest.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/AddFormulaLineRequest.java
    • backend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionServiceTest.java
  3. 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.java
    • backend/applications/cosmoreg-core/src/main/resources/messages.properties
    • backend/applications/cosmoreg-core/src/main/resources/messages_fr.properties
  4. 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.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLineRepository.java
  5. 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.java
    • backend/applications/cosmoreg-core/src/main/resources/messages.properties
    • backend/applications/cosmoreg-core/src/main/resources/messages_fr.properties
  6. 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.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaService.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixCandidateResponse.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaRepository.java
  7. 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.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaCompositionService.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixTreeResponse.java
    • backend/applications/cosmoreg-core/src/main/java/com/chromasoftware/cosmoreg/core/dto/formula/PremixTreeNodeResponse.java
  8. 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.java
    • backend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/service/formula/FormulaServiceTest.java
    • backend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/repository/formula/FormulaPhaseLinePremixH2IT.java
    • backend/applications/cosmoreg-core/src/test/java/com/chromasoftware/cosmoreg/core/controller/formula/FormulaPremixCompositionIT.java
  9. 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.ts
    • frontend/src/domains/formulation/features/formulas/lib/mock-premix-data.ts
    • frontend/src/domains/formulation/features/formulas/api/formula-composition.ts
    • frontend/src/domains/formulation/features/formulas/description/hooks/use-formula-composition.ts
    • frontend/src/domains/formulation/features/formulas/description/hooks/use-formula-description.ts

Implementation

Latest accepted result
Technically validated · Review ·
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

From build report workflow-artifact-b8ecfed8f5cd7d07bc0c873b5271cb4145516136 · 2026-10-16 18:36Z · accepted by a handoff

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

GateStatusCommandReason
backend: FormulaPremixCompositionIT (17 tests, new, written for CI)Not executable herecd /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 buildNot executable herecd /work/backend && ./gradlew --no-daemon buildNo Docker in this pod; the full build runs the Testcontainers-bound IT layers. CI runs it.
backend: RawmaterialSiteCodeHistoryH2IT (20 tests)Not executable herecd /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.mdNot executable herecd /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 leftPassedgrep -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/Passedgrep -rn 'LINE_KIND\|String kind\|kind()' --include=*.java /work/backend/applications/cosmoreg-core/srcNo match: the component, the mapper constant and the test assertion are gone and nothing replaced them.
backend: compileJava compileTestJavaPassedcd /work/backend && ./gradlew --no-daemon compileJava compileTestJava—
backend: spotlessCheckPassedcd /work/backend && ./gradlew --no-daemon spotlessCheck—
backend: unit + H2 tests (1432 executed, 0 failed)Passedcd /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 clientPassedgit diff --staged --name-only 3830b223cba49f2cf0cbffc1547d46f031e0d747 | grep -E 'frontend/.*/api/.*\.ts$' | xargs -r grep -nE "from ['\"]@/shared/lib/api-client"—
frontend: npm run buildPassedcd /work/frontend && npm run buildCompiled successfully in 28.6s, 42/42 static pages.
frontend: npm run lintPassedcd /work/frontend && npm run lint13 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 filesPassedgrep -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.propertiesAll 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 mainPassedgit 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

Review cleanADVISORY 2MINOR 12026-10-16 23:17Z

Verified review commit 9049c1e1dd19

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

IdSeverityFileFinding
F1MINORbackend/applications/cosmoreg-[redacted].javawouldContainItself's javadoc still points at lib/premix-rules.ts#wouldCycle, which this same commit deleted
F2ADVISORYbackend/applications/cosmoreg-[redacted].javaheightAbove issues one query per ancestor formula, so saving a line on a widely reused pre-mix costs a query per daughter
F3ADVISORYbackend/applications/cosmoreg-[redacted].javaThe 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

Agent work request · Running2026-10-15 12:56Z · requested by danny_p

Agents are working on your request.

Human request text — verbatim from Jira (secrets masked)
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].java backend/applications/cosmoreg-[redacted].java frontend/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.tsx
  • frontend/src/domains/formulation/features/formulas/description/pages/formula-description-page.tsx
Routing
REPLAN → decision REPLAN
Human finding
—
Fix
—
Plan revision
sha256:7a6dd27a36e2df264a63cb562a7304770dfc6e9411718ac2555b27c34314b42b · accepted plan feature-contract-4da80112ec4ee78cdd47b7b0adfd4337 · cf0a69c8d24e
Bound PR / SHA
#143 @ 8a2f48e12979
Target stage
mock
Lifecycle
Running · 2026-10-15 12:56Z
Triage RAG context
revision 6ff9aee4994f · indexed 3830b223cba4 · freshness EXACT · 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.

Agent work request · Not accepted2026-10-15 12:56Z

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
Agent work request · Not accepted2026-10-15 12:53Z

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
Agent work request · Not accepted2026-10-15 12:53Z

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
Agent work request · Not accepted2026-10-15 12:51Z

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
Agent work request · Not accepted2026-10-14 18:41Z · requested by danny_p

Your request was not accepted.

Human request text — verbatim from Jira (secrets masked)
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 plan feature-contract-4da80112ec4ee78cdd47b7b0adfd4337 · cf0a69c8d24e
Bound PR / SHA
#143 @ ddb518c70882
Triage RAG context
not collected

Accepted handoffs and binding

WhenStepResultRecord
2026-10-16 23:17Zreview (backend)CLEANworkflow-handoff-e618a68b44225fbfa9a16bd6b1b95394
2026-10-16 20:35Zfix (backend)ISSUESworkflow-handoff-aab824efb51245b925fa3839df42db6a
2026-10-16 19:19Zreview (backend)ISSUESworkflow-handoff-65076730aaecc5c7ad02265ce3f024c9
2026-10-16 18:36Zbuild (backend)CLEANworkflow-handoff-3c937d797e609c73dfeacb38b28418d0
2026-10-16 16:46Zplan (backend)CLEANworkflow-handoff-ea36e38f7ce6ce312b5c0e0ab30d8df6
2026-10-15 14:01Zreview (mock)CLEANworkflow-handoff-f316cf6009dbf6b3d711f627c3460022
2026-10-15 13:49Zbuild (mock)CLEANworkflow-handoff-6caf3a41a050772df52101c5cd48ff07
2026-10-15 13:35Zplan (mock)CLEANworkflow-handoff-8e18e15d851a4b49bd6950798af0483a
2026-10-15 13:11Zplan (db)CLEANworkflow-handoff-b0aa7425652ca2a3e23a5fe1a842c569
2026-10-13 16:37Zdesign (db)CLEANworkflow-handoff-02b958cf7c2e360cefd3557a693faaef
2026-10-13 15:40Zplan (db)CLEANworkflow-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

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