Skip to content

Decision Log: Item Manufacturer Field

Tracks the design decisions for the Item Manufacturer field (PDEV-664): how a manufacturer is modeled and resolved, how the supplier reference is generalized to serve it, what consistency machinery is kept, and the project scope. Round 1 was resolved during the workbook analysis review (see workbooks/notebooks/manufacturer-field/ba-manufacturer-changes-analysis.md); it is transcribed here as the canonical record.

#QuestionStatusDecisionRound
DQ-001Where does find-or-create live?DecidedBackend resolver (atomic), not BFFR1
DQ-002Fixed behavior or caller-selected mode?Decided (revised R2)Optional STRICT/PROPAGATE, default PROPAGATE; client always explicit; hard-required → PDEV-1107R1→R2
DQ-003New ManufacturerReference or generalize?DecidedGeneralize → BusinessRoleReference (PDEV-875)R1
DQ-004Keep retired/rId/provenance + retired guard?DecidedKeptR1
DQ-005Keep the role→item listener?DecidedRequiredR1
DQ-006Manufacturer lookup endpointDecidedReuse generic role-filtered lookupR1
DQ-007Frontend contract modeDecidedAlways reference; no probeR1
DQ-008Execution scopeDecidedFE+BE+printing; templates & bulk-import → follow-upsR1
DQ-009PDEV-875 handlingDecidedInline as Phase 1 T-01; close itR1
DQ-010Is role a stored column?DecidedNo — per-slot parameter; no migration/backfillR2
DQ-011BFF / shared / SPA code separationDecidedFormalized convention; codified in workspace FE skill + agentR2

Round 1: Analysis Review (resolved 2026-07-08)

Section titled “Round 1: Analysis Review (resolved 2026-07-08)”

DQ-001: Where does the find-or-create choreography live?

Section titled “DQ-001: Where does the find-or-create choreography live?”

Context: When an item is saved with a manufacturer name, something must link it to a real BusinessAffiliate + MANUFACTURER role (or create them). This can happen in the backend (inside the write transaction) or in the frontend BFF (multiple calls).

OptionDescriptionTrade-offs
ABackend resolver, inside the item write transaction (mirrors ItemVendorResolver)Atomic; symmetric with supplier; reuses role-parameterized service methods. Adds a small resolver.
BBFF choreography (lookup → create → PUT), strict backend rejects unlinked refsBackend stays validate-only, but non-atomic (orphaned affiliates on partial failure); diverges from supplier; first item BFF route to do multi-call choreography.

Recommendation: Option A — atomicity and symmetry with the shipped supplier path.

Decision: Option A, with the mode refinement in DQ-002.

Applied to: Design § Overview, § Behavioral Design; Project Plan Phase 1.


DQ-002: Fixed resolution behavior or caller-selected?

Section titled “DQ-002: Fixed resolution behavior or caller-selected?”

Context: The vendor resolver is PROPAGATE-only (STRICT/LAX were removed under ⚠Q9). Manufacturer callers may want either strict linking or find-or-create.

OptionDescriptionTrade-offs
AFixed PROPAGATE (like vendor today)Simplest; no strict option for callers that want to guarantee linkage.
BRequired STRICT/PROPAGATE parameter, no defaultExplicit intent at the API boundary; reintroduces STRICT on the shared resolver (touches vendor signatures).
COptional STRICT/PROPAGATE, default PROPAGATE; client always sends it explicitlyBackward-compatible on a shared, already-live endpoint; explicitness enforced in our stack, not the wire contract.

Recommendation: Option B — make the intent explicit; PROPAGATE remains the vendor callers’ choice.

Decision (R1): Option B — required, no default.

Revised decision (R2): Option C. R1’s “required, no default” is a breaking change to the shared item-write endpoint: a missing required parameter is rejected (unlike an unknown extra field, which the backend ignores via ignoreUnknownKeys = true), so it would 400 every not-yet-upgraded caller (current frontend, api-test, future bulk-import) and would break independent backend-before-frontend deploys. Instead: the backend flag is optional and defaults to PROPAGATE (today’s behavior); the Arda frontend/BFF always sends an explicit mode, so intent stays explicit where it matters. The hard requirement is deferred to PDEV-1107, to be applied after all callers send the flag and a soak period confirms none rely on the default. See DQ-002 discussion in the session (C3).

Applied to: Design § Decision Summary, § API Contract, § resolver, § Testing; Project Plan Phase 1 T-06, Phase 3 (deploy guard + Follow-ups); PDEV-1107.


