# Bean & Bark: release the Pricing SDK

Friday's doubled totals came from pricing logic copied into two applications. The post-mortem fixed the source tree; now the shared `BeanAndBark.Pricing` library has to become something the Orders API, storefront, and future Roastery Desk can all install. The dangerous part is not `dotnet pack`. It is proving the package is restorable, versioned honestly, and unchanged between Tunde's test and the approved release.

**You'll learn**

- Turn an SDK repository into an explicit public/private package surface
- Use the evaluated dependency graph to stop a private implementation leaking through a public contract
- Audit immutable versions before staging a feed-derived release candidate
- Build a clean consumer from the RC feed and publish the approved stable bytes with a journal

> **Monday, 11:05am — the follow-up ticket:** **Nadia:** *"the pricing fix is merged, but Tunde still has to copy the project into the storefront to use it. can we release the library properly? and please make sure Friday's version can't quietly change underneath him 🙏"*

## Declare what is allowed to leave

The repository contains two packable projects: the contract partners may consume, and roastery-only discount rules. `packages init --split` gives that boundary a checked-in policy instead of leaving it in someone's memory.

```bash
cd BeanAndBark.Pricing

# Keep the release tool version reviewable in this repo
dotnet new tool-manifest
dotnet tool install Kanject.Cli

# This SDK has a public contract and private roastery rules
dotnet kanject packages init --split
dotnet kanject packages plan --write
dotnet kanject packages verify
```

```json
"policy": {
  "allowPublic": [
    "BeanAndBark.Pricing"
  ],
  "denyPublic": [
    "BeanAndBark.Pricing.RoasteryRules"
  ]
}
```

## The first gate goes red—and that is good

`verify` evaluates the same MSBuild graph `pack` sees. It finds that the public Pricing project still references `RoasteryRules`: a public consumer would need a package the policy forbids from leaving. The build is green, but the product boundary is not.

The fix is not to add the private package to `allowPublic`. Move the shared calculation contract into the public project and keep the seasonal margin tables behind the private implementation. Rerun `plan --write`, review the one-line public-list diff, then rerun `verify`. The gate is now green for the reason you intended.

> **A successful pack can still be unusable:** `dotnet pack` can create a perfectly valid nupkg whose first-party dependency is unavailable to its consumer. Package closure is a product guarantee, not a compiler guarantee; that is why `verify` runs before publication.

## Ask whether the version is still yours to use

The Pricing project still says `1.2.4`, the version from before the duplicate logic was removed. The feed already owns that identity. `versions --packed-dir --strict` compares the packed payload and normalized dependency contract, then names the project that needs a bump.

```bash
dotnet pack BeanAndBark.Pricing.slnx   -c Release   -o packed

dotnet kanject packages versions   --packed-dir packed   --strict
```

You choose `1.2.5`, rebuild, and rerun the audit. Kanject does not decide semantic-version magnitude; it makes an accidental overwrite impossible and puts the decision back in the pull request.

## Give Tunde a candidate, not a promise

The release build compiles once. Stable packages travel forward in the protected pipeline artifact; the same assemblies are packed again with the next suffix derived from the candidate feed. No human guesses `rc.7`, and two runners cannot both claim it silently.

```bash
RC_SUFFIX="$(dotnet kanject packages next-rc)"

dotnet pack BeanAndBark.Pricing.slnx   -c Release   --no-build   -p:VersionSuffix="$RC_SUFFIX"   -o packed-rc

dotnet kanject packages publish   --profile candidate   --packed-dir packed-rc   --ci

dotnet kanject packages smoke feed   --plan release-plan-rc.json   --build
```

The smoke command creates clean consumers and restores from the actual RC feed. A package that only worked because your global NuGet config knew a private source now fails here, before the approval button exists. Tunde installs the candidate into the storefront and confirms the wholesale totals against Thursday's orders.

> **🎯 One calculation, three consumers:** The Orders API, storefront, and Roastery Desk now resolve one Pricing contract. More importantly, Tunde tested an addressable candidate from the same feed shape CI tested—not a project reference, local folder, or attachment in chat.

## Approve what was tested

Approval releases the trusted publisher, not another build. It recomputes routing from deployment-owned policy, refuses a conflicting identity, publishes dependencies first, and writes a journal entry for every push.

```bash
# Runs in the trusted publish stage after the approval action
PUBLISH_PUBLIC=true dotnet kanject packages publish   --profile approved-release   --packed-dir packed   --ci
```

The release plan says what should happen. Preflight verdicts say every destination was safe before mutation. The journal says what actually happened and can resume a partial release without repeating completed operations. Those are the receipts Nadia wanted when she said the version must not move underneath anyone.

**Recap**

- `packages init --split`, `plan --write`, and `verify` turn a public/private intention into an evaluated, blocking release boundary.
- `versions --packed-dir --strict` treats package id + version as immutable and names contracts that need a bump.
- The RC suffix comes from the candidate feed; clean consumers restore and build before the release becomes approvable.
- The trusted publisher ships the approved stable identities and leaves release-plan, preflight, and journal evidence.

**Before Tunde updates the storefront**

**1. The public Pricing package references a private first-party package. `dotnet pack` succeeds. What should the release do?**

- Fail `packages verify` before publishing ✓ — A public consumer cannot restore a dependency that only exists on the private route. The closure gate catches that product failure even when MSBuild can pack the producer repo.
- Publish the dependency publicly automatically — Publication policy is a reviewed product decision. The CLI never widens the public surface to make a build green.
- Remove the dependency from the nuspec — That would hide the contract rather than fix it; consumers would restore and then fail at compile or runtime.

**2. Why is stable publication not another build after approval?**

- Because approval is for the candidate-tested assemblies ✓ — Compile once, pack twice keeps RC and stable assemblies identical. Rebuilding after approval would create new, untested bytes.
- Because NuGet cannot build projects — NuGet is not the reason. The release design deliberately keeps compilation before approval so the approved evidence refers to the shipped bytes.
- Because stable packages never have dependencies — Stable packages keep their real dependency contracts; the publisher simply preserves and orders them dependency-first.

**Try it yourself**

**1. Create a closure failure on purpose**

In a sample two-package repo, mark one package public and its dependency private. Run `packages verify`, then explain why allowing the dependency publicly may be the wrong fix.

_Hint:_ Ask whether consumers need the implementation or only an abstraction that currently lives in the wrong project.

_Solution:_ The durable fix is often to move the consumer-facing contract into the public package and keep implementation/configuration private. `allowPublic` is appropriate only when the dependency itself is intentionally part of the supported public surface.

**2. Read the three receipts**

Open a release plan, preflight verdict document, and publication journal. Write one sentence describing the question each artifact answers.

_Solution:_ Plan: what is intended? Preflight: is every destination safe before mutation? Journal: what actually completed, failed, or can resume?

> **Where next?:** The shared package now powers the API and storefront. The roastery still runs labels and stock counts from three spreadsheets on three operating systems. [Ship Roastery Desk](https://www.kanject.com/docs/cli-bean-bark-desktop/) turns that next problem into one tested desktop candidate and a byte-identical stable release. The neutral package reference stays at [Publish .NET packages](https://www.kanject.com/docs/packages/).

---
_Source: https://www.kanject.com/docs/cli-bean-bark-packages/ · Kanject Docs_
