Bean & Bark: release the Pricing SDK

View .md

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

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 repodotnet new tool-manifestdotnet tool install Kanject.Cli# This SDK has a public contract and private roastery rulesdotnet kanject packages init --splitdotnet kanject packages plan --writedotnet 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.

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 packeddotnet 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-rcdotnet kanject packages publish   --profile candidate   --packed-dir packed-rc   --cidotnet 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.

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 actionPUBLISH_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 storefront2
The public Pricing package references a private first-party package. dotnet pack succeeds. What should the release do?
Why is stable publication not another build after approval?
Try it yourself 2
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.
Ask whether consumers need the implementation or only an abstraction that currently lives in the wrong project.
Show 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.
Show solution
Plan: what is intended? Preflight: is every destination safe before mutation? Journal: what actually completed, failed, or can resume?
Was this page helpful?