DQ-003: New ManufacturerReference or generalize SupplierReference?

Section titled “DQ-003: New ManufacturerReference or generalize SupplierReference?”

Context: Manufacturer needs a reference value object of the same shape as the supplier’s. PDEV-875 already calls for generalizing SupplierReferenceBusinessRoleReference.

OptionDescriptionTrade-offs
AAdd a parallel ManufacturerReferenceSmaller diff now; leaves two near-identical types; defers PDEV-875.
BGeneralize to BusinessRoleReference with a role field (execute PDEV-875)One type for all roles; ~46-file rename sweep; preserves supplier wire shape.

Recommendation: Option B — do it once, correctly; PDEV-875 is the mandated direction.

Decision: Option B — execute PDEV-875 inline (see DQ-009).

Applied to: Design § Class Diagram, § Key Classes; Project Plan Phase 1 T-01.


DQ-004: Keep the denormalized fields and the retired guard?

Section titled “DQ-004: Keep the denormalized fields and the retired guard?”

Context: An earlier draft proposed dropping retired/rId/provenance for manufacturer.

OptionDescriptionTrade-offs
ADrop denormalization; name-only referenceSmaller; but stale names on affiliate rename, no retirement tracking, diverges from ItemSupply.
BKeep full denormalization + retired guardParity with supplier; supports the follow-up management screens; consistent via the listener.

Recommendation: Option B.

Decision: Option B — deleted/retired affiliate behavior must follow the current ItemSupply behavior exactly. The denormalized fields are needed for the follow-up management screens and for system consistency.

Retirement granularity (confirmed): retirement is per role when a BusinessRole retires (it blocks only that role) and across all roles when the BusinessAffiliate retires. So the manufacturer PROPAGATE guard may attach a MANUFACTURER role to a live affiliate even if that affiliate’s VENDOR role is retired, but refuses to attach to (or resurrect) a retired affiliate.

Applied to: Design § Class Diagram, § resolver; Project Plan Phase 1 T-03, T-04.


Context: When a manufacturer role is renamed/retired, linked items hold a denormalized copy.

OptionDescriptionTrade-offs
ASkip the listener in v1Less code; stale item names if a role changes; inconsistent with supplier.
BImplement the listener (renamed from “reverse cascade”)Keeps Item.manufacturer.name consistent unless the item is deleted/retired; needs an item-side findItemsByManufacturerRole query.

Recommendation: Option B.

Decision: Option B — required, not optional. It must update Item.manufacturer.name (and mark retired on delete) unless the item itself is deleted/retired.

Applied to: Design § listener, § second sequence diagram; Project Plan Phase 1 T-05.


Context: The typeahead and filter need known manufacturers for the tenant.

OptionDescriptionTrade-offs
ADedicated lookup/manufacturers canonical endpointSymmetric with vendors; more backend code.
BReuse the generic …/business-affiliate/lookup?role=MANUFACTURERAlready parses role; less code; slightly different result shape the FE adapter maps.

Recommendation: A or B both viable.

Decision: Option B — reuse the generic role-filtered lookup.

Applied to: Design § API Contract; Project Plan Phase 1 T-04, Phase 2 T-08.


Context: The supplier FE runs a runtime legacy/reference probe (a migration shim for when supplier changed string→reference).

OptionDescriptionTrade-offs
AReuse/add a capability probeRobust across mismatched deploys; extra machinery for a net-new field.
BAlways reference; no probeNet-new field, no legacy contract exists; UI relies on released behavior; simplest.

Recommendation: B.

Decision: Option B — manufacturer is net-new; no backward-compat check. The FE always sends the BusinessRoleReference object.

Applied to: Design § API Contract; Project Plan Phase 2 T-08.


Context: PDEV-664 spans backend, frontend, printing, print-templates, and bulk-import.

OptionDescriptionTrade-offs
AEverything in one projectLarge; couples repos with different release cadences.
BFE + BE + printing (incl. Kanban) now; templates & bulk-import as follow-upsShips the user-visible feature + data delivery; defers template layout and importer with concrete specs.

Recommendation: B.

Decision: Option B. Printing = operations-side payload delivery (card/Kanban/label/ breadcrumb). Print-template testData.json/placeholders and bulk-import are follow-up tickets; the bulk-import ticket is created after the backend is complete so it carries concrete specifics.

