Decision Log: Item Manufacturer Field
Purpose
Section titled “Purpose”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.
Decision Table
Section titled “Decision Table”| # | Question | Status | Decision | Round |
|---|---|---|---|---|
| DQ-001 | Where does find-or-create live? | Decided | Backend resolver (atomic), not BFF | R1 |
| DQ-002 | Fixed behavior or caller-selected mode? | Decided (revised R2) | Optional STRICT/PROPAGATE, default PROPAGATE; client always explicit; hard-required → PDEV-1107 | R1→R2 |
| DQ-003 | New ManufacturerReference or generalize? | Decided | Generalize → BusinessRoleReference (PDEV-875) | R1 |
| DQ-004 | Keep retired/rId/provenance + retired guard? | Decided | Kept | R1 |
| DQ-005 | Keep the role→item listener? | Decided | Required | R1 |
| DQ-006 | Manufacturer lookup endpoint | Decided | Reuse generic role-filtered lookup | R1 |
| DQ-007 | Frontend contract mode | Decided | Always reference; no probe | R1 |
| DQ-008 | Execution scope | Decided | FE+BE+printing; templates & bulk-import → follow-ups | R1 |
| DQ-009 | PDEV-875 handling | Decided | Inline as Phase 1 T-01; close it | R1 |
| DQ-010 | Is role a stored column? | Decided | No — per-slot parameter; no migration/backfill | R2 |
| DQ-011 | BFF / shared / SPA code separation | Decided | Formalized convention; codified in workspace FE skill + agent | R2 |
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).
| Option | Description | Trade-offs |
|---|---|---|
| A | Backend resolver, inside the item write transaction (mirrors ItemVendorResolver) | Atomic; symmetric with supplier; reuses role-parameterized service methods. Adds a small resolver. |
| B | BFF choreography (lookup → create → PUT), strict backend rejects unlinked refs | Backend 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.
| Option | Description | Trade-offs |
|---|---|---|
| A | Fixed PROPAGATE (like vendor today) | Simplest; no strict option for callers that want to guarantee linkage. |
| B | Required STRICT/PROPAGATE parameter, no default | Explicit intent at the API boundary; reintroduces STRICT on the shared resolver (touches vendor signatures). |
| C | Optional STRICT/PROPAGATE, default PROPAGATE; client always sends it explicitly | Backward-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 SupplierReference → BusinessRoleReference.
| Option | Description | Trade-offs |
|---|---|---|
| A | Add a parallel ManufacturerReference | Smaller diff now; leaves two near-identical types; defers PDEV-875. |
| B | Generalize 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.
| Option | Description | Trade-offs |
|---|---|---|
| A | Drop denormalization; name-only reference | Smaller; but stale names on affiliate rename, no retirement tracking, diverges from ItemSupply. |
| B | Keep full denormalization + retired guard | Parity 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.
DQ-005: Keep the role→item listener?
Section titled “DQ-005: Keep the role→item listener?”Context: When a manufacturer role is renamed/retired, linked items hold a denormalized copy.
| Option | Description | Trade-offs |
|---|---|---|
| A | Skip the listener in v1 | Less code; stale item names if a role changes; inconsistent with supplier. |
| B | Implement 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.
DQ-006: Manufacturer lookup endpoint
Section titled “DQ-006: Manufacturer lookup endpoint”Context: The typeahead and filter need known manufacturers for the tenant.
| Option | Description | Trade-offs |
|---|---|---|
| A | Dedicated lookup/manufacturers canonical endpoint | Symmetric with vendors; more backend code. |
| B | Reuse the generic …/business-affiliate/lookup?role=MANUFACTURER | Already 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.
DQ-007: Frontend contract mode
Section titled “DQ-007: Frontend contract mode”Context: The supplier FE runs a runtime legacy/reference probe (a migration shim for when
supplier changed string→reference).
| Option | Description | Trade-offs |
|---|---|---|
| A | Reuse/add a capability probe | Robust across mismatched deploys; extra machinery for a net-new field. |
| B | Always reference; no probe | Net-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.
DQ-008: Execution scope
Section titled “DQ-008: Execution scope”Context: PDEV-664 spans backend, frontend, printing, print-templates, and bulk-import.
| Option | Description | Trade-offs |
|---|---|---|
| A | Everything in one project | Large; couples repos with different release cadences. |
| B | FE + BE + printing (incl. Kanban) now; templates & bulk-import as follow-ups | Ships 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.
DQ-009: PDEV-875 handling
Section titled “DQ-009: PDEV-875 handling”Context: DQ-003 requires the BusinessRoleReference generalization, which is its own backlog
ticket (PDEV-875).
| Option | Description | Trade-offs |
|---|---|---|
| A | Coordinate PDEV-875 as a separate prior PR | Extra coordination; blocks this project. |
| B | Execute PDEV-875 inline as this project’s first backend task; close it here | Single 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.
| Option | Description | Trade-offs |
|---|---|---|
| A | Persist role on the reference (new column) | Flexible (one owner could hold mixed-role refs); needs migration + backfill + wire change; contradicts PDEV-875. |
| B | role is a per-slot parameter (constant per embedding site), not persisted/serialized | No 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_ref ⇒
VENDOR, manufacturer_ref ⇒ MANUFACTURER); 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.
| Option | Description | Trade-offs |
|---|---|---|
| A | Leave as-is; place manufacturer code ad hoc | Fast; perpetuates the mixed src/lib/ bucket and leak risk. |
| B | Formalize a three-bucket convention (BFF-exclusive / shared / SPA-exclusive) and codify it | Clear 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/**(thinroute.ts), server logic undersrc/server/**, secret/JWT/env libs — all guarded byimport 'server-only'. Never import Redux or browser fetchers. - Shared (BFF + SPA): route payload types + DTOs in
src/types/**; pure utilities (mappers, normalizers) insrc/lib/shared/**. Noserver-only/client-onlydeps. - 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 thefront-end-engineeragent profile and theclean-componentsskill inworkspace. Broader hardening (addserver-onlytoenv.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
Copyright: © Arda Systems 2025-2026, All rights reserved