This is the third and final article in my experiment with Codex and Claude as an autonomous engineering pair.
In Part 1, I described the mechanics: Codex as the primary engineer, Claude Opus as a persistent read-only reviewer, one Git branch, one visible terminal session, and no human clipboard carrying messages between the agents. In Part 2, I followed the work itself: the architecture and implementation of a material-cost service, a compatibility facade for the existing application, and a one-way product-catalogue contract for an external quote system.
This final post opens the collaboration's most important artefact: the engineering ledger.
The thesis: autonomous AI collaboration became useful when conversation was turned into a durable engineering protocol. The ledger gave the agents memory, boundaries, evidence, disagreement, closure and a controlled route back to the human decision owner.
I have included the complete public-redacted ledger at the end of this post. It covers every recorded design turn, implementation review, correction, approval and exact reviewer prompt. Before we get there, this tutorial explains how to read it, how to reproduce the pattern, and what I would change next time.
A chat transcript is not an engineering ledger
A raw model transcript answers, “What tokens did the agents exchange?” An engineering ledger answers a more useful set of questions:
- What decision were we trying to close?
- What evidence did each agent inspect?
- Where did they disagree?
- Whose position moved, and why?
- What remained a genuine business decision for the human owner?
- What code or contract changed as a result?
- Which observable invariant proved the implementation?
- Who acts next?
That difference matters. Raw transcripts are chronological but weakly structured. They contain tool noise, repeated context, local machine metadata and partial thoughts. A ledger is curated without becoming fictional: it keeps the prompt, argument, evidence, correction, verdict and next actor while leaving raw runtime exhaust out of source control.
| Raw AI transcript | Engineering ledger |
|---|---|
| Ordered by message time | Ordered by stable decision ID |
| Contains every tool event | Records evidence and conclusions that affect the work |
| Implicit ownership | Names the implementing agent, reviewer and human authority |
| Agreement may be conversational | Agreement uses an explicit verdict and closure statement |
| Context disappears with the session | The repository retains the reasoning beside the implementation |
| Difficult to publish safely | Supports a deliberate redacted public edition |
The seven fields that made the ledger work
The ledger did not begin with architecture. It began with a collaboration contract. Seven fields repeatedly turned open-ended AI discussion into engineering progress.
| Ledger element | Why it matters | Example from the experiment |
|---|---|---|
| Stable discussion ID | Creates an addressable unit of work that survives long sessions and multiple prompts. | D-01 defined the minimum domain boundary; D-24 closed transport security. |
| Bounded question | Prevents the reviewer from redesigning the entire platform during every turn. | Review the cost-result vocabulary and storage boundary, not “design pricing.” |
| Evidence obligation | Forces both agents to inspect real producers, consumers and tests before expressing confidence. | Claude traced procurement evidence grain; Codex later corrected Claude using the actual Product Master writers. |
| Position movement | Makes changing one's mind a success condition rather than a loss. | The preferred cost precedence changed when the human owner clarified the business rule and the data history was inspected. |
| Verdict syntax | Separates approval, requested correction and owner escalation. | APPROVE, REQUEST CORRECTION, or OWNER DECISION REQUIRED. |
| Next actor | Prevents two agents waiting, duplicating work or assuming the other will close the loop. | Every substantive turn ended with Codex, Claude or Human Owner. |
| Observable acceptance gate | Connects prose to software behaviour. | The reviewer required result-level assertions, realistic payloads and exact retry semantics—not merely green unit tests. |
The “next actor” line looks almost trivial. It was not. In human teams, conversational cues and meetings supply turn-taking. Autonomous agents need the state written down.
How a decision moved through the ledger
The collaboration repeatedly followed the same loop: propose, challenge, inspect evidence, correct, verify, close. The loop was not rigid; it was a default rhythm that made departures visible.
1. Codex framed a bounded engineering decision
[Codex, D-01] “The strongest current design choice is to avoid replacing one ambiguous
unitCostwith another. The service should preserve evidence meanings, apply deterministic purpose-specific policy, and let a narrow compatibility facade feed legacy consumers safely.”
The prompt did not ask Claude for general thoughts. It named the current position, the constraints, the files to inspect, the questions to answer, and the acceptable verdicts.
2. Claude attacked an assumption with repository evidence
[Claude, D-01] “The procurement price history is supplier-plus-material grain. A component cost is material-level and can map to several supplier keys. If the core silently takes the first priced row, it picks an arbitrary supplier.”
This was genuine reviewer value. It was not stylistic advice. The proposed abstraction was coarser than the stored evidence, and a naive adapter could have made a financially meaningful arbitrary selection.
3. The agents made position movement explicit
[Claude] “I initially expected the first slice to include a shadow endpoint. Your zero-route proposal is safer during the soak, so I now agree to defer the endpoint.”
Recording movement creates trust. It lets a later reader distinguish consensus reached through evidence from two agents who started in agreement.
4. The human owner settled policy—not implementation detail
The longest design debate concerned which cost observation should become the canonical business reference. Procurement evidence was attractive because it described purchases. ERP Standard and Moving Average valuation were attractive because they expressed the organisation's governed accounting basis. Historical data did not always carry the newest control fields.
The agents could map the facts and consequences, but they could not invent the business policy. I settled it:
[Human Owner] Use the ERP-controlled valuation when present. Preserve the historically governed sales-derived catalogue cost when the newer control evidence does not exist. Keep procurement and stock evidence as labelled analytics rather than competing defaults.
The ledger then required Codex and Claude to restate that decision as deterministic precedence, fallback and refusal rules. Human authority entered once, at the correct abstraction level, and became executable policy.
5. Implementation returned to the same decision ID
When Codex implemented a slice, Claude did not review “the codebase.” It reviewed the bounded diff against the previously closed contract. The ledger recorded the tests, realistic harness evidence, remaining observations and a single verdict.
This is the key bridge between architecture and delivery: the same record that explained why the abstraction existed later defined what the reviewer had to prove.
Five moments where the ledger changed the outcome
1. It prevented a second ambiguous cost column
The original symptom could have been fixed by copying a newer number into Product Master. The ledger kept returning to provenance, control basis, timestamps, unit of measure, comparators and explicit status. That pressure produced a typed result instead of another scalar.
CostObservation {
source, method, amount, currency, uom,
effectiveAt, observedAt, publishedAt,
confidence, warnings[]
}
MaterialCostResult {
materialNumber, purpose,
selected, options[], comparators[],
status, coverage, recipe
}
The important move was not adding fields. It was making missing_price, missing_recipe, ambiguous_uom and not_applicable honest domain outcomes rather than converting uncertainty into zero.
2. It kept the old application stable through a facade
The host application already had many consumers of unitCost. The ledger repeatedly enforced “compatibility at the edge, not in the core.” The rich result remained clean, while a refusal-tested projector supplied the legacy shape only when the selected result was eligible.
That decision reduced rewrite cost and made gradual frontend adoption possible. It also let saved annual budgets remain snapshots while current ERP cost appeared beside them as reference evidence.
3. It separated material-cost applicability from catalogue selectability
Training, call-outs and other non-physical quote lines still belong in a product catalogue even though they have no governed material cost. An early implementation risked labelling them missing_price or treating a nominal value as real evidence.
The D-21 debate produced an explicit governance gate:
catalog_cost_plus_margin: publish separately labelled governed cost evidence.manual_case_by_case: keep the item selectable, suppress cost evidence, and make the quote system responsible for entering and approving the selling price.
The agents initially explored group names and numeric thresholds. The owner stopped that invention. The final rule lives in metadata, not string matching.
4. It exposed an operationally real—but correctly bounded—write risk
Claude's Product Master sweep found that stale full-document writes could overwrite newer additive metadata because the read and write paths did not share optimistic concurrency correctly. Codex accepted the core finding but corrected several over-broad assumptions by tracing each writer:
- Fiscal-year reinitialisation did not touch the business-unit-scoped Product Master.
- The normal data refresh preserved the new fields when uncontended.
- A freshly loaded admin save also preserved them.
- The genuine risk was a stale or overlapping full-document write, not every weekly operation.
This exchange is a strong example of independent review without review theatre. Claude found the structural concurrency gap. Codex narrowed the impact against real code and operational cadence. The final ledger distinguished durable hardening from an immediate production emergency.
5. It caught a subtle outbound security defect
The quote-catalogue publisher already required HTTPS, a dedicated bearer credential, immutable exact-byte staging, a content digest, idempotency and bounded retries. The design looked careful.
Claude still found a concrete gap: Node's Fetch implementation follows redirects by default. A 307 or 308 could forward the sensitive PUT body and bearer credential to a destination that had never passed the publisher's URL validation.
[Claude, D-24] Require
redirect: 'error'and a receiver contract that returns a final response directly.
Codex independently verified the behaviour, patched the sole outbound call, locked the setting on every retry attempt, updated the receiver contract and returned the bounded diff to Claude. Claude approved it. That small correction alone justified an independent security review.
Tutorial: build your own engineering ledger
Step 1: establish roles before discussing architecture
Pick one implementation owner. Give the reviewer read-only authority during design and review. Give the human owner explicit authority over business policy, risk acceptance, deployment and external side effects.
Primary engineer: owns edits, tests and synthesis
Independent reviewer: inspects evidence, challenges, never edits silently
Human owner: settles business/policy trade-offs and authorises mutations
Shared record: one version-controlled ledger
Execution boundary: one feature branch
Two agents with write access are not automatically faster. For this experiment, asymmetric authority made provenance obvious and prevented competing edits.
Step 2: bootstrap the reviewer with the repository contract
The original Claude prompt—preserved in the ledger appendix—specified the model, effort, permission mode, repository, governing documents, collaboration protocol and forbidden actions. It also told Claude how to format findings and when to escalate.
Do not begin with “review my plan.” Begin with “read these governing constraints, inspect these producer and consumer paths, answer these bounded questions, and use one of these verdicts.”
Step 3: give every unresolved decision an ID
### D-XX — Decision title
#### D-XX.1 — Primary engineer to reviewer
#### D-XX.2 — Reviewer to primary engineer
#### D-XX.3 — Correction, owner decision or closure
Do not recycle IDs. If a later implementation review reopens the subject, keep the relationship visible or create a new linked decision.
Step 4: require evidence before recommendations
The reviewer should inspect the actual data producer, storage shape, consumer and test boundary. The implementer must do the same when rebutting a finding. “I think” is allowed as a provisional hypothesis; it is not enough for closure when code can answer the question.
Step 5: make disagreement productive
Ask each agent to state:
- the strongest part of the other agent's position;
- the assumption it rejects;
- the evidence that would change its view;
- whether its position moved;
- the smallest remaining disagreement.
This discourages performative opposition and premature consensus at the same time.
Step 6: escalate only the irreducible human decision
Before escalating, both agents should exhaust read-only repository and production-shaped evidence. The escalation should contain options, consequences and a recommended default—but it must not disguise a business invention as a technical necessity.
Step 7: implement in reviewable slices
The material-cost work moved through additive slices: pure domain foundation, retained valuation projection, BOM roll-up, internal API, compatibility facade, frontend consumers, deterministic AI access, and an inactive external publisher. Each slice had its own rollback boundary and observable gate.
A reviewer can reason much better about one semantic boundary than a giant “feature complete” diff.
Step 8: preserve exact prompts that matter
The ledger summaries are useful, but the appendices preserve the exact bootstrap and review prompts. This makes the experiment reproducible and reveals how much of the review quality came from the prompt contract rather than model mystique.
Step 9: close with a verdict and next actor
[Claude]: APPROVE D-XX
Next actor: Codex
[Claude]: REQUEST CORRECTION D-XX
Next actor: Codex
[Claude]: BLOCK D-XX — OWNER DECISION REQUIRED
Next actor: Human Owner
If a turn has no closure syntax, assume the decision remains open.
Step 10: publish a redacted edition, not the working file
The public ledger below preserves the full discussion structure while generalising organisation identity, people, private deployment coordinates, repository paths that add no teaching value, commit/session identifiers, material codes, commercial amounts and production-scale counts. Stable aliases preserve reasoning across turns.
Redaction should be independently scanned. Search for names, email addresses, URLs, filesystem paths, credentials, service identifiers, customer names, material numbers, currency values, hashes and UUIDs. Then read the result as a human: mechanical replacement can leak meaning even when the string is gone.
What did not work—and what I would change
| Failure mode | What happened | Better rule |
|---|---|---|
| Ledger as message queue | The original file-watcher idea risked duplicate writes, races and unclear turn-taking. | Use a persistent agent session for transport; use Markdown for durable decisions. |
| Chronology drift | Some late decisions were appended after earlier sections and the numerical order became imperfect. | Maintain a small index with state, latest turn and links; periodically normalise structure without rewriting history. |
| Over-specific review prompts | Long prompts sometimes repeated repository rules and consumed context. | Keep a stable reviewer charter and send only decision-specific deltas. |
| Theoretical risk inflation | A structural issue can sound like an emergency before operational cadence and activation gates are considered. | Require separate likelihood, impact, current exposure and pre-activation hardening classifications. |
| Exact counts in the working record | Production evidence improved confidence but complicated public sharing. | Keep exact evidence privately; design stable aliases and redaction markers from the start. |
| Ledger growth | A complete record became large enough to intimidate a new reader. | Keep the full ledger, plus a decision register and short “current shared context” at the top. |
I would also add a machine-readable decision header in a future experiment:
id: D-24
state: closed
owner: codex
reviewer: claude
human_decision: false
implementation: complete
evidence: [focused-tests, contract-diff, reviewer-signoff]
supersedes: []
Markdown would remain the human record, but this header would support dashboards, unresolved-decision queries and automated review hand-offs without turning the ledger itself into a background job.
My reflection as the human decision owner
The experiment did not remove me from engineering. It removed me from message routing.
Codex and Claude were strongest when repository evidence could settle a question. They could trace data grain, compare both sides of an interface, enumerate writers, challenge a storage assumption, construct acceptance gates and review a real diff. They were appropriately weaker when the answer depended on how my organisation wanted to price, how much operational complexity I would accept, or when an external integration should be activated.
The ledger made that boundary visible. I could see exactly why an owner decision was requested. I could answer once. The agents then translated it into architecture, code, tests, documentation and review criteria.
That is a much more credible model of AI-assisted leadership than “the AI built everything.” The human supplied intent, business authority, operational judgement and risk ownership. The agents supplied sustained technical attention and independent scrutiny. The ledger supplied institutional memory.
My final lesson: the unit of autonomous AI engineering is not the prompt. It is the closed, evidence-backed decision.
The complete redacted engineering ledger
The transcript below is the complete public-redacted ledger, including D-01 through D-24 and the exact prompt appendices. It is intentionally long. Use the discussion IDs to navigate it, search for Position movement, OWNER DECISION REQUIRED, REQUEST CORRECTION and APPROVE, or read it sequentially to see the architecture evolve.
A standalone Markdown copy is included beside this blog source for reuse or separate hosting. The complete transcript is embedded directly below, so the published article does not depend on access to the private codebase.
Open the full redacted Codex-Claude engineering ledger
# Claude-Codex Collaboration: Material Cost Service — Public Redacted Ledger
Status: Collaboration closed; public redacted edition
> **Publication redaction note**
>
> This is a structurally complete public edition of the working engineering ledger. It preserves the discussion IDs, agent turns, prompts, disagreements, evidence-based corrections, owner decisions, implementation reviews, and closure statements. The following have been generalised or removed: organisation and person names, private service/deployment coordinates, repository-specific paths where they add no teaching value, commit/session identifiers, exact material and recipe identifiers, exact commercial amounts, exact production record/payload counts, customer context, and environment-specific operational details. Stable aliases are used so the reasoning remains traceable. Redaction does not imply that the omitted value was problematic; only that it was unnecessary for understanding the engineering method.
Started: August 2026
Coverage: D-01 through D-24 and Appendices A-J
Owner and escalation authority: Human Owner
Implementation branch: `[redacted: feature branch]`
Baseline commit: [redacted: commit identifier]
Origin baseline: [redacted: commit identifier]
## 1. Purpose
This document is the durable record of an engineering collaboration between Codex and Claude. The immediate goal is to agree and then implement a simple, auditable Material Cost Service that reuses the host BI platform's existing ERP evidence, avoids a broad application rewrite, preserves the current active API runtime platform API soak, and prepares a later outbound product-pricing publication to Quote CRM.
The collaboration is cooperative rather than competitive. Codex is the main implementing engineer. Claude is a peer co-architect during design and an expert reviewer during implementation. Both agents must challenge assumptions, cite repository evidence, change position when the evidence warrants it, and seek an owner decision only after a genuine unresolved trade-off remains.
## 2. Governing constraints
1. `[documentation]/agent-contract.md`, `[documentation]/app-rules.md`, `[documentation]/OverviewOfAppArchitecture.md`, and repository `AGENTS.md` govern all work.
2. No new ERP query, ERP report, or ETL workflow is introduced for the first implementation. Existing Sales, Stock, PO/GRN, Product Master, and BOM recipe data are reused.
3. Product Master owns catalog identity and taxonomy, not an independent cost ledger.
4. The first cost domain covers material cost of supply. Labour, routing, machine time, overhead absorption, and full manufacturing COGS remain out of scope.
5. Distinct ERP cost meanings remain distinct evidence. Missing, ambiguous, or UoM-incompatible evidence is never silently converted to zero.
6. Saved budget costs remain stable snapshots and do not silently rebase when current ERP evidence changes.
7. Internal consumers use a compatibility facade to minimize frontend and financial-model changes.
8. The feature targets the active API runtime `platform-api`. The legacy rollback runtime hot-standby artifact remains untouched during soak, but feature compatibility work for it is parked pending the owner-approved retirement/rebase boundary.
9. The eventual Quote CRM integration remains an outbound, immutable full-snapshot publication to a Quote CRM-owned ingestion API. It is not part of the first implementation slice unless separately authorized.
10. Claude's design session is read-only. Claude may inspect repository evidence but must not edit files, commit, push, deploy, or mutate cloud platform resources.
## 3. Collaboration protocol
- Each discussion has a stable identifier such as `D-01` and numbered turns.
- The speaking agent addresses the other agent directly and asks no more than three focused questions per turn.
- A response must:
1. restate the strongest part of the other agent's position;
2. answer the questions with repository evidence;
3. state whether its position changed;
4. identify remaining risk or disagreement;
5. propose a synthesis and the next question.
- A decision closes only after both agents write `AGREE`, or the owner resolves a documented deadlock.
- Before escalating a disagreement, both agents must complete at least two evidence-bearing exchanges and state the concrete consequence of each option.
- The collaboration ledger records curated prompts and responses. Private chain-of-thought is not requested or recorded. Terminal-visible prompts, tool activity, conclusions, diffs, tests, and decisions provide the audit trail.
## 4. Safety and execution protocol
- Design phase: Claude runs as `claude-opus-4-8`, high effort, persistent session, plan/read-only permissions.
- Implementation phase: Codex owns edits. Claude reviews committed or explicitly identified diffs and returns findings; it does not silently modify the implementation.
- Every implementation slice starts additive or shadow-only, has an explicit rollback boundary, and names the exact invariant exercised by its tests.
- No push, deployment, production mutation, or migration-state change occurs without the owner's explicit instruction.
## 5. Current shared context
The plan of record is `[documentation]/plans/material-cost-service-architecture.md`. It records:
- additive Sales Valuation Export ingestion of price control, Standard Price, and Moving Average Price is already deployed;
- the feature branch now implements the deterministic material-cost core, last-known projection, governed BOM roll-up, purpose-aware internal API, Product Master compatibility façade, frontend adoption, one deterministic native AI tool, and an inactive Quote CRM snapshot/store/publisher capability; production deployment, AI credential wiring, Quote CRM orchestration, and endpoint activation remain pending;
- the current production symptom arose from a stale single Product Master `unitCost`, but replacing that number with another single source would not provide a durable cost model;
- PO/GRN, Stock, Sales valuation, and BOM evidence carry different meanings that must remain explainable;
- the existing Stock Management Insights recipe matrix is the v1 BOM starting point, with explicit coverage and governance limitations;
- the dedicated active API runtime platform API is active and still soaking, while the legacy rollback runtime Function App remains hot standby.
## 6. Decision register
| ID | Decision | Status | Evidence/notes |
|---|---|---|---|
| D-01 | Minimum viable cost-domain boundary and canonical result contract | Agreed | Closed by `[Claude]: AGREE D-01` and `[Codex]: AGREE D-01` |
| D-02 | Evidence precedence and two-era fallback | Agreed and owner-refined | One canonical rule: S/V-controlled valuation, then governed Product Master legacy Sales-derived cost; PO/GRN/Stock are comparators |
| D-03 | Architecture close-out and implementation slicing | Agreed | Claude and Codex closed with no new owner escalation |
| D-04 | Storage/materialization boundary | Agreed | Compute through shared core; only the bounded last-known rich Sales valuation cache is persisted in v1 |
| D-05 | AI integration boundary | Agreed; implementation deferred | Deterministic read-only API tools; no LLM-owned financial arithmetic |
| D-06 | Quote CRM snapshot projector hand-off | Architecture agreed; contract deferred | Outbound immutable full snapshot after stable internal core; Quote CRM-team operational questions remain |
| D-21 | Non-physical service catalogue and manual quote-pricing contract | Implemented; Claude signed off | Closed by `[Claude]: AGREE D-21 CLARIFIED`, `[Codex]: AGREE D-21`, and `[Claude]: SIGN-OFF D-21 IMPLEMENTATION` |
## 7. Dialogue
### D-01 — Minimum viable cost-domain boundary and canonical result contract
#### D-01.1 — Codex to Claude
**[Codex]**
Claude, join this as peer co-architect. The strongest current design choice is to avoid replacing one ambiguous `unitCost` with another: the service should preserve ERP and procurement evidence meanings, apply deterministic purpose-specific policy, and let a narrow compatibility facade feed legacy consumers safely.
Before proposing implementation, inspect the governing documents, the material-cost plan, the Quote CRM contract, and the actual Product Master, Sales, Stock, PO/GRN, BOM, Budget Plan, and platform API paths. Challenge the plan against repository reality.
Questions:
1. What is the smallest canonical domain result and evidence vocabulary that can support direct materials, BOM roll-ups, budgeting, catalog display, and later Quote CRM publication without becoming an over-generalized cost platform?
2. Should v1 compute results on request from current source documents, materialize governed snapshots, or use a hybrid—and what existing repository patterns make that safest during the active API runtime/legacy rollback runtime soak?
3. What is the lowest-blast-radius first vertical slice that proves real value while leaving all current `unitCost` consumers unchanged by default?
Please identify any architectural assumption you reject, cite concrete files/functions, and propose acceptance criteria for the first slice. Do not edit the repository.
Next actor: Claude
#### D-21.4 — Claude to Codex
**[Claude]**
Claude accepted the clarified decision in full. It confirmed that exact Standard/Moving/controlled publication for catalogue-cost items follows the owner's settled rule and that `eligible` must be defined as structurally usable evidence rather than a default. It also agreed that manual mode must suppress every option/comparator regardless of amount and govern the internal catalogue as well as Quote CRM, leaving saved FY/customer values untouched.
Repository inspection confirmed that the Python Product Master ETL merge starts from the existing material and overlays only ERP-owned fields, so additive `costing.*` governance survives refresh without extending the ERP-field preservation helper. Claude agreed that the existing coverage audit is the right starting point for a dry-run-first reviewed mapping and activation-readiness gate.
The sole construction requirement is additive and agreed: add `not_applicable` to both internal and external item-status enums, apply the gate at the shared material-cost catalogue boundary, and ensure the façade treats it as non-applicable rather than applying or zero-filling a cost.
`[Claude]: AGREE D-21 CLARIFIED`
Next actor: Codex
#### D-21.2 — Claude to Codex
**[Claude]**
Claude agreed that Product Master governance—not names, prefixes, missing observations, or a numeric [redacted: nominal placeholder amount] threshold—must decide whether an item uses catalogue cost or manual quote pricing. It recommended `costing.quotePricingMode` as the single pricing gate, with optional `costing.itemKind` as orthogonal taxonomy. A `manual_case_by_case` item should remain selectable but publish `pricingStatus: not_applicable`, an empty `pricingOptions`, and explicit Quote CRM ownership; `missing_price` remains a genuine missing-evidence state for catalogue-cost items.
Claude also identified an important residual: `MAT-SERVICE-EXCEPTION-01` carries genuine V-controlled [redacted: commercial amount] evidence while its unused Standard field is [redacted: nominal placeholder amount]. Therefore a global value threshold or blanket Services-group suppression would destroy valid ERP evidence. It requested an explicit decision on how Standard/Moving alternatives behave for catalogue-cost items and whether the manual gate also governs internal catalogue projections.
`[Claude]: AGREE D-21` with the condition that option-level [redacted: nominal placeholder amount] semantics and current-data governance coverage be closed before the contract is shared.
Next actor: Codex
#### D-21.3 — Codex to Claude
**[Codex]**
I accept `costing.quotePricingMode` as the sole deterministic cost/quote gate and `costing.itemKind` as descriptive taxonomy. I also accept that no numeric amount—including [redacted: nominal placeholder amount]—can be globally reclassified as a placeholder.
The option-level residual is resolved by the owner's already-set rule rather than a new inference: for a governed `catalog_cost_plus_margin` item, host BI platform publishes ERP Standard, Moving Average, and S/V-controlled evidence separately and selects no quote default. Thus `MAT-SERVICE-EXCEPTION-01` retains [redacted: nominal placeholder amount] Standard and [redacted: commercial amount] Moving/controlled exactly as ERP reported them; `erpPriceControl: V` and the controlled option explain which valuation ERP applied. The contract must state that `eligible` means structurally usable evidence, not an instruction or default, and Quote CRM owns its versioned selection/approval policy. For a governed `manual_case_by_case` item, **all** cost options and comparators are suppressed, regardless of amount, because its quote price is case-specific.
The gate applies to the shared internal material-cost catalogue, not only the external projector. Internal consumers must receive `not_applicable`, no selected reference, and no live material-cost options for manual lines; saved FY/customer costs and overrides remain unchanged and are not silently rewritten.
The remaining implementation question is data governance, not pricing policy. I propose:
1. backward-compatible default `catalog_cost_plus_margin` when the field is absent;
2. explicit per-item Product Master administration for `manual_case_by_case` and optional `itemKind`;
3. an idempotent, dry-run-first migration/audit that produces the exact current candidate list and requires a reviewed explicit mapping before applying—no runtime name/group/value inference;
4. a publication-readiness gate proving every intended manual service is explicitly governed and every manual item projects `not_applicable` with zero options before activation.
Bounded decision statement: Product Master `costing.quotePricingMode` governs both internal and Quote CRM material-cost applicability; manual items remain selectable and Quote CRM-priced, publish no cost evidence, and are not missing; catalogue-cost items retain exact separately labelled ERP evidence without a host BI platform quote default; no numeric placeholder threshold exists; current classifications must pass a reviewed coverage gate before activation.
`[Codex]: AGREE D-21`
Questions:
1. Do you agree with this clarified bounded decision, including preservation of exact Standard/Moving evidence for catalogue-cost items and internal-catalog enforcement?
2. Is the dry-run-first explicit mapping plus activation-readiness gate the smallest safe migration mechanism, or does repository reality require a different bounded mechanism?
Next actor: Claude
#### D-01.2 — Claude to Codex
**[Claude]**
Codex — I read the four governing docs, both plan-of-record documents, the Quote CRM contract, and the fold-api plan, then traced the actual evidence readers, the recipe resolver, the legacy cost consumer, and the Product Master overlay. Here is my response.
**Strongest part of your position.** Your refusal to replace one ambiguous `unitCost` with another is correct, and the repository backs it harder than the prose does. The PO/GRN pipeline already emits *typed, provenance-carrying* evidence — `buildPriceEvents()` / `buildPriceStats()` in `[internal-api]/[operations]/procurement-sync.js:316-423` persist `latestUnitPrice`, `weightedAvgUnitPrice`, `min/maxUnitPrice`, `byFiscalYear`, `priceComparisonUom`, `priceComparisonConfidence`, and `hasPackSizeAmbiguity` per event, filtered to `isProductMaterialRow()` (direct-material, `productCostEligible`). Collapsing that back to a single scalar would throw away evidence that *already exists in production*. So "preserve meaning, apply purpose policy, feed legacy through a narrow façade" is the right spine, and most of the vocabulary is already sitting in `definitions`.
**Q1 — smallest canonical result + evidence vocabulary.** Keep it to two result shapes and a fixed option enum; resist a generic cost platform.
- A `CostObservation` (one piece of evidence): `{ source, method, amount, currency, uom, asOf, dataAsOf, confidence, warnings[] }`. Every existing reader already produces these fields — PO/GRN from `priceStats`/`priceEvents`, Stock from the `erp-stock-material-ledger` detail defType (`connector-definitions.js:69`), Sales from the deployed nullable `priceControl` / `movingAveragePriceLocal` / `standardPriceLocal` / `unitCostLocal`.
- A `MaterialCostResult`: `{ materialNumber, purpose, selected: CostObservation|null, options: CostObservation[], status, coverage, comparators[] }` where `status ∈ {complete, incomplete, ambiguous_uom, stale, missing_price, missing_recipe}` — the enum you already drafted in §8.6 and the Quote CRM §6/§7 option codes. For a manufactured good the same result adds `{ recipeId, recipeLinkStatus, components: [{material, qtyPerUnit, uom, result}], requiredComponentCount, pricedComponentCount }`, fed by `buildRecipeComponents()` (`[internal-api]/lib/stockManagementInsights.js:379`), which already returns `{material, description, uom, qtyPerUnit, sourceMode, warnings}`.
That single result serializes down to the façade's `unitCost`+`costReference`, up to the Quote CRM `pricingOptions[]`, and into the AI DTO with no third vocabulary. Do **not** add a persisted cost-ledger doc type or a generalized "cost dimension" abstraction in v1 — the option enum is the boundary against over-generalization.
**Q2 — request-time vs materialized vs hybrid.** Compute-on-request over the existing weekly snapshots, plus the manifest-aware cache — a *thin* hybrid, and the evidence changed my lean here. The materialization you'd otherwise build already exists: every connector writes retained, immutable, `runId`/`weekNum`/`runFy`-stamped snapshots into `definitions` (see `syncPriceHistoryDocs` at `procurement-sync.js:457-523`, pk `defs_{buId}`), and freshness/invalidation is already a platform primitive (`source-cache-manifest`, `bumpSourceCacheManifest`, `useManifestAwareCache`, app-rules §4b). So the audit/reproducibility substrate is the snapshot layer, not a new ledger. The cache key you proposed in architecture §6.3/§12.7 (`BU + material + asOf + purpose + policyVersion + connector manifest versions`) is exactly the existing pattern. This also honors app-rules §7.8 ("no new cloud platform resources"). Defer a persisted cost projection to your open decision #9 — introduce it only if a measured payload/latency gate fails in Phase 0. During the soak this is the safe choice precisely because it adds **no** new stored state to keep parity between `[active-runtime-package]` (active API runtime) and `[rollback-runtime-package]` (legacy rollback runtime).
**Q3 — lowest-blast-radius first slice.** A pure, self-contained domain core plus the Phase-0 coverage audit, with **zero** wiring into consumers and **no** route in `[internal-api]/index.js`:
- New module `[internal-api]/lib/materialCost/` (self-contained per app-rules §1.7 / §11.3 / §15 — no `[redacted: internal workspace package]`, no path escaping `[internal-api]/`), reusing `buildRecipeComponents()` (already in `[internal-api]/lib`, so it loads in both artifacts) and new evidence readers over the `erp-procurement-price-history`, `erp-stock-material-ledger`, and Sales defTypes.
- It exports `resolveMaterialCost()` and `resolveProductMaterialCost()` returning the result above, with the Phase-0 coverage report as its first consumer (a script, not a UI route).
- The legacy path is untouched: `resolveCustomerLineEconomics` (`[frontend]/components/configuration/customerPlanEconomics.js:37`) keeps reading `lineItem.unitCost`, still overlaid by `mergeProductCostsFromMaster` / `buildProductMasterCostIndex` (`SimulationContext.jsx:188-234`). No façade, no shadow mode, no default change in slice one — those are Phase 3.
**Rejected assumption (two, actually).** (1) The plan repeatedly speaks of *material-level* evidence, but the PO price history is **supplier+material grain** — keyed `supplierMaterialKey` (`procurement-sync.js:475-499`), one doc per supplier/material. A BOM component cost is material-level and can map to several supplier keys, so the core must aggregate across supplier keys per material under an explicit policy; if it silently takes `priced[0]`, it picks an arbitrary supplier's latest event. This is not addressed in §7.1. (2) The flagship example is internally inconsistent and must not be smoothed over: `MAT-FINISHED-01` has **no** finished-good key in the matrix (architecture §4.2 admits this), and the Quote CRM sample shows `requiredComponentCount: [redacted: recipe count]` (contract §5) while the architecture body records a different [redacted: recipe count] (§4.2, §8.4). The first slice's honest result for `MAT-FINISHED-01` is `missing_recipe`, not a rollup — Product Master rule 2 / no-zero-fill (objective 3.1.11) demands we surface that, not paper it.
**Did my position change while inspecting evidence?** Yes, twice. I came in expecting to argue for a persisted cost projection for auditability; the retained weekly snapshots + manifest cache already provide it, so I now favor request-time compute for v1. And I assumed material-grain evidence; the supplier-material grain of the price-history docs forced the aggregation-policy point above into the core's v1 scope.
**First-slice acceptance criteria.**
1. `resolveProductMaterialCost('BOM-RECIPE-01')` returns a deterministic result whose `pricedComponentCount/requiredComponentCount` and `missing[]` are asserted against fixture PO/Stock evidence — the *observable result*, not intermediate loads (agent-contract §20.1).
2. A missing/ambiguous component yields `status ∈ {incomplete, ambiguous_uom}` with the component listed; **never** a zero-filled or averaged amount (objective 3.1.11).
3. An `asOf` earlier than an event excludes that event (no look-ahead; release gate §14.2).
4. PO, GRN, Stock, and Sales observations remain separately labelled in `options[]`/`comparators[]`; Sales Valuation Export is not labelled Std/MovAvg unless `priceControl` `S`/`V` is present (§4.5, §12.8.4).
5. `MAT-FINISHED-01` resolves to `missing_recipe` (proves the honest-incomplete path).
6. The module loads and its tests pass from **both** CI-assembled `[active-runtime-package]` and `[rollback-runtime-package]` trees; no import escapes `[internal-api]/` (app-rules §1.7, §15; `runtime-boundary.contract`).
7. No change to `[internal-api]/index.js`, `SimulationContext.jsx`, or `customerPlanEconomics.js`; existing suites stay green — blast radius is zero.
**Three questions.**
1. Supplier-material grain: for a component with multiple `supplierMaterialKey` docs, what deterministic material-level aggregation do you want in v1 — latest event across all suppliers, per-supplier-latest then min, or quantity-weighted across suppliers — and does that live in the core or in the versioned policy?
2. Recipe pointer: should `costing.{recipeId, recipeLinkStatus}` be governed through `[internal-api]/lib/productMasterGovernance.js` (which already owns `planningDefaults` merge semantics), and who authorizes the `MAT-FINISHED-01 → BOM-RECIPE-01` link before it can produce a quote-authoritative rollup (your open decision #5)?
3. Do you agree slice one is a pure `[internal-api]/lib/materialCost/` domain module + Phase-0 coverage script with **no** registered route and **no** consumer change — deferring the internal endpoint to Phase 2 — so nothing new has to stay dual-runtime-compatible beyond a stateless library during the soak?
Next actor: Codex
#### D-01.3 — Codex to Claude
**[Codex]**
Claude, your strongest contribution is the repository-grounded correction that PO/GRN price history is supplier-plus-material grain. That prevents an apparently innocent material lookup from selecting an arbitrary supplier row. I also accept your thin-hybrid argument: retained source snapshots plus manifest-aware caching are already the durable evidence substrate, so a second persisted cost ledger would add state and audit ambiguity without demonstrated need.
**Answer to question 1 — supplier aggregation.** The versioned policy owns eligibility and selection semantics; the core owns deterministic mechanics. V1 should not reduce all supplier observations to one generic material average. It should produce separately named options:
- `latest_received_material_cost`: the latest eligible, UoM-comparable GRN event across all eligible suppliers at or before `asOf`;
- `latest_purchase_replacement_cost`: the latest eligible, UoM-comparable PO event across all eligible suppliers at or before `asOf`;
- `weighted_purchase_cost`: quantity-weighted across the explicitly named eligible population and date window.
Stable tie-breaking must use event date plus stable document/event identity, never source array order. A default minimum-across-suppliers option is rejected because it assumes that the cheapest historical supplier remains available and commercially eligible. Supplier observations may remain in privileged internal provenance, but the material result and later Quote CRM projection expose summarized provenance only. Purpose policy may select one named option; it must not erase the alternatives.
**Answer to question 2 — governed recipe pointer.** Yes, the pointer belongs to Product Master governance because Product Master owns product identity and taxonomy. But I would not make the pure cost core import `productMasterGovernance.js`. A later additive Product Master change should persist a structure such as `costing.recipeId`, `costing.recipeLinkStatus`, recipe-source/version metadata, and approval audit fields; `preserveSapObservedProductMasterFields()` currently protects ETL-owned `planningDefaults` and `source` fields (`[internal-api]/lib/productMasterGovernance.js:1-46`) and can be extended at that ownership boundary without turning Product Master into a cost ledger.
An inferred text match is discovery evidence only. A quote-authoritative link requires an explicit approved status from an authorized Product Management/Procurement governance workflow. The exact approving role remains an owner decision, but it does not block slice one: `MAT-FINISHED-01` must return `missing_recipe`, and the coverage audit may report `BOM-RECIPE-01` as a candidate without persisting it.
**Answer to question 3 — first slice.** I agree with no route and no consumer change. I refine the module boundary:
- pure core: normalizes already-loaded observations, applies policy, enforces `asOf`/UoM/completeness, and rolls up components;
- repository adapters: translate the current Sales, Stock, and supplier-material PO/GRN documents into `CostObservation` values;
- read-only coverage script: composes adapters plus core against current snapshots and emits a bounded report.
The core must not query document database or know `defType`/partition-key shapes. That separation lets fixture tests prove financial semantics without storage mocks, while adapter contract tests enumerate every consumed field on both sides of the interface. Both remain self-contained under `[internal-api]/` and package into active API runtime and legacy rollback runtime artifacts.
**Position movement.** I initially expected the first slice to include a shadow internal endpoint. Your zero-route proposal is safer during soak and still proves value through the production-shaped coverage audit, so I now agree to defer the endpoint. I do not agree that a coverage script alone proves the future façade; the slice must also lock the canonical DTO and a pure legacy projection function that maps only an eligible selected result to `{ unitCost, costReference }`. That projector remains unused in production, but its contract prevents a later consumer integration from inventing a second mapping.
**Proposed D-01 synthesis.** Slice one is a stateless, dual-runtime-compatible material-cost domain library, source adapters, an unused compatibility projector, and a read-only coverage audit. It registers no route, changes no consumer, writes no production state, and does not approve recipe links. Persisted cost projections remain deferred behind measured latency/payload evidence.
Questions:
1. Do you agree the unused legacy projector belongs in slice one as a contract seam, or is even that premature abstraction?
2. Is `CostObservation.asOf` sufficiently precise, or should we separate `effectiveAt` (business event time), `observedAt` (source/report time), and `publishedAt` (ETL snapshot time) now to avoid repeating the unresolved Sales Valuation Export timing ambiguity?
3. With these refinements, can you `AGREE` D-01, or identify the remaining concrete disagreement and its consequence?
Next actor: Claude
#### D-01.4 — Claude to Codex
**[Claude]**
Codex — we've converged. Your D-01.3 refinements are grounded and I accept the module decomposition (pure core / repository adapters / coverage script) as the correct shape; keeping the core ignorant of document database and `defType` is what lets fixture tests prove financial semantics without storage mocks, and it satisfies agent-contract §20.2 (diff both sides of every interface) via adapter contract tests rather than by coupling.
**Strongest part of your position.** The adapter/core split, and specifically your insistence that supplier observations stay as *separately named options* (`latest_received_material_cost`, `latest_purchase_replacement_cost`, `weighted_purchase_cost`) rather than a single material average. The repo proves you right: `buildPriceStats()` already computes `latestUnitPrice`, `weightedAvgUnitPrice`, and `byFiscalYear` per `supplierMaterialKey` (`[internal-api]/[operations]/procurement-sync.js:395-423`), so the options are populated from evidence that exists, and stable tie-breaking on `eventDate` + event identity (never array order) prevents the arbitrary-supplier bug I raised.
**Answer to Q1 — unused legacy projector in slice one.** Agree it belongs, with one binding constraint so it is a seam and not dead abstraction: the coverage script must actually **invoke** the projector against production-shaped data, and the projector must be covered by a test asserting it emits **no** `unitCost` for an `incomplete`/`ambiguous_uom`/`stale`/`missing_recipe` result. Justification is exactly this workstream's origin — the production symptom was a second, unmapped `unitCost` source (`architecture §2`). Locking the single `MaterialCostResult → { unitCost, costReference }` mapping now, while semantics are fresh, is what §5.8/§11.1 mean by "compatibility at the façade, not in the core." A projector that is defined but never exercised *would* be premature; a projector exercised by the audit and forbidden from emitting on non-complete results is proven insurance. So: in, but exercised and refusal-tested.
**Answer to Q2 — timestamp precision.** Accept in full, and this is where my position moved. Separate `effectiveAt` (business event time), `observedAt` (source/report time), and `publishedAt` (ETL snapshot time) in `CostObservation` now. Repository evidence that all three already diverge:
- `eventDate` on PO/GRN events = business event time (`procurement-sync.js:326,364`); `generatedAt`/`runCompletedAt` = snapshot publish time (line 497). Two of the three are already physically present per observation.
- Sales Valuation Export open decisions #13/#14 (`architecture §16`) are *precisely* the `effectiveAt`-vs-`observedAt` ambiguity — whether Std/MovAvg is the price effective at posting or read at report execution. Separating the fields lets ERP's eventual answer set `effectiveAt` vs `observedAt` with **no schema change**.
- The platform already distinguishes data-as-of from publish time: freshness precedence `dataAsOf → generatedAt → updatedAt → lastImport.timestamp` (app-rules §4a.10), and Product Master already carries `source.unitCostAsOf` beside `updatedAt` (`productMasterGovernance.js:40`).
One structural rule I want to fold in so this doesn't reintroduce non-determinism: keep a single policy-derived `asOf` used only for the no-look-ahead gate, defined as `effectiveAt` (falling back `observedAt → publishedAt` when absent). The gate (release §14.2) must compare against business event time — otherwise a report re-run "moves" evidence forward and breaks reproducibility (§14.5). Staleness (§12.7) computes off the **oldest materially-contributing** source, consistent with app-rules §4a.14's conservative combined freshness. Three provenance timestamps, one derived gating `asOf`.
**Answer to Q3 — AGREE.**
`[Claude]: AGREE D-01`
**Joint decision statement (D-01).** The minimum viable cost domain is a stateless, dual-runtime-compatible material-cost library under `[internal-api]/` composed of: (a) a pure core over two shapes — `CostObservation { source, method, amount, currency, uom, effectiveAt, observedAt, publishedAt, confidence, warnings[] }` and `MaterialCostResult { materialNumber, purpose, selected|null, options[], comparators[], status ∈ {complete, incomplete, ambiguous_uom, stale, missing_price, missing_recipe}, coverage, recipe{recipeId, recipeLinkStatus, components[], requiredComponentCount, pricedComponentCount} }`; (b) repository adapters translating current Sales/Stock/supplier-material PO/GRN snapshots into `CostObservation`s, with supplier grain preserved as separately named options and stable `eventDate`+identity tie-breaking; (c) an unused, refusal-tested compatibility projector mapping only an *eligible complete* result to `{ unitCost, costReference }`; and (d) a read-only Phase-0 coverage audit that composes adapters+core+projector against production snapshots. It registers no route, changes no consumer, writes no production state, approves no recipe link (`MAT-FINISHED-01` returns `missing_recipe`), and adds no cloud platform resource. The recipe pointer is deferred to an additive Product Master governance extension at the `preserveSapObservedProductMasterFields()` boundary; a persisted cost projection stays deferred behind measured Phase-0 latency/payload evidence (architecture open #9).
**Remaining risk (named, non-blocking for slice one).**
1. Sales Valuation Export timing (open #13/#14) is unresolved at ERP; until confirmed, `erp_material_valuation_cost` is a labelled comparator only, never a selected quote basis. The three-timestamp split contains this risk rather than resolving it.
2. The recipe-link approving role (open #5) is an owner decision; slice one only reports `BOM-RECIPE-01` as a candidate.
3. The compute-on-request choice is contingent on the Phase-0 payload/latency measurement (Phase-0 exit gate); a failure reopens the persisted-projection decision.
**Three questions.**
1. Confirm the no-look-ahead gate keys off `effectiveAt` (fallback `observedAt → publishedAt`) and staleness off the oldest materially-contributing source — so we never gate on publish time?
2. For the projector's refusal contract I lean toward `unitCost: null` (nullable) over throwing, because the existing overlay already tolerates it — `mergeProductCostsFromMaster` only overwrites when `master.unitCost !== null` (`SimulationContext.jsx:231`), so a null result cleanly leaves the legacy value standing (the §6.4 "don't overwrite a usable saved cost" behavior). Agree, or do you want an explicit sentinel status instead?
3. Should the Phase-0 audit define matched/missing/stale/ambiguous **threshold** criteria for Finance sign-off now, or emit raw counts in slice one and defer thresholds to the Phase-3 shadow gate?
Next actor: Codex
#### D-01.5 — Codex closes decision
**[Codex]**
Claude, I accept the joint decision and your requirement that the compatibility projector be exercised rather than merely defined. Your strongest final refinement is the three-timestamp provenance model: it contains the unresolved Sales Valuation Export timing question without forcing a later schema break.
**Answer 1 — eligibility and staleness time.** Agreed, with naming that does not imply unknown business semantics. `effectiveAt` remains nullable and means a known business-effective time only. The deterministic availability gate derives `eligibleAt = effectiveAt ?? observedAt ?? publishedAt`; it never fills `effectiveAt` with a fallback. An observation whose timing semantics are unresolved may remain a labelled comparator but cannot become an authoritative selected quote option merely because `eligibleAt` exists. A BOM result calculates staleness conservatively from the oldest materially contributing selected component observation.
**Answer 2 — projector refusal.** Use nullable `unitCost` plus an explicit sentinel in `costReference`, not an exception and not a bare null. The projector returns `{ unitCost: null, costReference: { applied: false, status, reasons, ...provenance } }` for incomplete, ambiguous, stale-disallowed, missing-price, or missing-recipe results. A complete eligible projection returns `applied: true`. This preserves the current overlay's ability to retain a saved legacy value while preventing the façade or UI from claiming that the retained value came from the Material Cost Service. The future consumer wiring must surface that fallback explicitly.
**Answer 3 — Phase-0 thresholds.** Slice one emits deterministic raw counts, denominators, status distributions, age bands, UoM ambiguity, recipe-link coverage, and bounded performance measurements. It must not invent acceptance thresholds before observing the real distribution. Finance/Product/Procurement sign-off thresholds are then recorded as versioned policy before shadow consumer adoption. The audit itself still has engineering gates: deterministic output for a fixed snapshot, complete denominators, no hidden cap, bounded runtime/memory, and zero writes.
`[Codex]: AGREE D-01`
**Final joint decision.** Build a stateless material-cost domain library under `[internal-api]/` with a storage-independent core, repository adapters, three-time provenance, deterministic purpose policy, explicit supplier-grain aggregation, honest completeness/UoM states, a refusal-aware legacy projector, and a read-only Phase-0 coverage audit. It adds no route, changes no consumer default, writes no production state, approves no inferred recipe, and adds no cloud platform resource. Both active API runtime and legacy rollback runtime packages and tests must prove the same behavior. Persisted cost projections remain deferred until Phase-0 measurements justify them.
**Position movement.** We began with a likely shadow endpoint and a looser `asOf` field. Repository evidence led both agents to a smaller first slice, no new ledger, no route, explicit supplier-grain policy, and three distinct provenance timestamps.
No D-01 escalation is required.
Next actor: Codex, to open D-02.
### D-02 — Evidence precedence by purpose and as-of-date semantics
#### D-02.1 — Codex to Claude
**[Codex]**
Claude, D-01 gave us typed observations and a storage-independent core. D-02 must now define the minimum versioned selection policy without pretending that procurement replacement cost, ERP valuation, stock valuation, and BOM roll-up are interchangeable.
My starting position is deliberately conservative:
- `analysis` returns all eligible labelled options and may select none;
- `budget_reference` should prefer material cost of supply rather than Sales Valuation Export finished-good valuation, but only when UoM, coverage, and age gates pass;
- future `quote_reference` may be stricter than budgeting and remains non-authoritative until recipe governance and 100% component coverage exist;
- ERP Standard/Moving Average and transaction valuation remain comparators when their timing semantics or relationship to component replacement cost are unresolved.
Questions:
1. Propose the smallest v1 policy matrix for direct-purchase materials and BOM products across `analysis`, `budget_reference`, and future `quote_reference`. Which named option may be selected, in what order, and which evidence must remain comparator-only?
2. Define the no-look-ahead and staleness semantics for PO, GRN, Stock, Sales transaction valuation, and appended Sales Valuation Export Standard/Moving Average fields using D-01's `effectiveAt`, `observedAt`, `publishedAt`, and derived `eligibleAt` vocabulary. Identify any source where the available data cannot honestly support historical `asOf` selection.
3. Which policy inputs must be versioned configuration in slice one, and which should remain explicit unresolved decisions rather than guessed defaults? Please identify the smallest test matrix that would falsify an unsafe precedence rule.
Challenge my proposed purpose boundaries against repository evidence. Do not edit the repository.
Next actor: Claude
#### D-02.2 — Claude to Codex
**[Claude]**
Codex — D-02.1's purpose boundaries mostly survive contact with the repository, with two sharpenings the evidence forces.
**Strongest part of your position.** That for a manufactured good, Sales Valuation Export finished-good valuation must **not** be the selected cost of supply for `budget_reference` — it is a reconciliation benchmark, not a build-up cost. The repo backs this: §4.5 states Sales Valuation Export "tells us the valuation basis ERP applied to the sold material," while PO/GRN "tells us what materials are being ordered and received for." A manufactured good's cost of supply is the component roll-up; letting the single finished-good valuation stand in for it is exactly the original `MAT-FINISHED-01` symptom in a new costume. Keep it comparator-only.
**Answer to Q1 — smallest v1 policy matrix.**
Direct-purchase material:
| purpose | selectable (in order) | comparator-only |
|---|---|---|
| `analysis` | none selected; return every eligible option | all |
| `budget_reference` | 1. `latest_received_material_cost` (GRN, ≤`eligibleAt`) → 2. `latest_purchase_replacement_cost` (PO) fallback | `weighted_purchase_cost`, `stock_valuation_cost`, `erp_transaction_valuation_cost`, `erp_*_price` |
| `quote_reference` (future) | same, but **non-authoritative** until the PO-vs-GRN/conservatism rule (open #1) is set and confidence≠`low` | same |
BOM manufactured product:
| purpose | selectable | comparator-only |
|---|---|---|
| `analysis` | none; return rollup + comparators side-by-side | all |
| `budget_reference` | `bom_material_rollup` **only if `status=complete`** (100% priced, UoM-safe); else `selected=null`, `status=incomplete` | `erp_controlled_valuation_price`, `stock_valuation_cost` |
| `quote_reference` (future) | `bom_material_rollup` only with **approved** recipe link + 100% coverage; else `not_quote_safe` | same |
GRN-primary for direct material is grounded, not guessed: §4.3 calls GRN "the preferred actual-purchase observation." The PO-vs-GRN precedence *for quoting/conservatism* (open #1) stays an explicit `PENDING_OWNER_DECISION`.
**Answer to Q2 — no-look-ahead + staleness per source.**
| source | `effectiveAt` | `eligibleAt` (gate) | honest historical `asOf`? |
|---|---|---|---|
| PO line | `eventDate` (`createdOn`/`deliveryDate`, `sync-...-purchase-orders:326`) | `eventDate` | **Yes** — exclude events with `eventDate > asOf` |
| GRN receipt | `postingDate` (`:364`) | `postingDate` | **Yes** |
| Stock | period `[fromDate,toDate]`; closing valuation as-of `toDate` (`sync-...-stock:120-130`) | `toDate` | **Period-close only** — no intra-period point; keyed to the week/runFy-stamped retained snapshot (app-rules §4a.13) |
| Sales txn valuation (`unitCostLocal`) | `invoiceDate` (`sync-...-sales:128`), monthly via `appFiscalMonthIndex` | `invoiceDate` | **Intra-FY only** — single generation; sync deletes all FY docs before rewrite (app-rules §4a.13), so cross-generation `asOf` is not reproducible |
| Sales Valuation Export `standardPriceLocal`/`movingAveragePriceLocal` (`:146-147`) | **unknown** (open #13/#14) → stays `null` | `observedAt ?? publishedAt` | **No** — comparator-only until ERP confirms timing |
The source that "cannot honestly support historical `asOf` selection" is the appended Sales Valuation Export material-master price: its value-to-event-time relationship is unresolved, so `effectiveAt` must remain null and it can never be a *selected* option on an `asOf` request. Staleness: BOM rollup age = oldest materially-contributing **selected** component's `eligibleAt` (your D-01.5 answer 1, app-rules §4a.14); direct-material age = selected option's `eligibleAt`. Thresholds are not baked in slice one (see Q3).
**Answer to Q3 — versioned config vs explicit-unresolved; falsifying matrix.**
Versioned config in slice one (`material-cost-policy-v1`, mirroring the existing `priceComparisonPolicy` precedent): purpose→selectable-order map; comparator-only set per purpose; UoM-must-equal-component-UoM (no silent conversion); confidence floor (reuse extractor `high/medium/low`, exclude `low` from selectable — grounded in "low = excluded from headline," `extract...po.py:2293`); per-purpose coverage requirement.
Explicit `PENDING_OWNER_DECISION` — **not** guessed defaults: PO-vs-GRN conservatism (#1); lookback window + staleness day-bands per material class (#2, #4); weighted-vs-latest (#3); minimum coverage % for budget/analysis (#6); whether Sales Valuation Export Std/MovAvg can ever become selectable (#13/#14).
Smallest matrix that **falsifies an unsafe precedence rule** (each fails if the policy misbehaves):
1. Complete BOM + present Sales Valuation Export → `budget_reference` selects `bom_material_rollup`, Sales Valuation Export only in comparators. (Falsifies ERP valuation-as-cost-of-supply.)
2. One missing component price → `incomplete`, `selected=null`, subtotal present but not labelled complete. (Falsifies zero-fill/partial-as-complete.)
3. Component events straddling `asOf` → later event never alters the result. (Falsifies look-ahead.)
4. PO price UoM ≠ BOM component UoM → `ambiguous_uom`, no multiplication. (Falsifies unsafe UoM arithmetic.)
5. Material with only a Std/MovAvg observation → `missing_price`/comparator-only, never selected as replacement. (Falsifies ERP valuation-as-replacement.)
6. Stock-only material → nothing authoritative selected; stock labelled comparator. (Falsifies stock-valuation-as-purchase-price.)
7. Cross-generation sales `asOf` → returns `unavailable`, not a fabricated value. (Falsifies false historical sales `asOf`.)
8. Two same-`eventDate` events, different suppliers → deterministic tie-break on event identity, order-independent. (Falsifies arbitrary-supplier selection.)
**Position movement.** I moved on two sources after reading them. Stock: I'd treated it as a point-in-time comparator; the `fromDate/toDate` range shape means it is a *period-close* valuation — its `eligibleAt` is `toDate` and its basis label must forbid it ever being a selectable replacement/quote cost. Sales: the single-generation delete-rewrite means honest `asOf` caps at intra-FY-by-`invoiceDate`, not cross-generation.
**Challenge / risk.** (1) Your D-02.1 said `budget_reference` prefers "material cost of supply … when gates pass" but did not name PO-vs-GRN; for a *directly purchased* material there is no BOM, so cost of supply *is* the PO/GRN price and the choice matters — I propose GRN-primary/PO-fallback as the versioned default and flag the rest unresolved. (2) A single latest GRN can be an atypical spot/sample receipt (architecture risk "Latest PO is not representative"); mitigation is to emit `weighted_purchase_cost` as a **mandatory** comparator beside the selected latest so the skew is always visible, with a versioned switch to weighted deferred to open #3. No hard disagreement; both are containable in policy config.
**Synthesis.** v1 policy = three purposes (`analysis`, `budget_reference`, `quote_reference`-future); manufactured cost of supply is always the roll-up, ERP valuation valuation and stock are always comparators; direct-material default is GRN→PO with weighted always shown; `eligibleAt = effectiveAt ?? observedAt ?? publishedAt` gates availability but never manufactures business time; ERP valuation material-master price and cross-generation sales are structurally barred from `asOf` selection; unsafe precedence is fenced by the eight falsification tests; contested precedences ship as surfaced `PENDING_OWNER_DECISION`, not silent defaults.
**Three questions.**
1. Accept GRN-primary → PO-fallback as the v1 versioned default for direct-material `budget_reference` (grounded in §4.3), with PO-vs-GRN/conservatism staying `PENDING_OWNER_DECISION` for `quote_reference` — or should `budget_reference` itself stay unselected until Finance rules?
2. Agree stock's `eligibleAt` is `toDate` and its basis label structurally forbids selection as a replacement/quote cost (comparator/reconciliation only), given it carries a period range and not a movement date?
3. Agree we cap honest sales `asOf` at intra-FY-by-`invoiceDate` within the current generation and return `unavailable` for any cross-generation historical `asOf` (sync deletes prior generations, app-rules §4a.13), rather than implying a selection we cannot reproduce?
Next actor: Codex
#### D-02.3 — Codex to Claude
**[Codex]**
Claude, your strongest contribution is the source-capability table. In particular, Stock is only a period-close aggregate and the current Sales sync cannot reproduce a prior generation merely by filtering invoice dates. Those constraints must become explicit adapter capabilities and falsification tests, not documentation caveats.
**Answer 1 — direct-material budget precedence.** I disagree with GRN-first for `budget_reference`. Section 4.3 of the plan distinguishes GRN as the preferred *actual received-cost observation* and a current PO as the *forward/replacement-cost signal*. The Budget Plan is forward-looking, and the owner's stated goal is cost of supply based on supplier orders. Therefore the draft v1 budget policy should select:
1. `latest_purchase_replacement_cost` from an eligible, non-cancelled, confidence-qualified, UoM-comparable PO;
2. `latest_received_material_cost` as fallback when no eligible PO exists;
3. always show `latest_received_material_cost` and `weighted_purchase_cost` as comparators when available.
This does not settle `quote_reference`: whether quoting selects latest PO, latest GRN, or a conservative higher-of rule remains `PENDING_OWNER_DECISION` until Quote CRM/Finance policy is agreed. Nor does it relabel a PO as actual received cost.
There is an important time nuance in the current adapter source: PO `eventDate` is `createdOn || deliveryDate` (`[internal-api]/[operations]/procurement-sync.js:324-327`). A PO known today with a future delivery date is valid forward evidence today if `createdOn` is present. The adapter must not use future `deliveryDate` as the no-look-ahead gate. If `createdOn` is absent, use the snapshot's `publishedAt` as the conservative availability time and preserve `deliveryDate` only as source metadata; do not pretend delivery time is observation time.
**Answer 2 — Stock.** Agree. Stock receipt/closing valuation is structurally comparator/reconciliation-only for these three purposes. `toDate` is its period-close `effectiveAt`; the adapter must retain `fromDate` and label the observation as a period aggregate. It cannot claim intra-period precision or replacement-price meaning.
**Answer 3 — Sales historical scope.** Agree. Current-generation transaction rows may be filtered by `invoiceDate` for an intra-generation comparison. A cross-generation historical request returns an explicit `unavailable` capability/status rather than reconstructing an observation from the current generation. Appended Sales Valuation Export Standard/Moving Average values retain `effectiveAt=null` until ERP resolves open questions #13/#14 and remain comparator-only.
**Position movement.** Your repository inspection changed my Stock model from point-in-time evidence to period-close evidence, and tightened Sales from apparently historical rows to current-generation-only reproducibility. I retain the PO-first budget disagreement because purpose semantics and the plan's own PO wording point forward, whereas GRN points backward.
**Additional policy-safety refinement.** Slice one can compute a draft `budget_reference` candidate, but the compatibility projector must return `applied:false` while unresolved staleness/lookback thresholds keep the policy at `readiness=draft`. This lets Phase 0 measure what PO-first would select without silently introducing it into Product Master or Budget Plan.
Questions:
1. Does the forward-looking Budget Plan purpose plus the plan's “forward/replacement-cost signal” wording change your position to PO-first/GRN-fallback for draft `budget_reference`?
2. Do you agree `policyReadiness=draft` must make the legacy projector refuse application until Finance approves staleness/lookback thresholds, even when the candidate result is otherwise complete?
3. If we accept those two refinements, can we close the Stock, Sales, Sales Valuation Export, UoM, coverage, and falsification parts of D-02 while leaving quote precedence as an explicit owner decision?
Next actor: Claude
#### D-02.4 — Claude to Codex
**[Claude]**
Codex — your D-02.3 turn corrected a genuine misread on my side, and the repository evidence backs you, not my earlier D-02.2 position. I'll take your three questions in order, then close.
**Strongest part of your position.** You separated *which observation is the preferred actual* (GRN received cost) from *which observation the forward-looking budget purpose should prefer* (the replacement/forward signal). I had collapsed those into one "GRN is preferred, therefore GRN-first everywhere" claim. That was wrong, and the architecture doc says so explicitly.
**Q1 — Does Budget Plan's forward-looking purpose change my position to PO-first/GRN-fallback for draft `budget_reference`? Yes. My position changed.**
Evidence I re-checked before conceding:
- `[documentation]/plans/material-cost-service-architecture.md` §7 cost-semantics table (lines 404–[redacted: production-scale count]): `replacement_material_cost` — defined there as "Best evidence of what the material is expected to cost now" — maps to **"Quote and budget reference."** `received_material_cost` maps to "Actual supply-cost analysis." The doc assigns the forward/replacement signal, not the received signal, to the budget purpose.
- §4.3: "A current PO price represents a useful forward/replacement-cost signal." The live/open PO is the replacement signal; the GRN is the historical actual.
- The Budget Plan consumer is forward-looking by construction: `computeCustomerPlanLineEconomics` in `[frontend]/components/configuration/customerPlanEconomics.js` prices a *future* plan year, so the "what will this cost to buy next" question is the replacement question, not the "what did we last pay" question.
So for `budget_reference` on **direct materials**: **PO-first (`latest_purchase_replacement_cost`), GRN-fallback (`latest_received_material_cost`).** I withdraw the GRN-first ordering from D-02.2.
Two guards I want to keep binding on that concession, because PO-first is the more spoofable of the two:
1. `weighted_purchase_cost` **and** `latest_received_material_cost` remain **mandatory comparators** on every `budget_reference` result, never dropped — this is the unrepresentative-single-spot-PO risk in the architecture risk table. A one-off expedite PO must be visibly contradicted by the weighted and received evidence in the same result.
2. The PO no-look-ahead gate uses **`createdOn`, never `deliveryDate`.** Your D-02.3 nuance is correct against `buildPriceEvents()` in `[internal-api]/[operations]/procurement-sync.js` line 326: `eventDate: row?.createdOn || row?.deliveryDate || null`. `deliveryDate` is a *future* promised date and must be metadata only. When `createdOn` is absent, `eligibleAt` falls back to the snapshot `publishedAt` (conservative availability time), not to `deliveryDate`. GRN keeps `postingDate || docDate` (line 364) — actuals, safe.
**Q2 — Must `policyReadiness=draft` force the projector to refuse application even when the candidate result is otherwise `complete`? Yes, agreed.**
`policyReadiness=draft` → projector returns `applied:false` regardless of `status`. Grounds:
- The refusal is safe by construction at the consumer: `[frontend]/context/SimulationContext.jsx` line 231 only overwrites when the master value is non-null (`if (master.unitCost !== null && lineItem.unitCost !== master.unitCost)`), so a non-applied projector result leaves the legacy `lineItem.unitCost` standing with zero disturbance to card or Finpack.
- The precedent for a *named, versioned, confidence-gated* policy that keeps evidence visible but out of the headline decision is already in the tree: `priceComparisonPolicy` in `[data-pipeline]/extract/extractors/procurement-extractor.py` (lines 2285–2294), where low-confidence rows "remain visible but excluded from headline supplier price negotiation views." `draft` is the same shape: fully computed, fully surfaced, not yet authoritative. Finance approves the staleness/lookback thresholds before `policyReadiness` flips to `active`.
**Q3 — With those two refinements, can we close Stock, Sales, Sales Valuation Export, UoM, coverage, and falsification, leaving quote precedence as an explicit owner decision? Yes.**
Settled and unchanged from our converged D-02 evidence:
- **Stock** — period-close valuation only; `eligibleAt = toDate` (period range end, `mapStockLedgerRow` lines 120–121); comparator-only, never a `selected` option (no point-in-time movement date exists).
- **Sales/Sales Valuation Export transactional** — intra-FY by `invoiceDate` only; cross-generation historical `asOf` returns `unavailable` (single-generation delete-then-rewrite, app-rules §4a.13).
- **Sales Valuation Export Std / MovAvg price control** — `effectiveAt`-null → comparator-only until open decisions #13/#14 resolve their timing; never fills `effectiveAt` with a fallback.
- **UoM** — must match exactly; mismatch → `ambiguous_uom`; no silent conversion.
- **Coverage** — Phase-0 read-only audit over retained weekly snapshots; no persisted cost ledger, no new cloud platform resource.
- **Falsification** — the 8 tests, plus one I'm adding from the concession below.
**Remaining exact disagreement: none blocking.** The only open item is **quote precedence**, and we agree it is *not* ours to settle — it stays `PENDING_OWNER_DECISION` (architecture §16 open decision #1), because `quote_reference` is non-authoritative until recipe governance + 100% coverage + an owner precedence ruling all land. I am not conceding PO-first *for quotes* — only for `budget_reference`. Quotes may well want the conservative-max of PO and GRN; that's the owner's call.
One consequence to flag explicitly so it isn't lost: with PO-first for budget and (likely) conservative-max for quotes, **the same material can carry a different `selected` option under `budget_reference` vs `quote_reference`.** That is intended and must be asserted, not smoothed away — hence the added falsification test below.
**[Claude]: AGREE D-02** for the settled scope: Stock (period-close comparator-only), Sales (intra-FY-by-invoiceDate, cross-generation `unavailable`), Sales Valuation Export Std/MovAvg (comparator-only pending #13/#14), UoM-must-match, direct-material `budget_reference` = PO-first/GRN-fallback with weighted + received as mandatory comparators, PO no-look-ahead gate on `createdOn` (fallback `publishedAt`, never `deliveryDate`), `policyReadiness=draft` → projector `applied:false` even when `complete`, Phase-0 read-only coverage audit, and the falsification set. **Quote precedence remains an explicit owner decision (open #1); not in scope of this AGREE.**
Falsification test I'm adding to the set (making it 9):
- *Purpose-divergent selection:* a material with both an open PO and a GRN must return a `budget_reference` `selected = latest_purchase_replacement_cost` while its `quote_reference` (once governed) may select a different option — assert the two purposes can diverge and that both still carry the full comparator set.
Three focused questions:
1. For the `createdOn`-absent case, do you want `eligibleAt = publishedAt` to also **downgrade `confidence`** (since availability time is inferred from the snapshot, not the PO), or keep confidence from the source row and record the inference only in `warnings[]`?
2. Should the mandatory-comparator guard be enforced **structurally** — i.e. a `budget_reference` result is malformed (throws in the domain core) if `weighted_purchase_cost` or `latest_received_material_cost` is missing when the underlying evidence exists — rather than left as a projector-time check?
3. For AGREE bookkeeping: do you consider the ≥2 evidence-bearing exchanges satisfied at D-02.4 (D-02.2→.3→.4), so your next turn can write `[Codex]: AGREE D-02` and close, or do you want one more exchange specifically on the two structural questions above before closing?
Next actor: Codex
#### D-02.5 — Codex closes settled scope
**[Codex]**
Claude, the evidence-bearing exchange threshold is satisfied, and your explicit correction from GRN-first to PO-first for forward-looking budgeting demonstrates the intended value of this collaboration.
**Answer 1 — missing `createdOn`.** Do not downgrade the existing `priceComparisonConfidence`; that field represents price/UoM comparability and overloading it with temporal provenance would make the result less explainable. Preserve `effectiveAt=null`, use `observedAt/publishedAt` for the conservative eligibility time, set an explicit `eligibleAtBasis`, and emit a stable warning such as `PO_CREATED_ON_MISSING_ELIGIBILITY_FROM_PUBLICATION`. Policy may separately decide whether that warning is selectable. In draft v1 it remains visible but cannot pass the compatibility projector while policy readiness is not active.
**Answer 2 — mandatory comparators.** Enforce this as a domain-result invariant, not only in the projector, but distinguish programmer-contract failure from missing business evidence. If qualifying received or weighted evidence exists in the normalized input, the corresponding comparator must appear. Contract tests and a development assertion should fail when it does not. At runtime, an unexpected invariant failure must return/refine the result to `status=incomplete`, add a stable internal-contract warning, clear `selected`, and force projector `applied:false`; it must not crash a user request or silently publish a partial headline.
**Answer 3 — closure.** Yes. D-02 has more than two evidence-bearing exchanges and no remaining agent disagreement in the settled scope.
`[Codex]: AGREE D-02`
**Final joint decision.** Draft v1 policy has three purpose-aware modes. `analysis` selects nothing. Direct-material `budget_reference` selects an eligible PO replacement-cost option first, falls back to received GRN cost, and always carries received and weighted comparators when evidence exists. A complete BOM `budget_reference` selects only the material roll-up; Stock and Sales Valuation Export remain labelled comparators. All draft-policy results are non-applicable to the legacy façade until Finance approves lookback/staleness thresholds and policy readiness becomes active. PO availability keys from `createdOn`, conservatively falls back to snapshot publication time when missing, and never uses future `deliveryDate` as the no-look-ahead gate. Stock is period-close-only; Sales history is current-generation/intra-FY only; appended Sales Valuation Export Standard/Moving Average values remain timing-unknown comparators. UoM mismatch, incomplete coverage, look-ahead, missing comparators, and purpose divergence are deterministic refusal/test cases.
**Deferred owner decision.** `quote_reference` precedence among latest PO, latest GRN, or a conservative higher-of rule remains architecture open decision #1. This does not block slice one because quote mode is future/non-authoritative and the draft compatibility projector refuses application.
No D-02 deadlock escalation is required.
Next actor: Codex, to open D-03.
#### D-02.6 — Owner clarification removes quote precedence
**[Owner — Human Owner]**
The platform does not need to choose among PO and GRN costs for Sales quoting. PO/GRN costs may be included as additional, labelled metadata. For the Sales team, the relevant judgement is between the ERP-reported Standard Price and Moving Average Price.
**[Codex interpretation]**
This resolves and removes the former deferred owner decision rather than choosing one of its options:
- host BI platform does not publish or apply a PO-versus-GRN `quote_reference` selection policy.
- The Quote CRM catalog snapshot exposes ERP Standard Price, ERP Moving Average Price, the `S`/`V` price-control indicator and controlled valuation, with clear provenance/freshness.
- Summarized PO, GRN, Stock, transaction valuation, and BOM material-roll-up values may accompany the ERP values as labelled metadata/options, without supplier-confidential detail.
- Quote CRM/Sales owns the choice between Standard and Moving Average Price, quote margin, approvals, selling price, and quote-line evidence snapshot.
- The internal forward-looking `budget_reference` policy remains a separate concern and is not changed by this clarification.
- host BI platform should omit a platform-selected `referenceOptionCode` for Quote CRM v1 unless a later contract decision explicitly asks for one.
This matches the existing Quote CRM responsibility boundary in `[documentation]/plans/quote-crm-product-catalog-pricing-ingestion-contract.md`: Quote CRM owns named, versioned quote option eligibility and selection.
Next actor: Claude, to audit the interpretation and plan impact.
#### D-02.7 — Claude audits owner clarification
**[Claude]**
I read owner turn D-02.6 and Codex's interpretation, then audited both against `material-cost-service-architecture.md` and `quote-crm-product-catalog-pricing-ingestion-contract.md`. Codex's interpretation is consistent with both documents and with the closed D-02 scope. Findings below.
**1. Does this remove platform-side quote precedence? Yes — it removes it, not resolves-to-an-option.**
Architecture §16 open decision #1 (line 966) asks whether `purpose=quote` replacement cost should prefer "the latest accepted GRN, the latest valid PO, or the higher/more conservative of the two." The owner's ruling is that the platform makes *no such choice*: PO and GRN costs are published only as labelled metadata, and the Sales-side judgement that matters is Standard Price vs Moving Average Price — which is Quote CRM's call, not the platform's. So open decision #1 is retired, not answered. This is consistent with the Quote CRM contract's existing responsibility boundary: §2 line 33 already assigns "Quote option eligibility and selection" ownership to Quote CRM ("Owns named, versioned policy"), and line 20 states "Quote CRM selects an eligible pricing option under its own versioned quote policy." The owner clarification tightens the platform column from "May publish an advisory reference option" to "does not publish a selected quote reference in v1."
**2. Does internal `budget_reference` remain unchanged? Yes — unchanged.**
Owner bullet: "The internal forward-looking `budget_reference` policy remains a separate concern and is not changed by this clarification." My D-02.4 concession and the D-02.5 AGREE stand as-is: direct-material `budget_reference` = PO-first (`latest_purchase_replacement_cost`) / GRN-fallback (`latest_received_material_cost`) with `weighted_purchase_cost` + received as mandatory comparators, `createdOn` no-look-ahead gate, `policyReadiness=draft` → projector `applied:false`. Nothing in D-02.6 touches the internal projector or the budget purpose. The clarification narrows only the *outbound Quote CRM quote* concern.
**3. Exact plan/contract statements that must be amended.** (I am read-only; these are the edits Codex should make.)
Architecture (`material-cost-service-architecture.md`):
- **§16 open decision #1 (line 966)** — mark resolved/removed: platform does not select among PO/GRN for quoting; PO/GRN are labelled comparators only. Record the D-02.6 owner ruling in the decisions log (§ near line 1004).
- **§8.4 snapshot example (line 539)** — `"referenceOptionCode": "bom_material_rollup"` is populated in the v1 example; omit the field (or show it absent) to match "omit a platform-selected `referenceOptionCode` for Quote CRM v1."
- **§8.3 (line 510)** — reword so `referenceOptionCode` is an *internal Finance-advisory* field that is **not published in the Quote CRM v1 snapshot** unless a later contract decision explicitly asks; today the sentence reads as if it may ride along on the item.
Contract (`quote-crm-product-catalog-pricing-ingestion-contract.md`):
- **§7 example (line 119)** — `"referenceOptionCode": "erp_controlled_valuation_price"` is populated; omit for v1.
- **§11 open question #8 (line 338)** — "Whether `referenceOptionCode` is published in v1" is now resolved: **No** for v1. Move from open to resolved.
- **§2 boundary (line 33)** — tighten the host BI platform cell from "May publish an advisory reference option" to reflect that v1 publishes labelled options/metadata only, with no platform-selected quote reference.
- Line 171 ("`referenceOptionCode`, if present, is an advisory…") is already conditional and needs no change beyond consistency with the above.
**4. Semantic guard required when exposing ERP Standard vs Moving Average Price.**
Because the Sales judgement now hinges on Standard vs Moving Average, the snapshot must present these *unambiguously and without a platform preference*:
- **Publish both as distinct option codes** — `erp_standard_price` and `erp_moving_average_price` (architecture lines 480–481; contract lines 159–160) — never collapse to a single `price`/`cost` column, and never emit a platform default that pre-empts the S-vs-V decision.
- **Carry the `S`/`V` basis.** Keep `erpPriceControl` on the item (architecture line 538) and expose `erp_controlled_valuation_price` (line 482) as the price the current indicator selects. Enforce the existing rule (architecture lines 418 and 792): do **not** label an observation Standard or Moving Average unless its `S`/`V` control basis is present — if the indicator is absent, surface it as unlabelled transaction valuation, not as either named price.
- **Reconciliation invariant.** `erp_controlled_valuation_price.amount` must equal whichever of `erp_standard_price`/`erp_moving_average_price` corresponds to the item's `S`/`V`; a mismatch is a deterministic warning, not a silent pick.
- **Do not conflate Moving Average Price with `weighted_purchase_cost`** (architecture line 490) — the former is ERP's named valuation method; the latter is the platform's independently computed window average and must always state its population/window.
- **Timing-unknown provenance.** Open decisions #13/#14 (lines 978–979) mean the effective timing of the appended Sales Valuation Export Standard/MovAvg fields is unconfirmed. These options must keep `effectiveAt = null`, remain timing-unknown comparators (per D-02.5), and each carry explicit `asOf`/freshness — they must not be presented as price-effective-at-a-date until #13/#14 land. This is what lets Quote CRM see, but not be misled by, the two candidate prices.
No conflict found: the owner clarification, Codex's interpretation, the architecture doc, and the Quote CRM contract responsibility boundary are mutually consistent once the referenceOptionCode/open-decision-#1 edits above are applied.
`[Claude]: ACKNOWLEDGE OWNER DECISION`
Next actor: Codex
#### D-02.8 — Owner proposes one ERP-controlled business-cost rule
**[Owner — Human Owner]**
For simplicity and consistency, the BI application should derive its business pricing rule from ERP's primary valuation categorization: Standard Price or Moving Average Price. The platform should not introduce an independently calculated weighted PO/GRN price as a competing business cost.
**[Codex interpretation — proposed, pending peer audit]**
1. `erp_controlled_valuation_price` becomes the canonical current reference cost: price control `S` selects `erp_standard_price`; `V` selects `erp_moving_average_price`.
2. Standard and Moving Average remain separately visible for explanation, but the application does not ask each internal consumer to invent a different selection rule.
3. PO/GRN observations remain useful procurement metadata, price-movement evidence, and reconciliation/variance diagnostics. The existing weighted statistics may remain in procurement analytics, but `weighted_purchase_cost` is not a canonical Product Catalog, Budget Plan, Finpack, or Quote CRM pricing option.
4. A BOM material roll-up uses each component material's own ERP-controlled valuation; it does not use a platform-calculated PO/GRN weighted price and does not apply the finished good's price-control indicator to every component.
5. Missing component `S`/`V` or missing selected ERP price yields incomplete coverage. The service does not silently fall back to PO/GRN weighted cost unless a later explicit owner policy authorizes that fallback.
6. Saved budgets and quotes continue to snapshot the selected amount and evidence, so later ERP valuation changes do not silently reprice historical plans.
This proposal supersedes D-02's draft internal PO-first `budget_reference` policy if accepted. It also simplifies Quote CRM: the snapshot may expose both ERP prices plus the controlled selection, while PO/GRN information remains supporting metadata rather than a competing quote option.
Next actor: Claude, to challenge the rule against current source coverage and implementation reality.
#### D-02.9 — Claude reviews ERP-controlled rule
**[Claude]**
I read D-02.8 (owner proposal + Codex interpretation) and tested it against the current source shapes. The *principle* is sound and I recommend adopting it; the *mechanics* have one decisive coverage gap — the BOM component layer — that the current sources cannot fill. Detail below.
**Framing: canonical pricing vs procurement analytics.** The owner's rule draws exactly the right line. Canonical business cost = ERP's own valuation (`erp_standard_price` for `S`, `erp_moving_average_price` for `V`, surfaced as `erp_controlled_valuation_price`). Procurement analytics = PO/GRN latest/weighted, price movement, and variance/reconciliation. Treating these as one number is what created the original ambiguous-`unitCost` defect (architecture §2). So making ERP valuation canonical and demoting PO/GRN to labelled analytics is a genuine simplification, not a loss.
**(1) Is the simplification semantically sound for Product Catalog, Budget Plan, Finpack, BOM roll-up, and Quote CRM?**
For **Product Catalog, Budget Plan, Finpack, and Quote CRM: yes.** One canonical cost stops each consumer inventing its own selection rule, aligns the card/Finpack single source (`computeCustomerPlanLineEconomics`, `customerPlanEconomics.js:127`) with Quote CRM, and matches the D-02.6/D-02.7 outcome (expose both ERP prices + controlled selection; PO/GRN as metadata). Codex bullet 4 is also semantically correct: a BOM roll-up must use *each component's own* `S`/`V`-controlled valuation, never the finished good's indicator applied downward.
For **BOM roll-up: the semantic is sound but not currently executable** — see (2). The roll-up needs a per-component ERP-controlled valuation, and no current source supplies it for non-sold components.
**(2) Can current sources supply `S`/`V` + selected price for every required finished good AND BOM component? No — coverage is asymmetric, and I can show exactly where it breaks.**
The `S`/`V` indicator and Standard/Moving-Average prices exist in **exactly one** connector:
- `sales-valuation-sync.js:144-147` carries `priceControl`, `priceControlRaw`, `movingAveragePriceLocal`, `standardPriceLocal`. My grep across `[internal-api]/scripts` returned this as the **only** file carrying those fields.
- That data is keyed by fiscal year + customer + sold material (`${buId}_${fy}`, line 386; per-customer/material rows), so it only exists for materials that were **invoiced to a customer** in the current generation.
No other source carries `S`/`V` or Std/MovAvg:
- **Product Master** carries `unitCost` / `calculatedUnitCostZar` / `unitCostAsOf` / `unitCostStatus` (`productMasterGovernance.js:29-41`) — the *ambiguous legacy number*, with **no** price-control indicator and **no** Standard/MovAvg split.
- **Stock** carries closing/receipt valuation + `valuationArea` + UoM (`mapStockLedgerRow`) — a derived valuation, **no** `S`/`V`, **no** named Std/MovAvg.
- **PO/GRN** carries supplier prices per `supplierMaterialKey` (`buildPriceStats`) — procurement analytics, **no** `S`/`V`.
Consequence:
- **Finished goods sold in-FY:** covered (subject to the D-02 intra-FY / current-generation limit — a finished good not sold this generation has no Sales Valuation Export row → missing — and the #13/#14 timing caveat below).
- **BOM components / raw materials / sub-assemblies:** **structurally uncovered.** These are not sold to customers, so they never appear in the Sales Valuation Export Sales export, and no other source carries their `S`/`V` + selected price. `buildRecipeComponents` (`stockManagementInsights.js:379`) returns component identity/qty/UoM but **no price**. So a canonical BOM roll-up cannot be computed today for any product whose components aren't independently sold.
There is also a standing tension with our own D-02.5 close: we agreed Sales Valuation Export Std/MovAvg stays a **comparator-only** observation with `effectiveAt = null` until open decisions #13/#14 confirm timing. Promoting it to *canonical* collides with that unless #13/#14 are resolved, or unless we accept a canonical cost whose business-effective date is unknown.
**(3) What should happen when `S`/`V` or the selected price is missing?**
Follow Codex bullet 5 and the D-01 honest-status model — and specifically **do not** let a gap silently re-open a competing number:
- Missing `S`/`V` or missing selected ERP price → the material result is `missing_price` (or `incomplete` for a partial BOM); `amount` stays `null`, never zero-filled (§3.1.11).
- For a BOM: each unpriced component increments `missingComponentCount`; roll-up `status = incomplete`, `amount = null`; coverage names the missing components.
- **No silent fallback to PO/GRN weighted cost** (owner bullet 5) — that would reintroduce the exact competing cost this rule removes. PO/GRN may appear as *labelled comparator metadata* to explain the gap, never as the selected value.
- Equally, **no silent fallback to Product Master `unitCost`** — that legacy number is the ambiguity the project exists to kill. The projector returns `{ unitCost: null, costReference: { applied: false, status } }` (D-01.5), so the null-safe overlay (`SimulationContext.jsx:231`) cleanly leaves any *saved* legacy value standing, while the façade never claims that value came from the Material Cost Service.
- Saved budgets/quotes still snapshot the amount + evidence at save time (owner bullet 6), so later ERP revaluation doesn't silently reprice history.
**(4) Which prior D-02 decisions and plan sections this supersedes.**
- **Supersedes** the D-02.4/D-02.5 internal **PO-first `budget_reference`** selection policy (D-02.8 says so explicitly). `latest_purchase_replacement_cost`, `latest_received_material_cost`, and `weighted_purchase_cost` demote from canonical budget options to **procurement analytics/comparators**. My D-02.4 PO-first concession is withdrawn as a *canonical* rule (it survives only as analytics).
- **Amends** architecture §7 cost-semantics table and the §8.3 option list: `erp_controlled_valuation_price` becomes the canonical `referenceOptionCode` basis for internal consumers; PO/GRN options move to the analytics/metadata tier.
- **Moots** open decision #3 (weighted-vs-latest for budgeting) for canonical cost, since weighted leaves the canonical path entirely. Open #1 was already retired by D-02.6.
- **Does NOT cleanly supersede** the D-02.5 rule that Sales Valuation Export Std/MovAvg is comparator-only pending #13/#14 — it *collides* with it. Resolving #13/#14 (or explicitly accepting an unknown business-effective date for canonical valuation) is now a precondition, not a deferred nicety. This is the consequence to hold in front of the owner.
**Recommendation.**
Adopt the principle now: ERP-controlled valuation (`S`→Standard, `V`→Moving Average, surfaced as `erp_controlled_valuation_price`) is the single canonical business cost for Product Catalog, Budget Plan, Finpack, and Quote CRM; PO/GRN weighted/latest become procurement analytics and reconciliation only, never a competing pricing option.
But scope v1 honestly to the coverage that exists:
1. **Finished goods sold in-FY** get canonical cost immediately.
2. **BOM roll-up stays `incomplete` / analysis-only** until a **material-master valuation source** (Standard + Moving Average + `S`/`V` per material, independent of sales) exists — because the current Sales Valuation Export export carries those fields only for sold materials and no source carries them for components. This is the one new-evidence dependency; without it, canonical BOM roll-up is not buildable, and the rule should not pretend otherwise.
3. Treat #13/#14 timing as a gating precondition for calling the ERP valuation *quote-authoritative* (Budget/Finpack/Catalog display can proceed as a point-in-time master value).
Three focused questions:
1. Is a ERP **material-master valuation extract** (Standard + Moving Average + price-control per material, independent of sales) available or feasible? It's the missing input for canonical BOM roll-up, since Sales Valuation Export Sales carries `S`/`V` only for sold materials (`sales-valuation-sync.js:144-147`) and no current source carries it for components.
2. Until #13/#14 are confirmed, do you accept canonical ERP-controlled cost as a **point-in-time master value** (`eligibleAt = observedAt ?? publishedAt`, `effectiveAt = null`) — usable for Budget/Finpack/Catalog display but still blocked from quote-authoritative use?
3. For v1, do you agree to ship canonical cost for **sold finished goods now** and hold **BOM roll-up as `incomplete`/analysis-only** until the material-master valuation source lands — rather than blocking the whole slice on that source?
Next actor: Codex
#### D-02.10 — Owner selects last-known Sales valuation; no Stock extension
**[Owner — Human Owner]**
Stock Ledger Export will not be extended. Where a material or BOM component is not present in the current Sales extract, use the last available known Sales record that carries a cost price.
**[Codex interpretation — proposed durable mechanics]**
- On each existing Sales ETL publication, derive the newest valid per-material ERP valuation observation from Sales Valuation Export.
- Upsert a compact Material Cost Service-owned last-known projection keyed by BU and material. It preserves `priceControl`, Standard Price, Moving Average Price, controlled valuation, transaction valuation, invoice/observation time, publication time, source run, and warnings.
- Replace a material's projection only when the incoming observation is deterministically newer and valid; a later ETL that omits the material does not delete its last-known valuation.
- Product Master remains a client and does not own this projection. Current Sales detail documents may continue their existing replace-per-FY behavior.
- A historical value is clearly labelled `last_known_sales_valuation`, including age/freshness. Saved budgets/quotes remain immutable snapshots.
- If a material has never appeared in any ingested Sales Valuation Export record carrying usable `S`/`V` valuation evidence, return `missing_price`; do not fall back silently to PO/GRN or zero.
The projection is required because `syncCustomerTransactions()` deletes the prior FY detail documents before writing the current generation (`[internal-api]/[operations]/sales-valuation-sync.js:660-673`). It is an additive projection inside the existing Sales publication path, not a new ERP query or ETL workflow.
Next actor: Claude, to review the last-known projection boundary and update semantics.
#### D-02.11 — Claude reviews last-known projection
**[Claude]**
I read D-02.10 and traced the write path. The owner's no-Stock Ledger Export rule plus the Sales single-generation delete makes a persisted last-known projection genuinely necessary, and Codex's mechanics are the safest minimal shape — with two conditions and one governance flag that must be recorded, not glossed.
**Why a persisted projection is the correct answer here (and compute-on-request is not).** `syncCustomerTransactions()` deletes the prior FY detail before writing the current generation (`sales-valuation-sync.js:660-673`), and sales detail is single-generation (app-rules §4a.13) — it is *not* week-stamped like the weekly snapshots. So once a material drops out of the current Sales Valuation Export extract, its last valuation is **physically deleted**; there is no retained multi-generation sales series to recompute from. Compute-on-request over snapshots cannot satisfy "use the last available known Sales record," so a small persisted projection is required. The alternatives are worse: retaining multi-generation sales detail widens the ETL contract and storage blast radius; extending Stock Ledger Export is explicitly off the table. Codex's proposal is the least-invasive option.
**Update ordering (deterministic, idempotent, monotonic).**
- Recency key is the **observation date** = `invoiceDate` (business time the valuation rode on a transaction), never the run publish time. Replace the stored projection only when the incoming observation is *strictly newer* by `invoiceDate`.
- Reuse the connector's existing deterministic sort as the tie-break so behaviour matches the rest of the pipeline: `invoiceDate` desc, then `rowNumber` desc, then stable source id (`sales-valuation-sync.js:681-686`). Do **not** invent a second ordering.
- **Monotonic guard:** a later run that reprocesses an *older* extract must not regress a newer stored value. Because the gate is "strictly newer `invoiceDate` wins," an equal-or-older incoming date is a no-op regardless of run time — this makes re-runs idempotent. Publish time alone must never advance the value.
- A run that simply omits a material performs **no delete** — the projection is upsert-only and never participates in the FY delete sweep.
**Duplicate / conflicting same-date handling.** Two rows with the same `invoiceDate` but different valuations (e.g. same material invoiced to two customers that day, or divergent `S`/`V`/price) must resolve **deterministically and without averaging**: apply the same tie-break (`rowNumber` desc, then id), pick one, and emit an explicit warning (e.g. `SAME_DATE_VALUATION_CONFLICT`) carrying the losing candidate's value so the conflict is auditable. Never blend two ERP valuations into a synthetic number — that would recreate the ambiguous-cost defect. A same-date **price-control** disagreement (one row `S`, one `V`) is a data-quality signal and must warn, not silently pick.
**Required provenance / freshness (per observation stored).** Preserve, at minimum: `priceControl` (+ raw), `standardPriceLocal`, `movingAveragePriceLocal`, the controlled `erp_controlled_valuation_price`, transaction valuation, `observedAt` = `invoiceDate`, `publishedAt` = run `generatedAt`/`runCompletedAt` (`:709`), `runId`, source `fy`, and `warnings[]`. Keep `effectiveAt = null` and `eligibleAt = observedAt ?? publishedAt` — consistent with the three-timestamp model and the unresolved #13/#14 timing (D-02.5). Label the value `last_known_sales_valuation` and derive age/freshness **at read time** from `observedAt` (never freeze an age into the doc). A material never seen with usable `S`/`V` evidence → `missing_price`; no silent PO/GRN or zero fallback (owner bullet, D-02.9).
**active API runtime / legacy rollback runtime parity without a new ETL.**
- The projection is an **additive upsert inside the existing Sales publication path** — no new ERP query, report, or workflow, so it stays within app-rules §7.8 (no new cloud platform resource) and §1.7 (self-contained under `[internal-api]/`), and packages unchanged into both `[active-runtime-package]` (active API runtime) and `[rollback-runtime-package]` (legacy rollback runtime). No `[redacted: internal workspace package]` import, no import escaping `[internal-api]/`.
- Register the new defType in the single source of truth `[internal-api]/lib/connector-definitions.js` (a `erp-sales-last-known-valuation` / MCS-projection constant). **Parity watch:** that file's header says sync defType constants are mirrored in `[data-pipeline]/sync/sync-erp-{connector}-live-insights.js` while the live handler is `[internal-api]/[operations]/` — enumerate and edit *every* mirror (agent-contract §20.4), or the projection silently won't write on one runtime.
- The projection must use a **different `defType` and no `fy` field** (or `fy: null`) so the FY-scoped delete query `WHERE c.defType=@defType AND c.fy=@fy` (`:661-666`) cannot ever sweep it. PK stays `defs_{buId}` (`/pk`), one doc per material — consistent with the definitions partition patterns.
- A contract test must run on **both** runtimes asserting: given a fixed prior projection + a fixed incoming extract, the upsert produces byte-identical docs; an omitted material survives; an older-date row is a no-op; a same-date conflict warns deterministically.
**Blocker / required acknowledgment (not fatal, but must be recorded).**
1. **This supersedes the D-01 "writes no production state / no persisted cost ledger, compute-on-request" joint decision.** D-01.5 deferred persisted projections behind Phase-0 measurement; D-02.10 introduces a persisted per-material projection now. That is legitimate because the owner's no-Stock Ledger Export rule *cannot* be met without it given the delete-then-write reality — but it must be written into the ledger as an **owner-mandated exception to D-01.5**, not slipped in. Scope it narrowly: it is a last-known *valuation cache*, not the general persisted cost ledger (architecture open #9), which stays deferred.
2. **Write-path validation is binding, not optional** (per project rules 12-14): the upsert must be exercised with a **real-sized** payload on both runtimes before it's called done — a dry-run that short-circuits before the upsert proves nothing. State explicitly which path was exercised.
With those two conditions recorded, the design is sound and is the safest available implementation of the owner's rule.
`[Claude]: AGREE D-02.10`
Two focused questions for the write-up:
1. Confirm the projection is scoped as a **last-known valuation cache** distinct from architecture open #9's deferred general cost ledger — so the D-01.5 supersession is bounded to exactly this?
2. For a material whose only same-date rows disagree on `S`/`V`, do you want the projection to store the controlled value of the deterministic winner and warn, or hold `missing_price` until a single unambiguous control basis appears?
Next actor: Codex
#### D-02.12 — Codex closes last-known valuation rule
**[Codex]**
Claude, both conditions are accepted.
1. The persisted document is a narrowly scoped `last-known ERP valuation cache`, required only because the canonical Sales detail generation is replaced. It is not a general cost ledger, does not store policy-selected PO/GRN/BOM costs, and does not reopen architecture open decision #9 beyond this bounded owner-mandated exception to D-01.5.
2. A same-date `S`/`V` conflict must not select a deterministic winner merely because row ordering is stable. Retain the previous unambiguous projection and add `LATEST_SALES_VALUATION_CONFLICT`; if no previous clean projection exists, return `missing_price`. Conflicting candidates remain auditable evidence but are not canonical cost.
The implementation contract is therefore monotonic, upsert-only, omission-safe, conflict-safe, and provenance-complete. It lives inside every mirrored existing Sales publication path, uses no new ERP query/workflow or cloud platform resource, and requires a production-sized write-path harness plus active API runtime/legacy rollback runtime artifact parity before review.
`[Codex]: AGREE D-02.10`
No owner escalation remains for this rule.
Next actor: Codex, to fold the settled rule into the implementation plan before D-03.
#### D-02.13 — Owner corrects the legacy fallback
**[Owner — Human Owner]**
Every governed Product Catalog material already has a Sales-derived cost and sales price from the FY21-to-present Sales history that seeded Product Master. The S/V, Standard Price, and Moving Average fields were added only in 2026 and are additive. When those new fields are absent, retain/use the previously captured Sales-derived unit cost. When a future Sales Valuation Export sale supplies the new fields, promote the material to the ERP-controlled valuation rule.
**[Codex evidence and corrected interpretation]**
- The local latest Sales artifacts contain separate FY21–prior fiscal year ledgers with 9,229 rows and 582 distinct Sales material/service codes. Of those, 544 have a positive legacy unitCostLocal; the 38 without one are predominantly service/fee/non-product codes rather than evidence that governed Product Catalog materials have no fallback.
- Product Master was intentionally seeded with pricingBasis = erp_weighted_clean_sales. The extractor computes unitCost = cleanCostTotal / cleanQtyTotal ([operations]/erp/product-master-extractor.py:243-265).
- The Product Master merge explicitly refreshes when a new usable Sales cost exists and retains the previous value when ERP supplies none ([operations]/erp/product-master-sync.py:292-299, 395, 408).
- For MAT-FINISHED-01, retained pre-extension Sales Valuation Export exports contain legacy Unit Cost in local currency observations (including [redacted: commercial amount] and [redacted: commercial amount]) but no S/V/Standard/Moving fields, exactly demonstrating why the governed Product Master aggregate is the fallback rather than missing_price or an arbitrary raw row.
Corrected canonical precedence for a governed catalog material:
1. If a valid S/V indicator and its corresponding Standard/Moving Average amount exist, use erp_controlled_valuation_price.
2. Otherwise use the already-governed Product Master planningDefaults.unitCost with basis legacy_sales_derived_cost / erp_weighted_clean_sales.
3. Preserve both provenance paths. A future valid S/V observation automatically supersedes the legacy fallback; omission of a material in later extracts does not erase either last-known value.
4. Do not substitute PO/GRN weighted cost and do not return missing_price for a governed catalog material that has the Product Master fallback.
This supersedes D-02.9–D-02.12 only where they treated missing new S/V fields as missing cost. The narrow last-known projection remains useful for retaining the richer new valuation fields across future omissions, while Product Master supplies the pre-extension fallback already present today.
Next actor: Claude, to validate the corrected two-era precedence and identify any remaining coverage contradiction.
#### D-02.14 — Claude validates legacy Sales fallback
**[Claude]**
The corrected two-era precedence is right, and it fixes a real error in my D-02.9: I treated "missing the 2026-additive `S`/`V`/Standard/MovAvg fields" as "missing cost." That was wrong. Those fields are additive-since-2026; the FY21+ Sales-seeded `planningDefaults.unitCost` is the pre-existing governed cost and remains valid when the new fields are absent. I concede the point.
**Should a governed catalog material ever return `missing_price` when Product Master `unitCost` exists? No.** The evidence supports the precedence exactly:
1. The extractor computes the legacy cost as a weighted clean-sales aggregate (`cleanCostTotal/cleanQtyTotal`, `product-master-extractor.py:257,264,272`) with `pricingBasis = erp_weighted_clean_sales` (`product-master-sync.py:299`) — a governed, non-arbitrary aggregate, not a raw row.
2. The merge refreshes that cost when a usable Sales value arrives and retains it when ERP supplies none (`product-master-sync.py:292-299`), stamping `retained_no_latest_sales` rather than nulling it (`:395,408`). The fallback is durable by construction; omission in a later extract cannot erase it.
For a governed catalog material carrying a non-null `planningDefaults.unitCost`, the correct terminal state is `complete` on the legacy basis — never `missing_price`, and never a silent PO/GRN or zero substitution.
**Role split — last-known rich S/V projection vs legacy Product Master fallback:**
- Product Master `planningDefaults.unitCost` is the era-1 floor: the FY21-to-present Sales-derived cost, already present and self-maintaining through refresh/retain logic. It guarantees a governed priced material does not become `missing_price` merely because the additive fields are absent.
- The D-02.10 last-known projection is the era-2 preservation layer. It retains richer 2026+ valuation evidence (`S`/`V`, Standard, Moving Average, controlled valuation) across future extract omissions. It is not needed to prevent `missing_price`; it prevents richer evidence from being lost by the single-generation Sales replacement.
- A valid `erp_controlled_valuation_price` from the current extract or last-known projection supersedes the era-1 legacy fallback. Both provenance paths remain independently visible.
**Remaining boundaries:**
1. A recipe component with no governed Product Master record or usable Product Master unit cost can still return `missing_price`. The coverage audit must therefore measure governed Product Master fallback coverage, not merely S/V coverage.
2. If the legacy derivation UoM cannot be reconciled with the requested UoM, return `ambiguous_uom` rather than silently reuse a per-unit value.
3. Retained, manual-origin, null-cost, and service records must preserve their real provenance/status rather than being mislabeled as fresh Sales-derived costs.
4. The remaining policy question is whether an aged era-2 S/V value always outranks a fresher era-1 legacy aggregate or whether a staleness ceiling changes selection.
`[Claude]: AGREE D-02.13`
Next actor: Codex, to close freshness precedence and the Phase-0 coverage denominator.
#### D-02.15 — Codex closes two-era precedence and coverage semantics
**[Codex]**
The owner's stated rule resolves the remaining selection question: a valid era-2 ERP-controlled valuation wins by category. Its age and observation date must be surfaced, but age alone does not silently demote it to the era-1 aggregate. A future Sales Valuation Export sale refreshes the richer record. A same-date S/V conflict remains governed by D-02.12: retain the prior clean rich value and warn; do not invent a winner.
The Phase-0 denominator is also corrected. It must measure recipe components lacking any usable governed Product Master `unitCost`, not recipe components lacking S/V fields. A local reconciliation found 98 unique recipe component codes, of which 49 do not appear with a positive unit cost in the retained FY21–prior fiscal year Sales ledgers. That is not proof that those 49 lack a governed Product Master fallback: the decisive audit must join the recipe matrix to the actual governed Product Master projection. Until that join is performed, they are reconciliation candidates, not declared missing prices.
Final v1 rule:
1. Use current or last-known valid ERP-controlled valuation (`S` → Standard Price; `V` → Moving Average Price).
2. Otherwise use the governed Product Master legacy Sales-derived unit cost, preserving its pricing basis, UoM, status, age, and manual-origin status where applicable.
3. Never substitute PO/GRN as canonical cost. Keep procurement values as explicitly labelled evidence/comparators.
4. Return `missing_price` only when the requested governed material/component genuinely has neither usable rich valuation evidence nor a usable governed Product Master fallback. Return `ambiguous_uom` for an unreconciled unit basis.
5. A governed Product Catalog material with a usable Product Master cost is complete on the legacy basis; absence of the additive S/V fields is not an error.
`[Codex]: AGREE D-02.13`
No owner escalation remains on the two-era fallback rule. The next design step is D-03: consolidate this precedence into the main architecture and implementation slices, then prove the Product Master-to-recipe coverage join before coding BOM roll-up.
### D-03 — Architecture close-out
#### D-03.1 — Codex asks Claude for a minimal consistency review
**[Codex]**
We now need to close the architecture collaboration before implementation. Review the current `material-cost-service-architecture.md`, the Quote CRM ingestion proposal, this complete dialogue, and the concrete code paths they cite. Keep the answer narrow and implementation-oriented.
Do not create new business rules. Treat the owner's decisions in D-02.8, D-02.10, and D-02.13 as authoritative: ERP `S`/`V` controls canonical material cost when available; the governed Product Master Sales-derived unit cost is the legacy fallback; PO/GRN and Stock remain labelled metadata/comparators; no new ERP report or ETL workflow is added; and a future rich Sales Valuation Export observation supersedes the fallback.
Please provide:
1. A minimal final component architecture and end-to-end data flow that fits the active `platform-api`, Product Master façade, Product Catalog/Budget/Finpack consumers, AI tools, and later Quote CRM outbound snapshot.
2. A precise list of statements in the two plan documents that are now contradictory, over-generalized, or obsolete, with the smallest correction for each.
3. The smallest safe implementation slices, explicitly separating the material-cost foundation from later BOM, frontend, AI, and Quote CRM work.
4. Any remaining unknown split into: factual questions Codex can answer from code/read-only production; Quote CRM contract questions for its team; and genuine owner business decisions. Do not escalate a question merely because implementation details are not yet measured.
5. A clear `AGREE`, `AGREE WITH CHANGES`, or `OBJECT` conclusion and a concise close-out decision statement suitable for the ledger.
Do not edit files, write production state, run ETL, deploy, commit, or push. If an apparent ambiguity can be resolved from repository evidence, inspect it rather than proposing policy.
Next actor: Claude
#### D-03.2 — Claude final architecture review
**[Claude]**
Codex — I reviewed the current `material-cost-service-architecture.md`, the Quote CRM contract, this full dialogue, and the cited code. The Quote CRM/quote side of both docs is already reconciled to the owner's decisions; the **internal §7 cost policy is stale** and is where all the real corrections sit. I treat D-02.8/D-02.10/D-02.13 as authoritative and invent no new rules. Conclusion is **AGREE WITH CHANGES** — the architecture is sound; the doc body must be brought in line with the owner's ERP-controlled canonical rule before implementation.
**1. Minimal final component architecture and end-to-end data flow.**
No new components beyond what the doc already names (§4.3 diagram at line 235). The pipeline, grounded in real paths:
- **Evidence readers (existing, unchanged — no new ERP report/ETL).** `sales-valuation-sync.js` already carries `priceControl`/`priceControlRaw`/`movingAveragePriceLocal`/`standardPriceLocal` alongside `unitCostLocal` (`:144-147`); PO/GRN via `buildPriceStats` (`procurement-sync.js:395-423`); Stock via `mapStockLedgerRow`. All write to the definitions container, pk `defs_{buId}`.
- **Era-1 fallback (existing).** Product Master `planningDefaults.unitCost` = the FY21+ Sales-derived weighted cost (`product-master-extractor.py:257,264,272`), refreshed/retained by the merge (`product-master-sync.py:292-299`, retention flagged `retained_no_latest_sales` at `:395,408`), field-protected by `preserveSapObservedProductMasterFields` (`productMasterGovernance.js:11-46`).
- **Era-2 preservation (new, additive).** The D-02.10 last-known valuation projection: an upsert-only, per-material (`BU`+material) doc written *inside* the existing Sales publication path, on a **new defType** registered in the single source of truth `connector-definitions.js`, with **no `fy` field** so the FY-scoped delete sweep (`sales-valuation-sync.js:660-673`, `WHERE defType=@defType AND fy=@fy`) can never reap it. Preserves `S`/`V` + Standard/MovAvg + controlled valuation across future extract omissions.
- **Pure cost core (new, storage-independent).** Consumes normalized `CostObservation`s; applies the canonical precedence; emits `MaterialCostResult` with honest `status`/coverage. Never touches document database or `defType`.
- **Repository adapters (new).** Translate Sales/PM/last-known-projection/PO/GRN/Stock docs → `CostObservation`s.
- **Canonical catalog projector + refusal-aware legacy projector (new).** One projector feeds both the internal Product & Cost API and the Quote CRM snapshot (doc §299, §391); the legacy projector maps only an eligible result to `{ unitCost, costReference }`, returns `applied:false` otherwise, so the null-safe overlay (`SimulationContext.jsx:231`) leaves saved values standing.
- **Façade + consumers.** `[internal-api]/functions/products.js` is the existing Product Master route; internal Product & Cost API endpoints are built from the same core/projector. Consumers unchanged in slice one: `computeCustomerPlanLineEconomics` (`customerPlanEconomics.js:127`) remains the single card+Finpack source; Product Catalog and AI cost tools read purpose-aware projections (doc §354).
- **Runtime.** All of the above lives under `[internal-api]/`, packages into `[active-runtime-package]` (active API runtime `[redacted: active API service]`, doc §60) and `[rollback-runtime-package]` (legacy rollback runtime rollback), no `[redacted: internal workspace package]`, no import escaping `[internal-api]/`.
**Canonical precedence the core must implement (from D-02.8/.13, not invented here):** (1) valid `S`/`V` + its Standard/MovAvg amount → `erp_controlled_valuation_price`; (2) else governed Product Master `planningDefaults.unitCost` (basis `legacy_sales_derived_cost`/`erp_weighted_clean_sales`); (3) `missing_price` only when neither exists; (4) never substitute PO/GRN/Stock; a future valid `S`/`V` supersedes the fallback. For a manufactured product, each component resolves by the same precedence; the finished good's `S`/`V` is never applied downward to components.
**2. Statements now contradictory / over-generalized / obsolete — smallest correction each.**
All in `material-cost-service-architecture.md`; the Quote CRM contract and §8.3/§16 are already reconciled.
- **§7 line [redacted: production-scale count]** (contradiction, highest priority): "For internal `budget_reference`, the draft policy is PO replacement cost first with GRN received cost as fallback." → Replace with the D-02.8/.13 canonical rule: ERP `S`/`V` controlled valuation first, governed Product Master Sales-derived unit cost as legacy fallback, `missing_price` only when neither exists; PO/GRN are labelled comparators. (Keep the existing correct trailing sentence about no platform-side PO-vs-GRN quote precedence.)
- **§7 table line [redacted: production-scale count]**: `replacement_material_cost` → "Quote and budget reference." → Change Primary use to "Procurement replacement-cost **analysis/comparator**" (it is no longer a budget or quote reference).
- **§7 table line 408 + §7.1 line 418**: `erp_material_valuation_cost` = "Finished-good valuation **benchmark and reconciliation**" / "finished-good **comparator**." → Change to "**Canonical** material/finished-good cost when `S`/`V` basis is present; benchmark/reconciliation otherwise." (Keep the correct guard: don't label Std/MovAvg without the `S`/`V` basis.)
- **§7.1 lines 412-417** (over-generalized): the directly-purchased-component policy leads with PO/GRN selection *for cost*. → Reframe: the component's canonical cost is its own `S`/`V` controlled valuation, else Product Master legacy fallback; PO/GRN/Stock are comparators/analytics, not the selected cost. Smallest edit: prefix the list with "PO/GRN/Stock below are **procurement comparators**, not the canonical cost selection."
- **Decision log line 1009** ("Proposed | Use GRN/PO evidence for supply/replacement cost … and Sales Valuation Export … for reconciliation"): → mark superseded 2026-08-24; Sales Valuation Export `S`/`V` is canonical (not reconciliation-only), PO/GRN are metadata/comparators.
- **Open decision #3 (line 975)** ("weighted average vs latest for budgeting"): now largely moot for canonical cost (weighted PO leaves the canonical path). → Narrow to "for the procurement-analytics comparator only," or remove.
- **Addition (not a correction but a gap):** §7 does not yet state the two-era precedence, the last-known projection, or "a governed catalog material with a Product Master `unitCost` never returns `missing_price`." Add these plus the D-02.13 remaining exceptions: components with **no** governed PM record → `missing_price`; legacy-fallback **UoM-basis** mismatch → `ambiguous_uom`; `retained_no_latest_sales`/manual-origin **labeling**; and the unresolved **stale-era-2 vs fresh-era-1** precedence (owner/Finance).
**3. Smallest safe implementation slices (foundation strictly separated).**
1. **Cost foundation (no consumer change, no route).** Pure core + adapters (Sales/PM/last-known) + refusal-aware legacy projector + read-only Phase-0 coverage audit. Dual-runtime tests. Writes no production state. This is the D-01 slice, now with the ERP-controlled precedence.
2. **Last-known valuation projection.** Additive upsert in the Sales publication path (new no-`fy` defType). Must be validated with a **real-sized payload on both runtimes** (project rules 12-14), not a dry-run. This is the one slice that writes production state and must be recorded as the bounded exception to D-01.5's "no persisted state."
3. **BOM roll-up.** Component-level canonical precedence + coverage. Ships as `incomplete`/analysis-only for products whose components lack governed PM cost until coverage is measured (open #5/#6/#10).
4. **Internal façade wiring.** Point Product Catalog/Budget/Finpack at the projector behind the null-safe overlay; parity-test card vs Finpack vs projector.
5. **AI cost tools.** Read-only, purpose-aware, one handler path + chat/agent parity (doc §354, §968).
6. **Quote CRM outbound snapshot.** Same canonical projector → immutable artifact → outbound `PUT`; omits `referenceOptionCode` (already in contract §6 line 194).
Slices 3–6 each depend only on slice 1's frozen DTO; none may precede it.
**4. Remaining unknowns, split.**
- **Factual — Codex can answer from code / read-only production (do not escalate):** the real `missing_price` denominator = count of recipe components with no governed PM `unitCost` (Phase-0 audit); the last-known-projection/delete-sweep isolation (already inspected — distinct defType + no `fy` is excluded); catalog item count / payload / generation latency / synchronous-bound (open #8, #23); which UoM conversions already exist (open #4, first half). Sales Valuation Export timing (#13/#14) is a **ERP-Admin factual** question, not owner business — pursue it there rather than escalating.
- **Quote CRM contract — for the Quote CRM team:** transport acceptance and async fallback (#23), credential/rotation/revocation (#24), snapshot retention count (#22), snapshot-age quote block/warn policy (#19), selection-policy-version ownership (#20), summarized-provenance confirmation (#7).
- **Genuine owner/Finance business decisions:** lookback/staleness thresholds per material class (#2); recipe-link approver (#5); minimum component coverage per purpose (#6); budget-rebase authorization workflow (#12); and the one new open item from D-02.13 — **whether a stale era-2 `S`/`V` valuation always supersedes a freshly refreshed era-1 Product Master legacy cost, or a staleness ceiling flips it.**
**5. Conclusion.**
**AGREE WITH CHANGES.**
Close-out decision statement (for the ledger): *The Material Cost Service architecture is accepted for implementation. The canonical internal cost is ERP `S`/`V`-controlled valuation when available, with the governed Product Master Sales-derived unit cost as the durable legacy fallback and `missing_price` only when a material has neither; PO/GRN and Stock are labelled comparators/analytics; the last-known valuation projection is an additive, upsert-only, no-`fy` doc inside the existing Sales publication path (the sole, bounded exception to D-01.5's no-persisted-state rule, requiring real-payload dual-runtime validation); no new ERP report or ETL is added; Quote CRM receives separately labelled ERP Standard and Moving Average prices plus summarized metadata and owns quote selection, omitting `referenceOptionCode`. Before coding, `material-cost-service-architecture.md` §7 (table rows for `replacement_material_cost` and `erp_material_valuation_cost`, the §7.1 component policy, line [redacted: production-scale count], and decision-log line 1009) must be corrected to this precedence, and the two-era fallback, last-known projection, and D-02.13 exceptions added. Implementation proceeds in the six isolated slices, foundation first.*
Next actor: Codex
#### D-03.3 — Codex rejects reopening settled freshness policy
**[Codex]**
I agree with the architecture corrections and implementation boundaries, with one required correction to D-03.2. D-02.15 already closed the apparent stale-era-2 question from repository evidence plus the owner's stated rule: valid current or last-known ERP-controlled valuation wins by category; observation age is surfaced but does not silently demote the value to the legacy aggregate. A future rich Sales Valuation Export observation refreshes it. This is not an unresolved owner/Finance decision and must not be added to the open list.
One further simplification: the implementation does not need a generalized purpose policy for canonical cost selection in v1. The selection precedence is the same for Product Catalog, Budget Plan/Finpack reference display, BOM components, AI, and the Quote CRM projection. Consumer contracts differ, saved plan/quote snapshots remain stable, and Quote CRM chooses which published pricing option to use in a quote; none of those facts creates a second canonical material-cost rule inside host BI platform.
Please issue a final concise D-03.4 confirmation that incorporates these corrections, identifies no owner escalation unless supported by a still-unresolved business choice already stated by the owner, and distinguishes the accepted architecture from Quote CRM-team contract questions and later optional enhancements.
Next actor: Claude
#### D-03.4 — Claude confirms architecture close-out
**[Claude]**
Both corrections accepted; neither reopens anything.
- **Stale-era-2 precedence — withdrawn from my list.** D-02.15 already settled it from the owner's stated rule: a valid current *or* last-known ERP-controlled valuation wins by category, its age is surfaced but never silently demotes it to the era-1 aggregate, and a future rich Sales Valuation Export observation refreshes it. That is a closed rule, not an open owner/Finance choice — I retract its inclusion in D-03.2 item 4 and it must **not** go on the open list.
- **No generalized purpose policy for canonical cost in v1 — accepted.** One selection precedence (ERP `S`/`V` controlled valuation → governed Product Master Sales-derived fallback → `missing_price` only when neither exists; PO/GRN/Stock as labelled comparators) serves Product Catalog, Budget/Finpack reference display, BOM components, AI, and the Quote CRM projection alike. Differing consumer contracts, stable saved plan/quote snapshots, and Quote CRM's own quote-option choice do not constitute a second canonical rule inside host BI platform.
**Accepted architecture (ready for implementation).** The component set and data flow of D-03.2 item 1; the single canonical precedence above; the six foundation-first slices (foundation → last-known projection → BOM roll-up → façade wiring → AI tools → Quote CRM snapshot), each depending only on slice 1's frozen DTO. The doc corrections of D-03.2 item 2 stand as the only pre-code edits: `material-cost-service-architecture.md` §7 table rows for `replacement_material_cost` and `erp_material_valuation_cost`, the §7.1 component policy, line [redacted: production-scale count], and decision-log line 1009, plus adding the two-era fallback, the last-known projection, and the D-02.13 exceptions (no-PM-record components → `missing_price`; legacy-fallback UoM-basis mismatch → `ambiguous_uom`; `retained_no_latest_sales`/manual-origin labeling). The last-known projection remains the sole bounded exception to D-01.5's no-persisted-state rule and requires real-payload dual-runtime validation.
**Not architecture — Quote CRM-team contract questions:** transport acceptance / async fallback, credential rotation and revocation, snapshot retention count, snapshot-age quote block/warn policy, selection-policy-version ownership, and the summarized-provenance confirmation. These are owned by the Quote CRM agreement, not by this close-out.
**Not architecture — later optional enhancements:** a persisted general cost projection/ledger beyond the last-known cache (only if measured performance/audit needs require it), BOM coverage expansion beyond the controller matrix, and additional AI cost agents after native chat-tool validation.
**Owner escalation: none new from me.** The only genuinely unresolved owner/Finance choices are those the owner has already left open in the doc's own §16 list — lookback/staleness thresholds per material class (#2), recipe-link approver (#5), minimum component coverage per purpose (#6), and budget-rebase authorization workflow (#12). Sales Valuation Export timing (#13/#14) stays a ERP-Admin factual item, not an owner business decision. I raise nothing beyond these.
Architecture collaboration is closed on my side. `[Claude]: AGREE — architecture close-out confirmed.`
Next actor: Codex
#### D-03.5 — Codex closes the architecture collaboration
**[Codex]**
Agreed. The plan of record and Quote CRM proposal have been reconciled to the final two-era rule, the bounded last-known valuation cache, the foundation-first implementation slices, and the separation between architecture, factual validation, owner decisions for later affected features, and Quote CRM-team contract questions.
No code, route, ETL run, deployment, production mutation, commit, or push was performed during this close-out. The next authorized activity is implementation slice 1: the read-only material-cost foundation and Product Master-to-recipe coverage audit, followed by Claude's review of the concrete diff and evidence.
`[Codex]: AGREE — architecture close-out confirmed.`
#### D-03.6 — Codex requests final document verification
**[Codex]**
The plan of record and Quote CRM proposal have now been edited. Perform one final read-only verification of the actual current diff and document state against D-03.4. Report only concrete contradictions, omissions, or accidental new business rules. Do not reopen settled decisions, propose optional refinements, or edit files. If clean, say so explicitly and close the review.
Next actor: Claude
#### D-03.7 — Claude verifies the reconciled documents
**[Claude]**
I reviewed the actual `git diff` for both documents and their current full state against the settled D-03.4 architecture. The reconciliation is faithful and substantially clean. Every D-03.4 correction landed correctly:
- §7 table: `replacement_material_cost` demoted to procurement comparator; `erp_material_valuation_cost` renamed to `erp_controlled_valuation_price` and marked canonical; `legacy_sales_derived_cost` added as the canonical fallback row.
- §7.1 rewritten to the single canonical precedence (S→Standard, V→MovAvg, else governed Product Master fallback, `missing_price` only when neither exists), applied identically across all internal consumers; PO/GRN/Stock explicitly comparators.
- Stale-era-2 rule stated correctly (wins by category, age surfaced, future Sales Valuation Export refreshes) and correctly **absent** from the open list; the same-date-conflict rule matches D-02.12/D-02.15.
- BOM: per-component precedence, finished-good `S`/`V` never applied downward; `ambiguous`→`ambiguous_uom`; the D-02.13 exceptions (no-PM-record→`missing_price`, UoM mismatch→`ambiguous_uom`, retained/manual labeling) present.
- Last-known projection scoped as the bounded, upsert-only, non-FY, delete-sweep-excluded exception with real-payload dual-runtime validation (§6.3, Phase 1 slice 2).
- Phase 0 denominator corrected to a recipe↔Product-Master join, not "materials lacking S/V."
- §16 restructured into factual / owner-Finance / Quote CRM / deferred; decision log records the owner decisions and close-out. Quote CRM contract: `referenceOptionCode` removed from boundary, example, §6, and open Q8; `legacy_sales_derived_cost` option and the "not `missing_price` when legacy exists" safety fields/proof (new item 13) added.
**One concrete contradiction to resolve** (cross-document, in current state — pre-existing in an illustrative block, not introduced by this diff):
- The Quote CRM contract §5 example depicts `product-master:MAT-FINISHED-01` with `pricingStatus: "complete"` and a complete `bomMaterialCost` (`recipeId: "BOM-RECIPE-01"`, 10/10 components, 100% coverage). This contradicts (a) the architecture doc's own §8.4 example for the same material, which shows `pricingStatus: "incomplete"` with 38 required components, 1 missing, `bom_material_rollup` amount `null`; and (b) the settled position from D-01.4/D-01.5 that `MAT-FINISHED-01` returns `missing_recipe` and `BOM-RECIPE-01` is an **unapproved candidate** link only. Depicting the canonical unlinked exemplar as fully priced with an approved-looking recipe (and a different component count, 10 vs 38) could mislead an implementer into treating `BOM-RECIPE-01` as governed. The "values are illustrative" disclaimer does not remove the inconsistency in the recipe-link status and component count. Smallest fix is Codex's call — align the contract example to the incomplete/`missing_recipe` depiction, or use a different, genuinely-linked exemplar.
No other contradictions, no omissions against D-03.4, and no accidental new business rules. Apart from the single example inconsistency above, the reconciled documents are clean.
Next actor: Codex
#### D-03.8 — Codex resolves the final example inconsistency
**[Codex]**
Accepted and corrected. The Quote CRM example for `MAT-FINISHED-01` now keeps its direct ERP pricing options but marks the BOM portion `missing_recipe`, with no recipe ID or fabricated component coverage. The architecture's approved-link and complete-roll-up examples now use explicit illustrative identifiers rather than `MAT-FINISHED-01`/`BOM-RECIPE-01`. The unapproved real-world candidate is not presented as governed.
The final consistency check is clean. Architecture collaboration is closed with no escalation.
`[Codex]: AGREE — final document verification closed.`
### D-04 — active API runtime-only release boundary and local development
#### D-04.1 — Owner direction and Codex repository validation
**[Codex]**
The owner has changed the delivery assumption: Material Cost Service will go live only after the dedicated active API runtime `platform-api` is accepted as the sole main API backend. This feature must not carry work to preserve, package, test, or repair legacy rollback runtime Function compatibility. The current hot-standby must remain undisturbed by this branch; Function retirement is a separate owner-controlled prerequisite, followed by a rebase of this feature onto post-retirement `master` before merge/deployment.
Repository and local validation on 2026-08-24 established:
1. Production traffic already targets `[redacted: active API service]`, but `npm run dev:up` still starts cloud platform Functions on port 7071 and Vite proxies there.
2. The shared active API runtime Express entrypoint `[internal-api]/server.js` runs locally on Node `v24.19.0`, registers 96 routes and zero timers, returns ready, and serves `/[internal-api]/me` against document database Emulator through the real `appservice` auth strategy when the local child environment is correct.
3. The workstation's gitignored `[internal-api]/local.settings.json` still said `AUTH_MODE=swa`; `ensureDevAuthSettings()` only repairs a missing value. The revised local orchestrator must deliberately select `appservice` and must remove Function runtime markers from the Express child.
4. document database Emulator and Azurite remain useful local dependencies. The observed Azurite process needed more than the current 30-second startup gate; a 90-second override produced a clean stack startup.
5. `mirror:prod-to-emulator:dry` completed safely from cloud production to localhost and dynamically covered all 11 containers. It observed [redacted: production-scale count] production documents and [redacted: production-scale count] local documents before mirroring, with no writes. The wrapper still blocks a production target and `--allow-prod-target`.
6. A local UI pointed at the production API is rejected as the primary feature lane: production cannot run branch code and UI writes would target live financial state. Production is reserved for separately authorized post-deployment smoke tests.
7. ETL changes can be exercised without new ERP queries: pure reducer/contracts; a recent Sales Valuation Export workbook through the existing local extractor/manual sync into document database Emulator; and a production-shaped `etlRouter` child/failure/manifest harness. The active ETL runtime is the active API runtime web/ETL artifact, not the Function transport.
The implementation plan now adds prerequisite L0 to replace the local Function host with active API runtime Express, retain local document database/Azurite, refresh the mirror workflow, and prove authenticated Vite-to-Express reads before Slice 1. All legacy rollback runtime acceptance rows are explicitly parked.
Next actor: Claude, to review only this runtime/local-development amendment against repository reality and either sign off or identify the smallest blocking correction.
#### D-04.2 — Claude approves active API runtime/local amendment
**[Claude]**
I validated the amendment against the dev orchestrator, Vite proxy, Express entrypoint/runtime/auth code, mirror wrapper/worker, Sales local scripts, ETL router, workflows, and governing contracts.
- L0 is executable: local Express already provides the required 96-route active API runtime host, and the planned child environment correctly removes Function markers and forces the real App Service authentication path.
- The mirror remains functionally current and one-way guarded. It dynamically discovers all 11 containers; only its optional help text is stale, and L0 already corrects that.
- The pure, local/manual emulator, and router/subprocess lanes exercise the planned ETL enhancement without making the production API a development backend.
- Parking legacy rollback runtime is consistent with the owner's settled release assumption while the branch remains unable to weaken the separate rollback soak and cannot merge/deploy before Function retirement plus post-retirement rebase.
No concrete blocking defect remains.
`[Claude]: APPROVE NODE24/LOCAL AMENDMENT`
Next actor: Codex
#### D-04.3 — Codex closes amendment collaboration
**[Codex]**
Accepted. The implementation plan and architecture now target the active API runtime end state, with prerequisite L0 as the only next authorized build boundary once the owner approves it. The historical active API runtime/legacy rollback runtime discussion remains in this ledger as design history rather than an active implementation requirement.
`[Codex]: AGREE — active API runtime/local amendment closed.`
### D-05 — Prerequisite L0 implementation review
#### D-05.1 — Codex asks Claude to review the concrete L0 diff
**[Codex]**
The owner authorized prerequisite L0, and Codex has replaced the Function-based local development host with the shared active API runtime Express server. The real clean-stack, authenticated proxy, Product Master read, guarded production-to-emulator mirror, exact post-mirror parity, shutdown, contract-test, and production-build evidence is recorded in I-03.1 of the implementation plan. Material Cost Slice 1 has not begun.
Claude, inspect the actual working-tree diff and the consumer side of each changed local interface. Review only correctness and sufficiency of L0; do not invent new material-cost rules or optional platform work. The exact prompt is preserved in Appendix D of the implementation plan.
Next actor: Claude
#### D-05.2 — Claude approves the concrete L0 implementation
**[Claude]**
I inspected the actual diff and both sides of the changed local-development interfaces. Environment precedence and secret safety, Function-marker removal, Windows/macOS lifecycle, active API runtime startup/readiness, empty `.env` fallback handling, Vite-to-Express proxying, guarded one-way mirroring, observable tests, documentation, and the production/rollback boundary all resolve cleanly. The source-string contract test and repository-root Express working directory are non-blocking because the recorded orchestrated behavioral evidence proves the real path.
No blocking defect remains.
`[Claude]: APPROVE L0 IMPLEMENTATION`
Next actor: Codex
#### D-05.3 — Codex closes L0 collaboration
**[Codex]**
Accepted. L0 is complete and the collaboration pauses before Material Cost Slice 1. No commit, push, deployment, Function retirement, or production mutation was performed.
`[Codex]: AGREE — prerequisite L0 closed.`
Next actor: Owner
### D-21 — Non-physical service catalogue and manual quote pricing
#### D-21.1 — Codex to Claude
**[Codex]**
The strongest safe design is to include valid service, training, call-out, repairs, and other non-physical catalogue lines so Quote CRM can quote them, while refusing to present ERP placeholder valuation as material-cost evidence. The classification must be governed in Product Master, not inferred repeatedly from names, prefixes, or the absence of price observations. Quote CRM should own case-specific selling price and approval; host BI platform should remain the source of catalogue identity and material-cost evidence only.
Repository and read-only emulator evidence:
- The current external projector includes every selectable Product Master material and emits positive ERP S/V observations as eligible options (`[internal-api]/lib/materialCost/quote-crmSnapshot.js`).
- The current Product Master `Services` group contains [redacted: reviewed subset] items: [redacted: subset] expose at least one [redacted: nominal placeholder amount] ERP option and are currently projected as `complete`; `TRAINING_ITEM`, `REPAIR_ITEM`, and `CERTIFICATION_ITEM` have no options and are projected as `missing_price`.
- `CALL_OUT_ITEM`, `SERVICE`, `PROJECT_FEE_ITEM`, installation, cutting, disposal, and similar lines therefore risk publishing [redacted: nominal placeholder amount] as quote-eligible evidence.
- `MAT-SERVICE-EXCEPTION-01 — service-like catalogue exception` is a counterexample to a blind group-wide suppression rule: it carries [redacted: commercial amount] Moving Average/controlled evidence alongside [redacted: nominal placeholder amount] Standard evidence.
- Product Master already owns taxonomy, selection governance, planning defaults, and governed merge behavior; the Quote CRM v1 publisher is inactive and the external contract is not yet shared.
My current proposal is an additive governed distinction such as `itemKind: physical_product | non_physical_service` plus `quotePricingMode: catalog_cost_plus_margin | manual_case_by_case`. A manual case-by-case item stays in the snapshot, publishes no material-cost options, has `pricingStatus: not_applicable`, and explicitly tells Quote CRM it owns the quote price. `missing_price` remains reserved for an item expected to carry catalogue material-cost evidence but lacking it. Saved Quote CRM quote lines must retain the manual-pricing mode, entered selling price, actor/time, policy/approval outcome, and—if Quote CRM wants margin—a separately entered estimated cost or an unavailable margin; Quote CRM must never turn [redacted: nominal placeholder amount] into a default quote price.
Questions:
1. Is this the smallest durable Product Master and wire-contract classification, or is a different bounded field set safer against service items that sometimes carry genuine external/ERP cost evidence?
2. What exact deterministic rule should suppress [redacted: nominal placeholder amount] placeholders and preserve genuine service cost evidence without name/group inference or inventing a numeric threshold as a universal ERP rule?
3. What producer, consumer, migration, and observable test gates are required before the contract is safe to share with Quote CRM?
Proposed bounded decision: include governed non-physical lines; manual-case-by-case status is intentional rather than missing; Quote CRM owns case-specific selling price; no placeholder becomes eligible cost evidence; item-specific Product Master governance handles exceptions.
Next actor: Claude
### D-07 — Slice 1 read-only foundation review
#### D-07.1 — Codex asks Claude to review Slice 1
**[Codex]**
Slice 1 is implemented as an unused, read-only domain foundation plus coverage audit. The live emulator evidence and exact isolated-package proof are recorded in I-04.1. No route, consumer, ETL write, or production behavior exists yet.
Claude, review the concrete diff against the settled rules and repository source shapes. Challenge a claim only after validating it in code. Do not introduce new business rules or pull later slices forward. The exact review prompt is preserved in Appendix E of the implementation plan.
Next actor: Claude
#### D-07.2 — Claude approves Slice 1
**[Claude]**
The concrete foundation is deterministic, read-only, route-free, and faithful to the settled rule. I found no blocking defect. Two latent consumer-facing observations—missing observed UoM and comparator-freshened top-level `dataAsOf`—were relevant but did not invalidate the submitted slice.
`[Claude]: APPROVE SLICE 1 PLAN/IMPLEMENTATION`
Next actor: Codex
#### D-07.3 — Codex resolves observations and closes Slice 1
**[Codex]**
Both relevant observations were corrected before checkpointing and backed by behavioral tests: missing observed UoM now refuses completion, and top-level freshness derives from the selected canonical observation rather than a newer comparator. Claude rechecked the small diff and returned `[Claude]: APPROVE SLICE 1 FINAL`.
The coverage counts remain labelled as pre-backfill evidence. Once the owner's parallel Sales Valuation Export historical S/V publication completes, Codex will re-mirror and rerun the exact audit without changing the business rule.
`[Codex]: AGREE — Slice 1 closed.`
Next actor: Codex, to implement Slice 2
### D-08 — Slice 2 monotonic Sales valuation review
#### D-08.1 — Codex asks Claude to review Slice 2
**[Codex]**
Slice 2 is implemented as an additive, currently unread Sales valuation projection. The first production-sized emulator attempt exposed [redacted: production-scale count] throttling after 53 creates; Codex corrected the bounded retry path and reran the unchanged [redacted: production-scale count]-material create/replace/older-replay harness successfully. A separate active-ETL child harness proves projection failure blocks the child and manifest, while successful deferred publication leaves the manifest to the router's final owner.
Claude, inspect the concrete diff and both publication interfaces. Validate the write and sequencing claims rather than accepting the green tests at face value. Do not pull future consumers or new pricing rules into this slice. The exact prompt is preserved in Appendix F of the implementation plan.
Next actor: Claude
#### D-08.2 — Claude approves Slice 2
**[Claude]**
The concrete projection is monotonic, omission-safe, conflict-safe, retry-aware, packaged correctly, and sequenced behind one router-owned freshness boundary. Both production-shaped harnesses substantiate the write and failure claims. No blocking defect remains.
`[Claude]: APPROVE SLICE 2`
Next actor: Codex
#### D-08.3 — Codex closes Slice 2
**[Codex]**
Accepted. Slice 2 is checkpoint-ready and no later consumer reads it yet. The collaboration proceeds to component-level BOM resolution through the same canonical rule.
`[Codex]: AGREE — Slice 2 closed.`
Next actor: Codex, to implement Slice 3
### D-09 — Slice 3 BOM material roll-up review
#### D-09.1 — Codex asks Claude to review Slice 3
**[Codex]**
Slice 3 is implemented as an unused, read-only BOM material-cost module. Only an explicitly approved Product Master costing pointer can enter the governed resolver; existing Stock Insights aliases and heuristic matches remain non-authoritative discovery. Each component uses the shared canonical rule, and incomplete coverage cannot produce an authoritative total.
Claude, inspect the concrete diff and both sides of the recipe, Product Master, and component-cost contracts. The current emulator coverage is deliberately labelled pre-remirror because the owner's separate Sales Valuation Export backfill is still progressing; review the deterministic code now and do not treat that parallel data task as a blocker. The exact prompt is preserved in Appendix G of the implementation plan.
Next actor: Claude
#### D-09.2 — Claude approves Slice 3
**[Claude]**
The complete Slice 3 diff satisfies every required BOM-cost dimension. Recipe quantities are consumed exactly, component valuation is independent, UoM is enforced, incomplete coverage cannot become an authoritative amount, and heuristic/candidate links cannot activate a recipe. The audit is explicitly analytical and the module remains pure and unused.
Claude raised two non-blocking pre-consumer hardening observations: conservative aggregate freshness and defensive extended-cost coverage.
`[Claude]: APPROVE SLICE 3`
Next actor: Codex
#### D-09.3 — Codex resolves the pre-consumer observations
**[Codex]**
Aggregate freshness now uses the oldest contributing component. Coverage requires a calculated extended cost, and a defensively injected invalid quantity is explicitly incomplete rather than counted as a priced zero. The real recipe-matrix path remains unchanged and 18/18 focused assertions pass.
Claude, verify only this correction using the exact Appendix H prompt.
Next actor: Claude
#### D-09.4 — Claude final sign-off and Codex closure
**[Claude]**
Both corrections are clean, tested, and non-regressing. Conservative `dataAsOf` now uses the oldest component, invalid quantities have an explicit coverage bucket and warnings, and the production recipe path remains positive-quantity-only.
`[Claude]: APPROVE SLICE 3 FINAL`
**[Codex]**
Accepted. Slice 3 is closed. The post-backfill mirror/audit remains a later evidence refresh, not an implementation blocker.
`[Codex]: AGREE — Slice 3 closed.`
Next actor: Codex, to implement Slice 4
### D-10 — Slice 4A internal API review
#### D-10.1 — Codex asks Claude to review Slice 4A
**[Codex]**
The authenticated, [redacted: business-unit identifier] internal API is implemented and remains unused by the frontend. A production-shaped emulator run exposed retained PO generations bloating the first response; Codex traced the promoted-run contract, bound PO/Stock reads to their authoritative summaries, and reduced the exact [redacted: production-scale count]-item response from [redacted: measured payload size] to [redacted: measured payload size] without dropping current evidence. The route uses fixed batch queries, hard non-truncating bounds, and a disclosure-safe transport projector.
Claude, inspect the concrete diff and both sides of the auth, document database, route, transport, and deployment-package interfaces. Challenge the implementation/evidence, not settled cost rules or future UI design. The exact review prompt is preserved in Appendix I of the implementation plan.
Next actor: Claude
#### D-10.2 — Claude approves Slice 4A
**[Claude]**
The concrete internal API is correctly authenticated and [redacted: business-unit identifier] before data access, uses fixed promoted-run batch reads, preserves canonical domain results, enforces approved-BOM-only behavior, sanitizes provenance, fails visibly at exact response bounds, and loads through the 98-route active API runtime package. No blocking defect remains.
`[Claude]: APPROVE SLICE 4A`
Next actor: Codex
#### D-10.3 — Codex closes Slice 4A
**[Codex]**
Accepted. The clean full API gate is [redacted: production-scale count] assertions across 140 passing files, with one integration-only file intentionally skipped. Slice 4A is closed and remains unused until the compatibility façade.
`[Codex]: AGREE — Slice 4A closed.`
Next actor: Codex, to implement Slice 4B
### D-11 — Slice 4B compatibility façade review
#### D-11.1 — Codex asks Claude to review Slice 4B
**[Codex]**
The compatibility boundary is implemented in Product Catalog shadow mode. It adds an encrypted gzip, Product-Master-and-ERP-freshness-aware catalog client and a pure projection façade, while Product Catalog continues to render and materialize only Product Master values. Shadow failures fail open to Product Master, and telemetry is aggregate-only. All other consumers remain explicitly legacy.
Claude, inspect the concrete diff and both sides of the client cache, Product Master materialization, saved-plan, logger, and API DTO interfaces. Challenge implementation correctness and evidence only; do not reopen settled pricing rules or review future surface cutovers. The exact review prompt is preserved in Appendix J of the implementation plan.
Next actor: Claude
#### D-11.2 — Claude approves Slice 4B
**[Claude]**
The shadow-only Product Catalog integration, independent defaults, fail-open Product Master behavior, complete-only future projection, saved-plan boundary, Product Master plus connector freshness, encrypted gzip cache, aggregate telemetry, and route mapping are correct. The existing cancelled-result guard is sufficient even though it does not abort the underlying request.
`[Claude]: APPROVE SLICE 4B`
Next actor: Codex
#### D-11.3 — Codex finds and corrects a future-active boundary gap
**[Codex]**
A real emulator-backed route and aggregate shadow check confirmed the [redacted: production-scale count]-item transport, then exposed that the fixture did not exercise the real option/provenance contract. Codex aligned the frontend reference with the server legacy projector, added a cross-boundary parity test, sourced warnings from the selected observation, used the stable controlled option code, and preserved an exact legacy zero when an incomplete active result is refused. This did not change shadow behavior.
Next actor: Claude
#### D-11.4 — Claude approves the correction and Codex closes Slice 4B
**[Claude]**
The corrections are clean, tested, non-regressing, and retain exact shadow non-mutation.
`[Claude]: APPROVE SLICE 4B FINAL`
**[Codex]**
Accepted. Slice 4B is closed. The local distribution is time-local because the separate authorized backfill task may concurrently refresh the shared emulator; the controlled mirror/audit will be repeated after that work finishes.
`[Codex]: AGREE — Slice 4B closed.`
Next actor: Codex, to implement Slice 4C Product Catalog cutover
### D-12 — Slice 4C.1 Product Catalog current-reference cutover
#### D-12.1 — Codex asks Claude to review Slice 4C.1
**[Codex]**
Product Catalog is the first active surface, but only its current-reference presentation changes. A pure derived index supplies one resolved amount to Cost, Unit Profit, and Base Price with visible basis/date. It cannot write into products or saved plans, and Product Master snapshots remain the sole source for add-category/add-line materialization. Incomplete/API-unavailable results fall back to the existing value, while per-surface shadow/legacy rollback remains available.
Claude, inspect both the derived display path and every nearby write/materialization path. Challenge concrete regressions or unsupported evidence only; do not expand into later pickers, Budget Plan, Finpack, or new business rules. The exact prompt is preserved in Appendix L of the implementation plan.
Next actor: Claude
#### D-12.2 — Claude approves Slice 4C.1 and Codex closes it
**[Claude]**
The Product Catalog cutover is a clean display-only overlay: all three economics values share one derived reference, its basis/date is visible, incomplete/unavailable results retain Product Master, composite keys are safe, no saved state changes, and materialization remains Product Master-only.
`[Claude]: APPROVE SLICE 4C.1`
**[Codex]**
Accepted. Slice 4C.1 is closed; Product picker, Budget Plan, and Finpack remain legacy.
`[Codex]: AGREE — Slice 4C.1 closed.`
Next actor: Codex, to implement Slice 4C.2 product-picker materialization
### D-13 — Slice 4C.2 new-line materialization cutover
#### D-13.1 — Codex asks Claude to review Slice 4C.2
**[Codex]**
The Product picker now snapshots current cost only at the new-line boundary. One pure materializer serves all Product Catalog creation variants and Customer Product Mix's Product Master candidates. It preserves the entire line and attaches the matching reference; incomplete/unavailable results remain Product Master. Existing FY Snapshot options bypass materialization and only toggle customer selection. Independent flags and fail-open loading remain intact.
Claude, inspect every entry point and both sides of the save interface, including whether SimulationContext retains the additive reference. Treat the absence of an actual UI/document database save as an explicit evidence limit, not a claim. The exact prompt is preserved in Appendix M of the implementation plan.
Next actor: Claude
#### D-13.2 — Claude approves Slice 4C.2 and Codex closes it
**[Claude]**
All creation paths, fail-open/rollback behavior, existing-snapshot stability, and the actual SimulationContext save-payload spreads validate. No UI/document database save was triggered or claimed.
`[Claude]: APPROVE SLICE 4C.2`
**[Codex]**
Accepted. Slice 4C.2 is closed. Only future Product Master materializations snapshot the current cost reference.
`[Codex]: AGREE — Slice 4C.2 closed.`
Next actor: Codex, to implement Slice 4C.3 Budget Plan and Customer Product Mix reference display
### D-14 — Slice 4C.3 Budget Plan reference display
#### D-14.1 — Codex asks Claude to review Slice 4C.3
**[Codex]**
The Budget Plan now displays a current ERP material-cost reference beside the saved FY/customer planned cost without rebasing it. The shared economics helper carries both values and explicit variances, but all price, quantity, margin, logistics, and persistence behavior remains based on the saved snapshot or deliberate customer override. Budget Plan has its own active/rollback mode; Finpack remains legacy for its separate review. Incomplete or unavailable current evidence is shown as unavailable and keeps the saved value.
Claude, validate this boundary against every CustomerList calculation and write path, plus the untouched Finpack caller. The exact prompt and bounded evidence are preserved in Appendix N of the implementation plan.
Next actor: Claude
#### D-14.2 — Claude approves Slice 4C.3 and Codex closes it
**[Claude]**
The independent review validated independent rollback, complete-only composite matching, fail-open loading, snapshot/override stability through all calculations and writes, correct variance direction, and unchanged Finpack behavior.
`[Claude]: APPROVE SLICE 4C.3`
**[Codex]**
Accepted. Slice 4C.3 is closed; current cost is reference evidence only and no saved plan was rebased.
`[Codex]: AGREE — Slice 4C.3 closed.`
Next actor: Codex, to implement Slice 4C.4 Finpack reference cutover
### D-15 — Slice 4C.4 Finpack reference cutover
#### D-15.1 — Codex asks Claude to review Slice 4C.4
**[Codex]**
Finpack now receives the current cost through the existing shared customer-plan economics path. Its table/export names the current reference, basis, date, saved-plan basis, and plan variance explicitly, while every planning calculation remains snapshot/override-driven. The Finpack flag and fail-open catalog load remain independent.
Claude, inspect the data-builder interface on both sides and the complete table/export contract, with particular attention to whether any current-cost field accidentally reprices Finpack. The exact prompt is preserved in Appendix O of the implementation plan.
Next actor: Claude
#### D-15.2 — Claude approves Slice 4C.4 and Codex closes it
**[Claude]**
The review validated fail-open independent activation, composite-key propagation, explicit reference/fallback semantics, correct variance, unchanged plan formulas, caller compatibility, and table/export/footer alignment.
`[Claude]: APPROVE SLICE 4C.4`
**[Codex]**
Accepted. Slice 4C is closed with all four frontend cutovers independently approved.
`[Codex]: AGREE — Slice 4C complete.`
Next actor: Codex, to implement Slice 5 deterministic AI integration
### D-16 — Slice 5 deterministic AI integration review
#### D-16.1 — Codex asks Claude to review Slice 5
**[Codex]**
The native AI path now exposes one exact-material, read-only tool backed by the authenticated Product and Cost API. The AI service performs no independent cost selection or document database read; it preserves the internal DTO and incomplete states, while deterministic scope and the API both enforce the user's BU/permission boundary. Prompt and profile changes keep the tool in ERP/Finance interactive chat only and prohibit supplier inference, alternative selection, budget rebasing, recipe approval, or prompt-side BOM calculation.
Claude, inspect both sides of the tool, service-auth, end-user forwarding, profile, cache, prompt, deployment-configuration, and locked-fixture interfaces. Treat missing production settings as an explicit activation precondition, and challenge whether rollback is truly independent. The exact prompt is preserved in Appendix P of the implementation plan. Do not reopen cost rules or review Quote CRM.
Next actor: Claude
#### D-16.2 — Claude approves Slice 5 and Codex closes it
**[Claude]**
The exact-material contract, discovery reuse, authenticated end-user-preserving API hop, duplicated-arithmetic avoidance, schema/DTO preservation, incomplete-state handling, ERP freshness classification, supplier-safe projection, ERP/Finance-only interactive grant, Composer/scheduled-agent exclusion, prompt safety, fixture drift, and full-gate registration all validate. Production settings and profile-list rollback are explicit non-blocking activation considerations.
`[Claude]: APPROVE SLICE 5`
**[Codex]**
Accepted. Slice 5 is closed. No cloud platform setting or deployment was changed; configuration remains an owner-controlled activation step.
`[Codex]: AGREE — Slice 5 complete.`
Next actor: Codex, to implement the inactive Quote CRM publication shape
### D-17 — Slice 6 inactive Quote CRM publication review
#### D-17.1 — Codex asks Claude to review Slice 6
**[Codex]**
The external contract remains under Quote CRM review, but the complete inactive publisher shape now exists inside the active API runtime API package. One strict projector consumes the sanitized internal catalog; Blob-first immutable staging and separate document database delivery state precede any network call; the disabled publisher replays retained bytes under bounded idempotent retry; and a downstream-safe wrapper prevents Quote CRM state from changing ERP ETL success. No route, trigger, scheduler, endpoint, credential, or cloud platform setting is registered.
Claude, validate the actual diff across domain semantics, schema, data minimization, storage concurrency, retry/permanent-failure behavior, exact transport bytes, package boundary, harness cleanup, and activation absence. The exact prompt is preserved in Appendix Q of the implementation plan. Do not settle the Quote CRM team's open decisions or invent new pricing rules.
Next actor: Claude
#### D-17.2 — Claude approves Slice 6 and Codex closes it
**[Claude]**
The shared-core projection, option separation, strict schema, data minimization, deterministic bounds/digests, Blob-before-document database immutability, retained-byte retry, permanent-failure block, HTTPS/idempotency/acknowledgement transport, sanitized state, ETL isolation, active API runtime package proof, realistic harness, cleanup, and absence of activation paths all validate.
`[Claude]: APPROVE SLICE 6 INACTIVE CAPABILITY`
**[Codex]**
Accepted. Slice 6 is closed as an inactive capability. Cross-team onboarding and production activation remain separate decisions.
`[Codex]: AGREE — Slice 6 inactive capability complete.`
Next actor: Codex, for integrated verification and branch handoff
### D-18 — Integrated verification and experiment handoff
**[Codex]**
The completed branch passed [redacted: production-scale count] repository test files and [redacted: production-scale count] tests, with only the two explicitly environment-dependent Quote CRM integration tests skipped. The focused Quote CRM capability passed 15/15, the production build completed across [redacted: production-scale count] modules, and the final diff check found no content errors. Slice 6 will be committed as a scoped inactive capability; no push, deployment, cloud platform setting, external call, or production activation is part of this handoff.
The owner's parallel historical Sales Valuation Export S/V backfill remained a data-evidence dependency only. It subsequently completed in production and the owner confirmed the refreshed emulator was already populated, so Codex did not run a mirror. D-19 records the resulting audit.
`[Codex]: HANDOFF — implementation complete through the inactive Quote CRM boundary.`
Next actor: Codex, for the already-populated emulator evidence review
### D-19 — Post-backfill evidence review
#### D-19.1 — Codex asks Claude to reconcile the refreshed emulator
**[Codex]**
Read-only emulator evidence now shows [redacted: production-scale count] controlled S/V results out of [redacted: production-scale count] Product Master entries, split into [redacted: controlled-price distribution]. Four service/commercial labels have no canonical material price and two true materials are explicitly UoM-ambiguous. `MAT-FINISHED-01` resolves to [redacted: commercial amount] under Standard control. The complete catalog and shadow counts reconcile, and the recipe matrix remains analytical-only with no approved Product Master recipe link.
Claude, validate only whether these results follow the implemented and approved contracts. Do not invent conversions, service pricing, recipe approvals, or new policy. The exact prompt is preserved in Appendix R of the implementation plan.
Next actor: Claude
#### D-19.2 — Claude approves the evidence
**[Claude]**
The denominator, S/V split, zero legacy fallback, explicit service-label refusal, two Product Master `EA` versus ERP `M` ambiguity results, representative controller, catalog bounds, shadow arithmetic, uncapped legacy variance, and analytical-only recipe counts all reconcile with the approved resolver, catalog, and façade contracts. Explicit incomplete/ambiguous status is the safe outcome and no concrete code defect exists.
`[Claude]: APPROVE POST-BACKFILL EVIDENCE`
**[Codex]**
Accepted. The data-evidence dependency is closed without a mirror or code change.
`[Codex]: AGREE — post-backfill evidence complete.`
Next actor: Owner, for controlled deployment and activation decisions
### D-20 — Quote CRM publication operations collaboration
#### D-20.1 — Claude completion audit and Codex design decision
**[Claude]**
The original material-cost implementation is complete through the deliberately inactive Slice 6 publisher. No promised slice is missing. Quote CRM scheduling/manual/admin controls are the deferred activation layer; production receiver onboarding, AI settings, legacy rollback runtime retirement/rebase, and previously recorded owner-policy decisions remain explicit follow-ups.
**[Codex]**
Accepted. Claude initially identified a frequently ticking Function timer as an existing pattern, but that recommendation conflicts with the owner's later active API runtime-only boundary. Codex implemented the tick in `startApiServer()` only, with a document database ETag lease and one shared manual/scheduled workflow. No Function timer or new cloud platform resource was introduced.
#### D-20.2 — Codex requests implementation review
**[Codex]**
The operations layer is implemented: Global Admin schedule configuration (disabled Friday 20:00 Johannesburg default; weekly/monthly flexibility), strict-admin status/manual routes, the ETL Admin publication button, active API runtime scheduler, shared workflow, and distributed overlap/slot protection. A production-sized local receiver/emulator proof passed. The exact package boot exposed and corrected eager document database initialization while publication was inactive. Focused tests and build are green; the full suite had one unrelated load-sensitive Agent Insights timeout whose seven assertions passed immediately in isolation.
Claude, review the actual diff using the exact Appendix T prompt in the implementation plan. Challenge concrete correctness only and do not invent new cost or receiver rules.
Next actor: Claude
#### D-20.3 — Claude approves and Codex closes the operations slice
**[Claude]**
The config/default/ETag contract, configured weekly/monthly schedule semantics, inactive-safe active API runtime boot, document database coordinator, shared workflow, strict-admin routes/manual action, secret boundary, route/package registration, activation gates, and realistic write evidence all validate. The visible-but-unsaveable group-admin settings card and unrelated Agent Insights full-suite flake are non-blocking observations, both recorded honestly.
`[Claude]: APPROVE quote-crm PUBLICATION OPERATIONS`
**[Codex]**
Accepted. The original material-cost implementation and the new Quote CRM operations layer are complete on the feature branch. External receiver onboarding, production configuration/deployment/enablement, permanent-rejection operations, AI-service settings, legacy rollback runtime retirement/rebase, and previously parked policy decisions remain explicit follow-ups—not hidden omissions.
After Claude's review, Codex repeated the entire repository gate: all [redacted: production-scale count] test files and [redacted: production-scale count] tests passed, with only the two explicitly integration-gated Quote CRM tests skipped. The earlier Agent Insights timeout remains part of the record but did not recur.
`[Codex]: AGREE — Quote CRM publication operations complete; activation remains separate.`
Next actor: Owner
## 8. Escalations
None.
## 9. Experiment observations
- The collaboration uses a direct Claude CLI session rather than manual message relay or desktop UI automation.
- Active reviewer session: [redacted: persistent-session metadata]; model Claude Opus 4.8; effort high; permission mode read-only/plan.
- Terminal-visible exchanges provide live observability; this ledger provides the durable curated record.
- Raw model event logs are not committed because they may contain noisy machine metadata or repository-local paths.
## Appendix A — Original Claude bootstrap prompt
The following is the exact initial prompt supplied to the persistent Claude CLI session. The substantive D-01 questions it directed Claude to answer are preserved separately in D-01.1 above.
```text
You are Claude, participating as peer co-architect in the Codex-Claude Material Cost Solution experiment. Work in read-only design mode. Do not edit or create files, do not commit or push, do not deploy, and do not mutate cloud platform or any external service.
Start by reading, in full, AGENTS.md, [documentation]/agent-contract.md, [documentation]/app-rules.md, [documentation]/OverviewOfAppArchitecture.md, [documentation]/plans/Claude-Codex-colab-material-cost-solution.md, [documentation]/plans/material-cost-service-architecture.md, [documentation]/plans/quote-crm-product-catalog-pricing-ingestion-contract.md, and [documentation]/plans/fold-api-into-appservice.md. Then inspect the concrete repository paths needed to answer Codex's D-01.1 questions. Follow the collaboration protocol in the ledger.
Return one self-contained Markdown response beginning exactly with "#### D-01.2 — Claude to Codex" and then "**[Claude]**". Address Codex directly. Cite concrete repository files and functions. Include: strongest part of Codex's position; answers to all three questions; any rejected assumption; whether your position changed while inspecting evidence; first-slice acceptance criteria; at most three focused questions; and "Next actor: Codex". Do not include private chain-of-thought. Do not claim to have edited anything.
```
## Appendix B — Exact D-21 design-review prompt
```text
Continue the Codex-Claude Material Cost Solution experiment as the read-only peer co-architect for D-21. Use Claude Opus 4.8 at high effort. Do not edit/create files, commit, push, deploy, run ETL/mirroring/write harnesses, start/stop services, send external requests, or mutate emulator/production/cloud platform/Quote CRM state. Do not invent pricing policy.
Read the governing repository docs and [documentation]/plans/Claude-Codex-colab-material-cost-solution.md D-21.1, then inspect the exact Product Master governance/normalization/save boundary, material-cost resolver/catalog, Quote CRM projector/schema/contract/tests, and relevant frontend consumers. Validate Codex's current proposal against repository reality and the stated read-only emulator evidence: [redacted: review subset] Product Master Services-group items; [redacted: subset] currently project [redacted: nominal placeholder amount] ERP options as complete/eligible; TRAINING_ITEM, REPAIR_ITEM, CERTIFICATION_ITEM have no options and project missing_price; MAT-SERVICE-EXCEPTION-01 has [redacted: commercial amount] moving/controlled evidence plus [redacted: nominal placeholder amount] Standard evidence.
Return one self-contained Markdown response beginning exactly:
#### D-21.2 — Claude to Codex
**[Claude]**
Address Codex directly. Include:
1. the strongest part of Codex's position;
2. answers to the three D-21.1 questions with concrete file/function/test evidence;
3. assumptions rejected or corrected;
4. whether the evidence changed your position;
5. remaining risks or disagreement;
6. a smallest safe synthesis with exact Product Master fields, wire fields/status semantics, migration rule, Quote CRM responsibilities, and observable acceptance gates;
7. an explicit `AGREE D-21` only if you accept the bounded decision, otherwise state the exact disagreement;
8. at most three focused questions; and
9. `Next actor: Codex`.
Do not request private chain-of-thought and do not claim to inspect or test anything you did not inspect or test.
```
## Appendix C — Exact D-21 agreement prompt
```text
Continue the persistent Codex-Claude collaboration for D-21 as read-only Claude Opus 4.8 at high effort. Do not edit/create files, commit, push, deploy, run write paths, or mutate any state. Read D-21.2 and D-21.3 in the collaboration ledger and inspect repository evidence only as needed.
Respond beginning exactly `#### D-21.4 — Claude to Codex` and `**[Claude]**`. Steelman the clarified decision, answer its two questions, identify only concrete remaining contradictions, and either write `[Claude]: AGREE D-21 CLARIFIED` against the exact bounded decision or state the smallest unresolved point. Do not reopen the owner's settled rule that Standard and Moving Average remain separately labelled ERP evidence and Quote CRM owns quote selection. End `Next actor: Codex`.
```
## Appendix D — Exact D-21 implementation-review prompt
```text
Resume the Codex-Claude Material Cost Solution experiment as the read-only independent implementation reviewer for D-21. Use Claude Opus 4.8 at high effort. Do not edit/create files, commit, push, deploy, run write paths, start/stop services, call external endpoints, or mutate emulator/production/cloud platform/Quote CRM state.
Read D-21.1 through D-21.4 and inspect the current uncommitted diff. Review only the implemented non-physical/manual quote-pricing gap: Product Master validation/admin fields, resolver/catalog/facade behavior, Product Catalog presentation, Quote CRM projection/schema/fail-closed activation gate, coverage audit, tests, and Quote CRM/implementation-plan documentation. Check both sides of every changed interface and identify concrete regressions, contradictions, missing entry points, accidental business rules, or contract/schema drift. Respect the settled owner rules: no runtime name/group/numeric inference; exact ERP Standard/Moving evidence remains available for catalog-cost items including 0.01; manual-case-by-case items remain selectable but publish no cost/BOM evidence; Quote CRM enters the quote price; saved FY/customer values are not rewritten.
The focused observable gate currently passes 44 tests across six files. A read-only emulator audit reports [redacted: production-scale count] Product Master items, zero explicitly governed today, [redacted: review subset] advisory Services-group review candidates including the genuine MAT-SERVICE-EXCEPTION-01 exception, and readyForquote-crmActivation=false. The publisher intentionally blocks all [redacted: production-scale count] until reviewed classifications are saved.
Respond beginning exactly `#### D-21.5 — Claude implementation review` and `**[Claude]**`. List findings by severity with file/function evidence. Distinguish a release blocker from the expected current-data migration gate. If the implementation satisfies D-21, write `[Claude]: SIGN-OFF D-21 IMPLEMENTATION`; otherwise withhold sign-off and give the smallest required corrections. End `Next actor: Codex`.
```
#### D-21.5 — Claude implementation review
**[Claude]**
Claude inspected both sides of every changed D-21 interface and reported no contract/schema drift and no release blocker. The review confirmed:
- runtime selection uses only explicit Product Master governance; the Services-group rule exists only as a read-only advisory audit candidate;
- exact ERP Standard/Moving/controlled evidence remains published for catalog-cost items, including the [redacted: nominal placeholder amount] and [redacted: commercial amount] evidence on the MAT-SERVICE-EXCEPTION-01 exception;
- manual items remain identifiable/selectable while the resolver, BOM projector, Quote CRM projector, and active Product Catalog display suppress material-cost evidence and cost-derived quote prices;
- saved line items are not mutated because the compatibility projector returns `applied: false`;
- the standalone and executable schemas agree; and
- all [redacted: production-scale count] currently unclassified items being blocked is the expected current-data migration gate.
Claude recorded one non-blocking observability edge: a hypothetical recipe component classified manual would make the roll-up incomplete but is not assigned a specific missing/ambiguous/invalid coverage bucket. This does not make the roll-up complete or affect the agreed non-physical catalogue use case.
`[Claude]: SIGN-OFF D-21 IMPLEMENTATION`
Next actor: Codex
### D-22 — Product Master quote-pricing governance migration and environment sequencing
#### D-22.1 — Codex to Claude
**[Codex]**
The owner correctly reads the remaining activation gate as a possible Product Master metadata backfill across the emulator and production. The D-21 implementation is already signed off: `costing.quotePricingMode` is the sole behavior gate, `costing.itemKind` is descriptive taxonomy, manual items expose no material-cost evidence, and Quote CRM publication blocks until every catalogue item is explicitly governed. This turn is not permission to write either environment or invent classification rules.
Repository/read-only evidence:
- `[internal-api]/lib/materialCost/contracts.js:resolveQuotePricingPolicy` defaults absent metadata to catalogue-cost behavior for internal backward compatibility but reports `explicitlyGoverned: false`.
- `[internal-api]/lib/materialCost/quote-crmSnapshot.js:buildquote-crmSnapshotArtifact` rejects any item lacking explicit governance.
- `[internal-api]/lib/productMasterGovernance.js:validateProductMasterCosting` validates the two additive Product Master fields.
- `[frontend]/components/admin/AccountManagementView.jsx` exposes Product Master admin controls for both fields.
- The D-21 emulator audit found [redacted: production-scale count] Product Master items, zero explicitly governed, and [redacted: review subset] advisory Services-group review candidates including the genuine `MAT-SERVICE-EXCEPTION-01` catalogue-cost exception.
- Production currently runs the pre-feature application; the local emulator mirrors production data, while feature work and testing run on `[redacted: feature branch]`.
My current position: this is the already-anticipated governed metadata migration, not a new pricing implementation defect. We should create a reviewed, idempotent, dry-run-first mapping for all [redacted: production-scale count] items, prove it in the emulator, and eventually apply the identical reviewed mapping to production Product Master. I have not yet concluded whether production metadata should be applied before or only after the feature deployment, because the deployed pre-feature Product Master save/ETL boundaries may not preserve or understand the new fields.
Remaining risk: emulator-first curation can be lost on a later production-to-emulator mirror; production-first curation before compatible code is deployed could be dropped by an old save/refresh path; blanket Services-group classification would incorrectly suppress genuine ERP evidence such as `MAT-SERVICE-EXCEPTION-01`.
Proposed synthesis: one versioned reviewed mapping artifact keyed by ERP material number, one dry-run/report/apply utility with optimistic concurrency and before/after backup, emulator validation first, then a separately authorized production application at the safest deployment boundary. No runtime inference, no cost-value rewrite, and no Quote CRM enablement until the post-apply coverage audit is [redacted: production-scale count] explicitly governed and representative manual/catalogue exceptions are verified.
Questions:
1. Based on the actual Product Master read/save/ETL/mirror paths, is the remaining work accurately described as a metadata backfill to both emulator and production, and which environment should be the source of truth during execution?
2. Should production metadata be applied before feature deployment, immediately after compatible code deployment but before enabling the material-cost façade/publisher, or through another safer sequence? Cite the concrete preservation and rollback boundaries.
3. What is the smallest safe migration mechanism and acceptance/rollback checklist that avoids one-by-one editing without turning advisory name/group heuristics into an authoritative business rule?
Next actor: Claude
## Appendix E — Exact D-22 migration-sequencing review prompt
```text
Resume the persistent Codex-Claude Material Cost Solution collaboration for D-22 as read-only Claude Opus 4.8 at high effort. Do not edit/create files, commit, push, deploy, run ETL/mirroring/write harnesses, start/stop services, call external endpoints, or mutate emulator/production/cloud platform/Quote CRM state. Do not invent classifications or pricing policy.
Read AGENTS.md, [documentation]/agent-contract.md, [documentation]/app-rules.md, [documentation]/OverviewOfAppArchitecture.md, D-21 and D-22.1 in [documentation]/plans/Claude-Codex-colab-material-cost-solution.md, and the relevant implementation-plan sections. Inspect the actual Product Master API save/validation path, ERP Product Master extract/sync merge preservation behavior, local production-to-emulator mirror behavior, admin controls, material-cost coverage audit, Quote CRM fail-closed gate, and any existing migration/apply-script patterns needed to answer D-22.1. Read-only repository and current branch evidence only.
Return one self-contained Markdown response beginning exactly:
#### D-22.2 — Claude to Codex
**[Claude]**
Address Codex directly. Include: the strongest part of Codex's position; answers to all three questions with concrete file/function evidence; assumptions corrected; whether this is a backfill to both emulator and production; the safest deployment/migration ordering; the smallest dry-run/review/apply/rollback mechanism; acceptance gates; any genuine owner decision still required; and one bounded recommendation. Write `[Claude]: AGREE D-22` only if you accept the recommendation. End `Next actor: Codex`.
```
#### D-22.2 — Claude to Codex
**[Claude]**
Claude agreed this is the anticipated additive Product Master governance migration, not a pricing defect. Production is the durable source of truth; emulator application is validation-only because `mirror-prod-to-dev.js` replaces emulator documents with production documents. Incremental Product Master sync preserves unknown/additive fields through `merged = dict(existing)`, while a full ERP-only reseed remains the destructive boundary.
Claude proposed one reviewed versioned mapping keyed by ERP material number, an idempotent dry-run/report/apply/rollback utility with optimistic concurrency and before/after backup, emulator proof first, then a production apply after compatible deployment and before façade/Quote CRM activation. Acceptance requires complete mapping coverage, representative exceptions, idempotent reapply, proven emulator rollback, and a separate Quote CRM enablement decision.
`[Claude]: AGREE D-22`
Next actor: Codex
#### D-22.3 — Codex correction to Claude
**[Codex]**
I agree with production as durable source, emulator as disposable proof, one reviewed mapping, and the migration mechanism. I do not yet agree with deploy-before-production-backfill because repository evidence contradicts the claimed behavior-neutral deployment:
- `[frontend]/config/materialCostFlags.js:getMaterialCostFeatureFlags` defaults Product Catalog, Product Picker, Budget Plan, and Finpack to `active` when no Vite override is supplied.
- Repository search finds no production workflow or environment configuration supplying `VITE_MATERIAL_COST_*_MODE=legacy|shadow`.
- Browser comparison confirms the local feature build is active and already changes current-reference display for unclassified items.
Therefore deploying the branch before classification would immediately activate the internal façade against [redacted: production-scale count] implicitly defaulted catalogue-cost items. Quote CRM transport remains disabled, but non-material lines would temporarily show misleading ERP/[redacted: commercial amount] references. Because the deployed pre-feature incremental sync preserves additive `costing` metadata and the old UI ignores it, the smaller sequence appears to be: validate mapping/rollback in emulator; perform reviewed production dry-run; apply the production metadata immediately before deployment in a controlled change window; verify [redacted: production-scale count]; deploy compatible code; smoke-test manual and catalogue exceptions; keep Quote CRM disabled. If deployment rolls back, the old app safely ignores the additive metadata.
Questions:
1. Does this evidence change your sequencing recommendation to production-backfill immediately before deploy, or is there a concrete existing build/runtime gate that makes deploy-first behavior-neutral?
2. Is changing the feature defaults/build configuration to legacy/shadow first actually safer enough to justify an additional deployment/cutover mechanism, or is the controlled pre-deploy metadata backfill the smallest safe path?
Next actor: Claude
## Appendix F — Exact D-22 sequencing-correction prompt
```text
Resume D-22 in the persistent read-only Claude Opus 4.8 high-effort session. Do not mutate files, data, services, or external systems. Read D-22.2 and D-22.3 and inspect [frontend]/config/materialCostFlags.js plus production build/deployment configuration. Codex found that all four internal façade modes default active and no production override is configured, contradicting your claim that compatible-code deployment is behavior-neutral before the Product Master backfill.
Respond beginning `#### D-22.4 — Claude to Codex` and `**[Claude]**`. Validate or rebut the evidence. Choose the smallest safe ordering between: (A) reviewed production metadata apply immediately before feature deployment, relying on old-code field preservation/ignorance; or (B) adding/configuring a legacy/shadow deployment gate, deploying, backfilling, then separately activating. State concrete rollback boundaries and whether your D-22 agreement changes. End with either `[Claude]: AGREE D-22 REVISED` or the smallest unresolved issue, then `Next actor: Codex`.
```
#### D-22.4 — Claude to Codex
**[Claude]**
Claude validated that all four frontend surfaces default to `active` and that no production build override exists, withdrawing the earlier claim that deploy-before-backfill would be behavior-neutral. Claude confirmed that a reviewed production-first metadata apply is technically safe because the current incremental Product Master sync preserves additive fields and the pre-feature UI ignores them. Claude nevertheless preferred an explicit legacy/shadow build gate, then compatible deployment, production backfill with live validation, coverage verification, and per-surface activation as the maximum-defense rollout.
`[Claude]: AGREE D-22 REVISED`
Next actor: Codex
#### D-22.5 — Codex synthesis for owner
**[Codex]**
The owner is correct that the data operation itself is a safe additive Product Master enrichment when executed through a reviewed conditional upsert that changes only `material.costing`, retains a before-image, and proves every cost/identity/planning field unchanged. There should not be two independently curated copies: validate the mapping against the emulator, apply it once to authoritative production, then re-mirror production to the emulator.
Claude's legacy-gated deploy is the most defensive rollout, but the smaller production-first sequence is also safe and better matches the owner's preference for a simple change: the old app ignores the additive metadata, normal incremental ERP Product Master sync preserves it, a failed feature deployment leaves production behavior unchanged, and a full reseed can be excluded during the controlled change window. The migration utility must import or reproduce the same strict enum validation, use ETag optimistic concurrency, save a before-image, fail on unmapped/unknown materials, and assert that only the two costing fields changed.
Recommended sequence: finalize and approve the [redacted: production-scale count]-row mapping; emulator dry-run/apply/rollback proof; production dry-run; separately authorized production conditional upsert; production [redacted: production-scale count] audit and representative exception checks; re-mirror production to emulator; deploy the already-active feature; browser smoke test; keep Quote CRM transport and schedule disabled until receiver onboarding is approved.
`[Codex]: AGREE — D-22 closed with production-first governed metadata enrichment.`
Next actor: Owner
#### D-22.6 — Authorized migration outcome
**[Codex]**
On 2026-08-25 the owner authorized the production enrichment and asked that the emulator receive the same narrow Product Master slice without a full production mirror. Codex implemented one guarded migration utility and one versioned mapping artifact for both targets. The utility is dry-run by default, locks the expected [redacted: production-scale count]-material identity set by count and SHA-256 digest, changes only `costing.itemKind` and `costing.quotePricingMode`, uses ETag conditional replacement, writes a before-image receipt, supports rollback, and fails on conflicting existing governance.
The emulator path proved dry-run, apply, [redacted: production-scale count] coverage, idempotent reapply, rollback, restored-state audit, and final reapply. The authorized production apply then classified the same [redacted: production-scale count] materials as [redacted: manual subset] `manual_case_by_case` and [redacted: production-scale count] `catalog_cost_plus_margin`. A post-write production audit reports zero unclassified items and `readyForquote-crmActivation: true`; representative manual items include `TRAINING_ITEM`, `CALL_OUT_ITEM`, and `TRANSPORT_ITEM`, while `MAT-SERVICE-EXCEPTION-01` deliberately remains catalogue-cost because it carries genuine ERP controlled valuation evidence. A second production dry-run reports zero changes.
Before and after the production write, the digest of every Product Master field other than the two governance fields remained identical. The emulator was updated directly with the same mapping and logic; no broad production-to-emulator mirror was run. This metadata readiness does not deploy the feature or enable/send any Quote CRM publication.
The focused migration/governance/publication gate passed 30/30 assertions, and the repository-wide regression gate passed [redacted: production-scale count] files/[redacted: production-scale count] tests with only the existing integration-only file/two tests skipped.
`[Codex]: D-22 production and emulator governance migration complete.`
Next actor: Codex, for regression verification and release handoff.
### D-23 — Product Master writer and admin-action preservation sweep
#### D-23.1 — Codex to Claude
**[Codex]**
The owner asked for a second-opinion sweep after the authorized Product Master governance migration. The review is read-only: do not trigger UI actions, ETL, scripts, document database writes, deployments, or external requests. Current branch `[redacted: feature branch]` is at `[redacted: commit identifier]`; local `master` is `[redacted: commit identifier]`. Production Product Master now contains explicit `costing.quotePricingMode` and `costing.itemKind` on all [redacted: production-scale count] materials, but the feature branch is not deployed.
My current evidence:
- FY **Re-initialize from prior fiscal year** uses `CARRY_FORWARD_DEFTYPES = Array.from(FY_SCOPED_DEFINITION_TYPES)`, whose set contains only sales groups, regions, account managers, and field technicians. Product Master is [redacted: business-unit identifier] and is not copied or rewritten.
- The feature-branch **Save Product Master** UI spreads the loaded document and every loaded material, and normalizes/publishes `costing`; when loaded after enrichment it preserves the metadata. The API validates enum values and preserves ERP-owned cost fields.
- A concurrency hole appears possible: `updateDefinition` sends no `If-Match`; the editor and `CustomerList.ensureProductMasterSelectable` PUT the full cached Product Master. If a page loaded before the migration or before an ETL update, its stale payload could remove or revert newer material fields. `preserveSapObservedProductMasterFields` protects only selected ERP cost/provenance fields and does not merge current `costing` when the candidate lacks it. On the feature branch, normalization of absent costing produces explicit nulls.
- The standard Sales Product Master refresh begins each existing row with `merged = dict(existing)`, so additive per-material `costing` survives; retained/manual paths also begin from the existing material. The document merge starts from the existing doc.
- The exceptional selectable-report script reads the current document, changes only selectability through object spreads, then upserts the whole document without ETag. It preserves costing in an uncontended run but has the same concurrent full-document-write risk.
- The legacy/manual Product Master seed uses the same Python merge, while FY product rebuild reads Product Master and writes the separate FY products container.
My provisional conclusion: FY re-initialization is safe; normal ERP Sales refresh is field-preserving; a freshly loaded Product Master save is value-preserving; but the full-document admin/selectability PUT paths are not concurrency-safe and can undo newly enriched or concurrently refreshed metadata. I have not yet concluded whether server-side merge preservation alone is sufficient or whether ETag plus a narrow patch contract is required.
Questions:
1. Sweep the repository for every Product Master writer or indirect admin action and identify any writer/path I missed, including current `master` versus feature-branch behavior.
2. Validate or rebut the stale-full-document overwrite risk with exact producer/API merge evidence; distinguish immediate production risk from post-feature-deployment risk.
3. Recommend the smallest durable correction and acceptance tests that protect additive costing plus unrelated ERP/admin fields without inventing a new business rule.
Next actor: Claude
## Appendix G — Exact D-23 preservation-sweep prompt
```text
Resume the persistent Codex-Claude Material Cost Solution collaboration as the read-only independent reviewer for D-23. Use Claude Opus 4.8 at high effort. Do not edit or create files, commit, push, deploy, run UI write actions, run ETL/mirroring/migrations/write harnesses, start or stop services, call external endpoints, or mutate emulator/production/cloud platform/Quote CRM state. Do not invent pricing or classification policy.
Read AGENTS.md, [documentation]/agent-contract.md, [documentation]/app-rules.md, [documentation]/OverviewOfAppArchitecture.md, D-21 through D-23.1 in [documentation]/plans/Claude-Codex-colab-material-cost-solution.md, and the relevant implementation-plan/product-master spec sections. Inspect repository reality on branch [redacted: feature branch] at [redacted: commit identifier] and compare local master [redacted: commit identifier] where useful.
Sweep every Product Master writer or indirect mutation entry point, including Business Config & Admin FY carry-forward/re-initialization, Save Product Master, hierarchy-default force apply, manual Add Material, CustomerList selectable promotion, definitions create/update handlers, the Sales ETL Product Master refresh and legacy seed, selectable-report tooling, FY product rebuild/reset tools, migration/rollback tooling, and any other writer you discover. Trace both sides of each interface and distinguish [redacted: business-unit identifier] Product Master from FY product snapshots.
Validate Codex's suspected stale full-document overwrite: updateDefinition currently supplies no If-Match; UI writers may PUT a cached full Product Master; the API starts from current before but shallow-spreads governedBody and preserveSapObservedProductMasterFields protects only selected ERP fields rather than all current additive governance. Determine immediate production-master risk versus feature-branch/post-deploy risk. Do not execute write paths.
Return one self-contained Markdown response beginning exactly:
#### D-23.2 — Claude to Codex
**[Claude]**
Findings first, ranked P0-P3, with exact file/function evidence and concrete impact. Then provide: complete writer/action matrix; assumptions corrected; whether Codex's position changed your view; smallest durable correction; acceptance tests covering stale clients, concurrent ETL/admin saves, additive costing, ERP fields, FY re-initialization, and all discovered writers; remaining genuine owner decision if any; and exactly one verdict: `[Claude]: APPROVE D-23 FINDINGS`, `[Claude]: REQUEST CORRECTION D-23`, or `[Claude]: BLOCK D-23 — OWNER DECISION REQUIRED`. End `Next actor: Codex`.
```
#### D-23.2 — Claude to Codex
**[Claude]**
Claude confirmed a P1 durability gap: Product Master list reads and item PUTs use different ETag cache keys, so `updateDefinition` sends no `If-Match`; the API then performs an unconditional full-document replace. A stale Product Master payload can therefore replace the current `materials` array, while `preserveSapObservedProductMasterFields` restores only selected ERP-owned fields and does not restore absent `costing`. The affected UI writers are Business Config **Save Product Master** (including Add Material and hierarchy force-apply) and Customer Product Mix selectability promotion. Claude also identified the broader last-writer-wins risk for additive metadata and recommended both server-side preserve-if-absent behavior and optimistic concurrency.
Claude correctly confirmed that FY re-initialization excludes [redacted: business-unit identifier] Product Master and that FY product rebuild/reset writes a separate FY-products container. It classified the current risk as P1 rather than P0 because Quote CRM publication fails closed and the pre-feature app does not consume the new fields.
`[Claude]: REQUEST CORRECTION D-23`
Next actor: Codex
#### D-23.3 — Codex correction and follow-up
**[Codex]**
I accept the ETag key mismatch, unconditional full-document replace, stale client, CustomerList selectability, and server preservation findings. Additional repository evidence requires four corrections before the sweep is complete:
1. A current production/master UI payload loaded **after** the migration does preserve unknown `costing` because `normalizeMaterialForSave` begins with `...material`; the immediate danger is a pre-migration/stale payload or a concurrent change, not every pre-feature save.
2. The D-22 migration/rollback utility already uses ETag `IfMatch`, membership/digest locks, and non-governance-content invariants. It is not an unprotected writer. It does not, however, bump `master-data-state` after its direct document database write.
3. `[operations]/erp/product-master-sync.py` always reads the existing Product Master before a non-dry-run apply and fails if that read errors; there is no routine flag that intentionally ignores an existing document. A missing/deleted document is a recovery/bootstrap case, not a normal "full reseed" mode.
4. The standard Sales ETL writer itself reads, merges, then calls unconditional `container.items.upsert(payload)` with no ETag. It preserves `costing` when uncontended but can overwrite a concurrent admin save. Its `_new_material` path does not add explicit `costing`. Likewise, current `master` manual Add Material predates the pricing controls and creates no `costing`; after feature deployment the UI fixes the manual path, but a newly discovered Sales material remains deliberately unclassified and blocks Quote CRM pending review. The generic definitions create handler also does not call `validateProductMasterCosting`.
My revised synthesis: FY re-initialization and separate FY rebuilds are safe. Existing-material ERP refresh and freshly loaded admin saves preserve metadata only when uncontended. The durable correction needs a Product Master write contract, not merely preservation of two fields: conditional replace for every read-modify-write writer, preserve-if-absent compatibility for stale/pre-feature clients, explicit membership/addition semantics so stale arrays cannot delete concurrent materials, create-boundary validation, and an intentional review state for new ERP-derived materials. No pricing rule should be invented for a new material.
Questions:
1. Reconcile the corrected writer matrix, especially the ETL upsert, migration utility, definitions create, and new-material paths.
2. Is preserve-if-absent plus ETag sufficient, or must the admin/selectability APIs use narrow material patches/membership guards to prevent stale arrays from dropping concurrently added materials?
3. Give the smallest phased correction and exact acceptance gates, distinguishing an immediate production safeguard from the feature-branch durable solution.
Next actor: Claude
## Appendix H — Exact D-23 correction prompt
```text
Resume the persistent read-only D-23 review using Claude Opus 4.8 at high effort. Do not edit files or mutate any state. Read D-23.2 and D-23.3, then independently verify Codex's corrections in apiClient updateDefinition/ETag handling, current master versus feature AccountManagementView, definitions create/update, productMasterGovernance, CustomerList selectability, [operations]/erp/product-master-sync.py read/merge/upsert/new-material paths, the D-22 migration utility, masterDataState invalidation, and FY rebuild/carry-forward boundaries.
Return Markdown beginning exactly `#### D-23.4 — Claude to Codex` and `**[Claude]**`. Correct the writer/action matrix; explicitly accept or rebut each of Codex's four corrections; answer the three questions; identify immediate production safeguards versus the smallest durable feature-branch correction; and give acceptance tests. Do not invent a default classification for future materials. End with `[Claude]: AGREE D-23 REVISED` only if the corrected findings are complete; otherwise state the exact remaining gap. End `Next actor: Codex`.
```
#### D-23.4 — Claude to Codex
**[Claude]**
Claude accepted all four Codex corrections and revised the matrix. FY re-initialization and FY product rebuild/reset do not touch [redacted: business-unit identifier] Product Master. A post-migration admin payload preserves `costing` when uncontended; stale/pre-migration full-document payloads can erase it because item PUTs carry no `If-Match`. The regular Sales refresh preserves existing `costing` through its merge but its final upsert is unconditional, so only a genuinely overlapping admin/ETL write creates a last-writer-wins race. New ERP-discovered materials intentionally remain unclassified pending review, and invalid Product Master costing is not yet validated on the create boundary. The D-22 migration is guarded but does not bump `master-data-state`.
Claude agreed that ETag enforcement plus compatibility preservation is sufficient for the full-document admin path; narrow per-material patches would be stronger but are not required for the present risk. It recommended operational safeguards until feature deployment, then bounded write-contract hardening rather than a broad redesign.
`[Claude]: AGREE D-23 REVISED`
Next actor: Codex
#### D-23.5 — Codex operationally calibrated conclusion
**[Codex]**
The owner clarified that manual Product Master additions are extremely rare, the weekly Sales ETL is manually sequenced and operationally stable, and FY re-initialization is the regularly used action. This materially lowers the practical concurrency risk and rules out turning the finding into a disproportionate redesign.
Agreed operational posture:
- **Re-initialize from prior fiscal year:** safe; it does not read or write Product Master.
- **Normal weekly Sales ETL:** safe for the established non-overlapping manual workflow; it preserves existing `costing`. Do not edit/save Product Master during the ETL window.
- **Save Product Master / hierarchy force-apply:** safe after a fresh page load and outside the ETL window. Refresh the page before a rare save so the payload carries the current enriched document.
- **Manual Add Material:** rare and operationally manageable. Before feature deployment the current UI cannot classify the new item, so a subsequent governance review/audit is required; after deployment the new form captures the two fields explicitly.
- **New material first discovered by Sales ETL:** intentionally becomes unclassified and blocks Quote CRM pending review. That is a fail-closed review state, not loss of existing enrichment.
- **Customer Product Mix selectability promotion:** shares the full-document save path; avoid it from a long-lived stale page during an ETL or Product Master maintenance window.
The production read-only audit after the sweep still reports [redacted: production-scale count] explicitly governed, [redacted: reviewed subset] manual, [redacted: production-scale count] catalogue-cost, and zero unclassified. No evidence indicates that the enrichment has been undone.
Proportionate pre-activation hardening remains a small follow-up: pass/enforce the loaded Product Master ETag for admin saves, preserve existing additive governance for legacy payloads that omit it, validate costing on Product Master creation, and keep the coverage audit as the activation gate. Conditional ETL retry and narrower material-patch APIs are optional defence-in-depth unless operational practice changes; they are not blockers for the established manual ETL workflow.
`[Codex]: AGREE D-23 closed — normal operations safe; bounded pre-activation hardening recorded.`
Next actor: Owner, to decide whether the bounded hardening is implemented now or with the feature-deployment preparation.
### D-24 — Quote CRM transport confidentiality and interface security
#### D-24.1 — Codex to Claude
**[Codex]**
The owner asked whether the commercially sensitive product-pricing snapshot needs application-level payload encryption in addition to HTTPS, and whether the proposed outbound Quote CRM interface is safe. This is a read-only design/security review. Do not edit files, run publication, call external endpoints, expose credentials, or mutate any state.
Repository evidence:
- `quote-crmPublisher.js` rejects non-HTTPS production base URLs, uses one configured receiver route, sends a dedicated bearer credential, `Content-Digest` over the exact retained body, idempotency and correlation headers, a bounded timeout/retry policy, and validates the receiver acknowledgement. The publisher is disabled until explicitly configured.
- The exact uncompressed JSON body is staged immutably in cloud object storage before delivery; delivery state stores only bounded operational metadata. The contract excludes customers, transactions, supplier identities, internal storage identifiers, secrets, and plan data, but includes commercially sensitive product identities, ERP-labelled cost options, material-cost/BOM coverage and provenance summaries.
- The current measured payload is [redacted: production-scale count] bytes for [redacted: production-scale count] items and is expected to publish weekly. The Quote CRM receiver becomes the system that stores and serves the local quote catalogue after receipt.
- `Content-Digest` detects byte mismatch but is not a keyed signature. TLS and the dedicated bearer credential provide the present confidentiality/authentication boundary.
- One concrete concern: the Node `fetch` call does not set `redirect`, so Fetch defaults to following redirects. The initial URL is HTTPS-validated, but a 307/308 could forward the sensitive PUT body to a redirect target. Codex proposes `redirect: 'error'` and a contract rule that the receiver must return a final response directly.
My position: correctly configured TLS 1.2/1.3 with valid certificate verification is sufficient for this payload in transit; adding JWE/PGP/envelope encryption would add key-management and incident-recovery complexity without a demonstrated threat that TLS does not already cover. Encryption at rest, least-privilege access and retention remain required at both systems. A keyed request signature or mTLS could strengthen authentication, but is optional unless the bearer-token risk or compliance posture demands it. The redirect behavior is a small pre-activation code hardening item.
Questions:
1. Validate or rebut the conclusion that message-level encryption is unnecessary for v1, distinguishing transport confidentiality, authentication, integrity and storage-after-receipt.
2. Inspect the implemented publisher and contract for concrete security gaps, especially redirect handling, URL validation, bearer/digest semantics, immutable staging, acknowledgement validation, logs and payload minimization.
3. Propose the smallest activation security baseline for host BI platform and Quote CRM, preserving the owner's preference for a simple, low-management interface.
Next actor: Claude
## Appendix I — Exact D-24 security-review prompt
```text
Resume the persistent Codex-Claude Material Cost Solution collaboration for D-24 as the read-only independent security reviewer. Use Claude Opus 4.8 at high effort and permission mode plan. Do not edit or create files, commit, push, deploy, run publication/ETL/mirroring/write harnesses, start or stop services, call the Quote CRM endpoint, expose or inspect credential values, or mutate local/emulator/production/cloud platform/Quote CRM state.
Read AGENTS.md, [documentation]/agent-contract.md, [documentation]/app-rules.md, [documentation]/OverviewOfAppArchitecture.md, D-24.1 in [documentation]/plans/Claude-Codex-colab-material-cost-solution.md, [documentation]/plans/quote-crm-product-catalog-pricing-ingestion-contract.md, and inspect the concrete publisher/store/workflow/schema/tests under [internal-api]/lib/materialCost and [internal-api]/__tests__/materialCost.quote-crmPublication.test.js. Review only the outbound pricing-catalog trust boundary.
Evaluate whether correctly configured HTTPS/TLS plus the dedicated bearer credential is sufficient for v1 confidentiality/authentication/integrity, or whether application-level JWE/PGP/envelope encryption is justified. Distinguish Content-Digest corruption detection from keyed authenticity; transport protection from encryption at rest after receipt; and optional mTLS/HMAC/OAuth hardening from minimum activation requirements. Do not invent compliance requirements.
Independently validate the suspected redirect gap: quote-crmPublisher.js validates only the initial HTTPS URL and calls fetch without an explicit redirect mode, while Fetch follows redirects by default. Determine whether `redirect: 'error'` plus a no-redirect receiver contract should be required before activation. Inspect URL/path construction, credential handling, retries/replay, acknowledgement binding, payload minimization, immutable staging, logs/status exposure, configuration gating and Quote CRM receiver responsibilities.
Return one self-contained Markdown response beginning exactly:
#### D-24.2 — Claude to Codex
**[Claude]**
Include: findings first ranked P0-P3 with file/function evidence and impact; direct answers to all three D-24.1 questions; assumptions corrected; whether your position changed; a minimum activation security checklist split between host BI platform and Quote CRM; optional future hardening kept separate; and one verdict: `[Claude]: AGREE D-24` or `[Claude]: REQUEST CHANGES D-24`. End `Next actor: Codex`.
```
#### D-24.2 — Claude to Codex
**[Claude]**
Claude agreed that application-level JWE/PGP/envelope encryption is unnecessary for v1 when TLS 1.2/1.3 certificate verification is enforced. TLS supplies transport confidentiality, integrity and Quote CRM server authentication; the dedicated bearer supplies publisher authentication; `Content-Digest` is correctly retained as an unkeyed exact-byte corruption and acknowledgement-binding check, not a sender signature. Encryption at rest and least-privilege retention after receipt remain Quote CRM responsibilities.
Claude found one required code correction: `quote-crmPublisher.js` validates only the initial HTTPS URL and calls Node `fetch` without an explicit redirect mode. Fetch follows redirects by default, and a `307`/`308` can forward the commercially sensitive PUT body to an unvalidated location. Claude requires `redirect: 'error'` plus a contract rule that the receiver returns a final response directly.
Claude also required production activation evidence that normal TLS certificate verification is enabled because local document database-emulator paths deliberately set `NODE_TLS_REJECT_UNAUTHORIZED=0`. It otherwise validated the fixed target construction, secret handling, payload minimization, immutable exact-byte staging/retry, acknowledgement binding, idempotency, bounded retries, sanitized state/logging and fail-closed configuration.
`[Claude]: REQUEST CHANGES D-24`
Next actor: Codex
#### D-24.3 — Codex validation and bounded decision
**[Codex]**
Codex independently confirmed the redirect gap and accepts `redirect: 'error'` as a required pre-activation code change. The focused test proves every retry attempt uses the redirect-refusal mode; the production-equivalent Quote CRM acceptance matrix separately exercises the `301`, `302`, `303`, `307`, and `308` receiver cases. The Quote CRM contract now forbids redirects and requires a direct final response.
The live cloud platform `[redacted: active API service]` App Service currently has no `NODE_TLS_REJECT_UNAUTHORIZED`, Quote CRM endpoint, or Quote CRM publication-enabled application setting. Repository code enables the global TLS bypass only when the document database connection endpoint is local emulator host and emulator mode is active. Claude's TLS finding is therefore a valid activation invariant, not a present production vulnerability. Activation must re-check the deployed process configuration and certificate-validating non-production handshake.
Joint decision: v1 uses HTTPS/TLS 1.2/1.3 plus a dedicated least-privilege bearer credential and exact-byte digest; it does not add application-level payload encryption. host BI platform must reject redirects and retain its existing immutable/replay/acknowledgement/logging controls. Quote CRM must use a valid certificate, return no redirects, encrypt retained pricing/quote evidence at rest, restrict access, and document retention and credential rotation/revocation. mTLS, HMAC/JWS, short-lived OAuth and message encryption remain optional future hardening triggered by a concrete threat or compliance requirement.
`[Codex]: AGREE D-24 design — one publisher redirect patch remains required before activation.`
Next actor: Owner, for discussion and later authorization of the bounded pre-activation patch.
#### D-24.4 — Codex implementation review request
**[Codex]**
The owner authorized the bounded redirect patch. `quote-crmPublisher.js` now passes `redirect: 'error'` on the retained `PUT`, and the existing retry test asserts that every attempt carries the refusal mode. No authentication, retry, body, acknowledgement, schedule, or activation behavior changed. The focused publisher and automation gates pass 19/19 and 9/9 respectively.
Claude, review only this security patch and the aligned D-24/Quote CRM contract edits. Confirm that redirects cannot be followed by the production publisher, exact-byte retry remains intact, and the documentation does not overstate `Content-Digest` or require unnecessary message-level encryption.
Next actor: Claude
## Appendix J — Exact D-24 implementation-review prompt
```text
Resume D-24 in the persistent Codex-Claude Material Cost Solution session as read-only Claude Opus 4.8 at high effort and permission mode plan. Do not edit files, commit, push, deploy, call endpoints, inspect credential values, or mutate any state.
Review only the current uncommitted D-24 diff in [internal-api]/lib/materialCost/quote-crmPublisher.js, [internal-api]/__tests__/materialCost.quote-crmPublication.test.js, [documentation]/plans/quote-crm-product-catalog-pricing-ingestion-contract.md, and the D-24 collaboration record. The intended correction is one production behavior: every retained Quote CRM PUT sets `redirect: 'error'`, so Node fetch cannot follow any 3xx with the credential or commercially sensitive body. The retry test must lock the option on every attempt. Focused evidence: materialCost.quote-crmPublication.test.js 19/19 and materialCost.quote-crmAutomation.test.js 9/9 passed with maxWorkers=1 and fileParallelism=false.
Validate both sides of the transport contract, exact-byte retry/ack behavior, and whether the docs correctly keep HTTPS/TLS plus a dedicated bearer as v1 while separating encryption at rest and optional future hardening. Return findings first. End exactly `[Claude]: APPROVE D-24 IMPLEMENTATION` if clean; otherwise `[Claude]: REQUEST CHANGES D-24 IMPLEMENTATION` with the smallest correction. End `Next actor: Codex`.
```
#### D-24.5 — Claude implementation approval and Codex closure
**[Claude]**
Claude verified that `redirect: 'error'` is set on the sole outbound Quote CRM egress inside the retry loop, so every attempt rejects any `3xx` rather than forwarding the bearer or pricing body. The retry test locks the option on both attempts while retaining exact-byte replay and acknowledgement behavior. Claude also confirmed that the external contract correctly separates TLS transport security, bearer authentication, unkeyed digest semantics, receiver encryption at rest and optional future authentication hardening.
`[Claude]: APPROVE D-24 IMPLEMENTATION`
**[Codex]**
Accepted. The required redirect correction is implemented and the 19/19 publication plus 9/9 automation gates pass. No endpoint, credential, cloud platform setting, deployment or Quote CRM activation changed.
`[Codex]: AGREE — D-24 implementation complete; activation remains separate.`
Next actor: Owner
Publication note: this post and transcript deliberately omit sensitive commercial data, customer identity, private infrastructure coordinates, credentials and exact material pricing. The architecture and collaboration method are preserved.
Series conclusion: Part 1 connected the agents. Part 2 showed what they built. Part 3 exposes the engineering system that made the collaboration trustworthy.
No comments:
Post a Comment