Applied to: Goal § Scope; Project Plan Phase 3, § Follow-ups.


Context: DQ-003 requires the BusinessRoleReference generalization, which is its own backlog ticket (PDEV-875).

OptionDescriptionTrade-offs
ACoordinate PDEV-875 as a separate prior PRExtra coordination; blocks this project.
BExecute PDEV-875 inline as this project’s first backend task; close it hereSingle coherent operations PR; this project owns the rename sweep.

Recommendation: B.

Decision: Option B — inline as Phase 1 T-01; the operations PR closes PDEV-875.

Applied to: Goal § Linear Tickets; Project Plan Phase 1 T-01, § Release.


Round 2: Review Follow-ups (resolved 2026-07-08)

Section titled “Round 2: Review Follow-ups (resolved 2026-07-08)”

Questions surfaced while reviewing the project plan.

DQ-010: Does the generalized reference persist role as a column?

Section titled “DQ-010: Does the generalized reference persist role as a column?”

Context: DQ-003 adds role to BusinessRoleReference. If role is a stored/serialized field, T-01 would need a migration to add a <prefix>_role column and a backfill of existing item_supply rows to VENDOR — and the wire JSON would change, contradicting PDEV-875’s “JSON shape unchanged” invariant.

OptionDescriptionTrade-offs
APersist role on the reference (new column)Flexible (one owner could hold mixed-role refs); needs migration + backfill + wire change; contradicts PDEV-875.
Brole is a per-slot parameter (constant per embedding site), not persisted/serializedNo column, no migration, no backfill; wire shape preserved; reads single-query. Role is known from the slot (supplier_ref⇒VENDOR, manufacturer_ref⇒MANUFACTURER).

Recommendation: Option B. Verified against SupplierReferenceComponent (no role column today) and PDEV-875 (JSON shape stays {name, eId, affiliateEId, rId, retired, provenance}). The authoritative role already lives on the business_role table; storing it on every reference is redundant. Read performance is identical either way, so B is strictly leaner.

Decision: Option B — no migration, no backfill. Sub-form (a): role is present on BusinessRoleReference.Value as a non-serialized, non-persisted field (excluded from the JSON / @Transient), populated per slot at the deserialize/component boundary (supplier_refVENDOR, manufacturer_refMANUFACTURER); the resolver and listener read it from the VO. The field must carry a clear code comment stating it is not persisted and how its value is populated. No role column; wire JSON unchanged.

Applied to: Design § Class Diagram (note), § Key Classes, § narrative; Project Plan Phase 1 T-01 note + acceptance.


DQ-011: Where do BFF-exclusive / shared / SPA-exclusive frontend modules live?

Section titled “DQ-011: Where do BFF-exclusive / shared / SPA-exclusive frontend modules live?”

Context: arda-frontend-app (Next.js) mixes server-only BFF code and client SPA code in one package. Investigation found the bones of a separation (src/server/** guarded by server-only, a src/lib/shared/** bucket) applied inconsistently — notably src/lib/env.ts (holds ARDA_API_KEY) lacks a server-only guard. A convention is needed so future work follows it.

OptionDescriptionTrade-offs
ALeave as-is; place manufacturer code ad hocFast; perpetuates the mixed src/lib/ bucket and leak risk.
BFormalize a three-bucket convention (BFF-exclusive / shared / SPA-exclusive) and codify itClear placement for this and future work; small upfront documentation cost.

Recommendation: Option B.

Decision: Option B. Convention:

  • BFF-exclusive (server-only): route handlers under src/app/api/** (thin route.ts), server logic under src/server/**, secret/JWT/env libs — all guarded by import 'server-only'. Never import Redux or browser fetchers.
  • Shared (BFF + SPA): route payload types + DTOs in src/types/**; pure utilities (mappers, normalizers) in src/lib/shared/**. No server-only/client-only deps.
  • SPA-exclusive (client): React components, Redux (src/store/**), browser client fetchers (ardaClient, lookup adapters); client entry points marked "use client". The rule is codified in the front-end-engineer agent profile and the clean-components skill in workspace. Broader hardening (add server-only to env.ts; an import-boundary lint rule) is noted as a separate improvement, not folded into this feature.

Applied to: Project Plan Phase 2 (code-placement note + T-08); workspace/instructions/claude/agents/front-end-engineer.md; workspace/instructions/claude/skills/clean-components/SKILL.md.



Copyright: (c) Arda Systems 2025-2026, All rights reserved