Unify CPQ Agent and Admin V3 catalog writes

DEA-6655 moves product and custom-attribute writes onto shared draft-write seams so the Config Agent and Admin V3 UI stop defining catalog persistence differently.

PR #6524 Author @mehulshinde Status open Branch claude/admin-agent-v3-catalog-unify-dc535c Files 12 Diff +563 / -157 Area Dealops 3 Admin + Config Agent

What it adds

A new shared dealops3/attributes/write.ts module validates and assembles org custom-attribute documents.

It includes 12 new unit tests covering add, remove, reserved keys, duplicates, category checks, and system-key collisions.

What it changes

The Config Agent now calls the same catalogProduct/write.ts product payload builder and draft upsert helper used by Admin-facing write code.

Attribute writes are unified for both tRPC publishing and agent draft staging.

What it preserves

The shared seam is intentionally publish-agnostic.

Admin V3 still publishes immediately; the agent still stages drafts for review, approval, receipts, and later publish.

Verification

clean pnpm run typecheck for apps/server.

341 pass full Config Agent Jest suite, plus 12 pass new attribute write specs.

Config Agent
Admin V3 tRPC
Shared write layer
Draft / versioning store
Catalog product rows
Attribute schema

1. Why this exists

Before

Config Agent persisted catalog changes through near-duplicate private writers.

  • Private product writeData.
  • Private upsertDraftRow.
  • Inline bulk catalogProduct.upsert.
  • Weaker custom-attribute validation than the Admin Attributes UI.
After

Both surfaces share the definition of a valid catalog write.

  • Product writes use exported writeData and upsertDraftProductWith.
  • Attribute writes use attributes/write.ts.
  • Each caller keeps its own publish policy.
Design point: the agent is not routed through Admin V3 tRPC handlers because those handlers publish immediately through flows like publishManualSkuChange or saveSpecVersion + createGlobalVersion. The PR shares the lower draft-write layer instead.

2. What changes

Config Agent stages

stageCreateCatalogProduct

stageUpdateCatalogProduct

stageDeleteCatalogProduct

bulkStageCatalogProducts

Shared write seam

writeData

upsertDraftProductWith

Draft catalog row

catalogProduct row in state: "draft".

Receipts hash the same payload that is written.

File Meaningful change Reviewer read
dealops3/catalogProduct/write.ts Exports writeData and documents why the agent needs it. This is the product payload single source of truth.
configAgent/catalogProductContext.ts Deletes private writeData, private upsertDraftRow, and inline bulk upsert. All create, update, delete, and bulk staging now use the shared helper.
configAgent/tools/catalogProductTableTools.ts Attribute tools switch to shared validation and document assembly. Agent now rejects the same invalid custom attributes as Admin V3.
Fingerprint stability

The agent verification path remains stable because validate-time plans, persisted-row receipts from readStagedCatalogWriteFingerprints, and the actual draft write all hash the same shared writeData shape.

The PR notes canonicalized keys and concrete pricebookId values keep the column payload byte-identical.

3. How it works

Product staging

Agent plan builders still compute identity, content, deletion state, and pricebookId.

Persistence now funnels through:

upsertDraftProductWith(
  ctx.prisma,
  ctx.orgId,
  ctx.userId,
  plan.skuId,
  plan.identity,
  plan.content,
  false,
  plan.pricebookId,
)
Attribute add

The agent creates the proposed AttributeDef, merges it purely, validates the full list, then stages a draft.

const doc = await buildOrgAttributesDoc(
  orgSlug,
  withAddedCustomAttribute(orgData, newAttr).attributes,
);
await saveDraft(orgSlug, userId, 'attributes', doc);
Attribute delete

Deletion only removes custom attributes. A system attribute with the same key is treated as not removable.

const { doc, removed } =
  withRemovedCustomAttribute(orgData, key);

if (!removed) {
  return { success: false, error: 'not found' };
}
Surface Shared code used What happens after shared write assembly
Admin V3 Attributes tRPC buildOrgAttributesDoc Saves spec version, creates global version, deletes draft, returns merged system + custom schema.
Config Agent attribute tools withAddedCustomAttribute, withRemovedCustomAttribute, buildOrgAttributesDoc Saves attributes draft for review and approval.
Config Agent product tools writeData, upsertDraftProductWith Upserts draft catalogProduct rows and keeps receipt fingerprints aligned.

4. What it doesn't change

Explicit non-goals

5. Risks / rollback / open questions

Main review risk: the shared attribute builder calls loadOrgAttributes to stamp orgId and orgName, and falls back to the org slug if that read fails. That preserves existing handler behavior, but reviewers should confirm this fallback is acceptable for agent draft writes too.
Rollback shape

Product rollback is straightforward: restore the agent-local writer and inline bulk upsert, then unexport writeData if no longer needed.

Attribute rollback means moving validation back into updateAttributeSchema.ts and restoring the agent's weaker checks.

Reviewer cleanup option

The two local dev helper scripts are additive but unrelated to DEA-6655.

If the PR should stay narrowly scoped, they can be dropped without affecting the write unification.

Validation confidence

Existing Config Agent catalog verification specs pass unchanged, including exact readback and fingerprint assertions.

New focused tests cover the shared attribute write module directly.