Files
build-or-buy/README.md
T
Paul HittandClaude Opus 5.5 2161af5ec0 Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:41:38 -04:00

95 lines
3.7 KiB
Markdown

# BuildOrBuy
Compares building a capability against adopting one over a multi-year
horizon, and checks the constraints that disqualify an option before it does
any arithmetic.
## What it does and why
Build-or-buy decisions are usually made as build days against a licence fee.
That framing hides the three things that most often decide the answer, so
this library models each of them.
### A prohibited option is not an expensive one
Suppose a hosted vendor is cheaper, but its terms of service forbid the use
you need. No cost comparison can give the right answer, because that option
was never available. You record the constraint as a `blocker`. Blockers are
checked before any cost is computed, and a blocked option is reported as
`not-applicable`. Pricing it would imply it could win.
### Self-hosting a third-party tool is a build with a buy label
Running someone else's software yourself is not purchasing it. You take on
backups, upgrades, capacity and on-call, and none of that shows up in a
licence table. An `adopt-self-hosted` option that has not listed its
`operationalSurface` is reported as not assessed and left out of the
ranking.
### The operating cost dominates, and it is the number nobody fills in
Build cost is paid once and estimated with care. Operating cost is paid
every year, for example the upkeep on an adopted tool or a usage bill that
keeps running after the project that needed it has ended. Costs are totalled
over `horizonYears`, and `operateDaysPerYear: null` makes an option
uncostable instead of free. Treating that term as zero is how "adopt" wins
on paper.
A recommendation made while any option is still unestimated is labelled
provisional. When most of an option's cost is operating, the finding says
so.
## A cost win is not the whole decision
The result is a cost comparison, not a verdict. Some reasons to build are
not about cost: data custody, keeping a third party out of the critical
path, a licence you cannot accept. If such a reason rules an option out,
model it as a blocker so the option is removed. If it is a preference, weigh
it in the decision made around this output.
## Usage
```ts
import { compare, formatComparison, type Candidate } from '@extant2000/build-or-buy'
const candidates: Candidate[] = [
{ name: 'in-house build', approach: 'build',
costs: { buildDays: 20, operateDaysPerYear: 2 }, where: 'decision.md' },
{ name: 'hosted service', approach: 'buy-hosted',
costs: { buildDays: 1, operateDaysPerYear: 1, cashPerYear: 600 }, where: 'decision.md' },
{ name: 'self-hosted tool', approach: 'adopt-self-hosted',
costs: { buildDays: 3, operateDaysPerYear: 6 },
operationalSurface: ['backups', 'upgrades', 'capacity', 'on-call'], where: 'decision.md' },
{ name: 'vendor X', approach: 'buy-hosted',
costs: { buildDays: 1, operateDaysPerYear: 0, cashPerYear: 300 },
blockers: [{ reason: 'terms of service prohibit the intended use', where: 'vendor-terms.md' }],
where: 'decision.md' },
]
const cmp = { horizonYears: 3, dayRate: 800 }
const { winner, viable, disqualified, uncostable, reason } = compare(candidates, cmp)
// A report in the @extant2000/evidence-record format, rendered as text.
console.log(formatComparison(candidates, cmp))
```
`totalCost(candidate, cmp)` returns the horizon total, or `null` when an
input is missing. `toReport()` returns the structured report that
`formatComparison()` renders. Every finding cites the `where` of its
candidate or blocker.
## Install
The package is published to the GitLab package registry, so point the
`@extant2000` scope there first:
```
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/build-or-buy
```
## License
MIT. See [LICENSE](LICENSE).