First public release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:49:01 -04:00
co-authored by Claude Opus 5.5
commit 498d70b9f6
14 changed files with 2114 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+8
View File
@@ -0,0 +1,8 @@
node_modules/
dist/
*.log
.DS_Store
.env*
!.env.example
.npmrc
dist-cjs/
+6
View File
@@ -0,0 +1,6 @@
stages: [test]
test:
stage: test
image: node:22-alpine
script: [npm ci, npm run build, npm test]
+14
View File
@@ -0,0 +1,14 @@
# Changelog
## 0.1.0 - 2026-09-28
First public release under MIT.
- `assess()` returns a readiness of `transferable`, `blocked`, `unproven` or
`unknown`, taken from the worst dependency, never the average.
- Person and machine bindings with no substitute are critical blockers.
Account bindings and unwritten knowledge with no substitute also block.
- A substitute that nobody but its author has exercised is reported as not
assessed.
- `toReport()` and `formatHandOff()` produce and render a report in the
`@extant2000/evidence-record` format.
+22
View File
@@ -0,0 +1,22 @@
# Contributing
Issues and merge requests are welcome at
https://gitlab.com/extant2000/hand-off.
## Ground rules
- **A test that cannot fail proves nothing.** If you fix a bug, add a test
that fails without your fix, and check that it does fail before you submit.
- Measure, do not assume. Two modules with the same line count can be
different programs.
- Keep dependencies minimal. Every new dependency needs a reason.
- Explain why in comments, not what.
## Before you open a merge request
Run both of these and make sure they pass:
```
npm run build
npm test
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Extant 2000 LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+82
View File
@@ -0,0 +1,82 @@
# HandOff
Decides whether a capability can be operated by someone other than the
person who built it, by looking at what it depends on.
## What it does and why
Handover is usually treated as a documentation problem. Documentation is the
easy half. **Operability is the absence of dependence on a specific person or
machine.** A thorough runbook does not remove a dependency. It describes one.
HandOff takes the list of things a capability depends on (credentials,
runtimes, knowledge, access, data), what each one is bound to, and what the
recipient would use instead. It checks them for three failures.
1. A personal credential doing institutional work. For example, several
products deploy with one engineer's personal access token. No runbook
transfers that: the recipient cannot obtain it, and it stops working when
the account goes away. It is reported as a critical blocker, not a gap in
the docs.
2. A dependency on a machine instead of a role. A script that hard-codes a
path on one laptop runs on that laptop. It works perfectly, forever, for
one person.
3. A substitute nobody has exercised. This one survives every review,
because it is written down and so looks done. A runbook nobody but its
author has followed is a draft, so it is reported as not assessed.
An account binding with no recorded way for the recipient to get their own
account fails at high severity. Operating knowledge that exists only in
someone's head fails too.
## Readiness is the worst dependency, never the average
One personal credential blocks a transfer however good everything else is.
Averaging is how that blocker disappears behind nine green rows.
| Readiness | When |
|---|---|
| `transferable` | Every dependency is unbound or has a substitute someone else has exercised. |
| `blocked` | At least one dependency cannot be handed over. |
| `unproven` | Nothing is blocked, but at least one substitute has never been exercised. |
| `unknown` | No dependencies were listed. The capability was assumed, not examined. |
## Usage
```ts
import { assess, formatHandOff, type Capability } from '@extant2000/hand-off'
const capability: Capability = {
name: 'release pipeline',
recipient: 'the partner ops team',
dependencies: [{
name: 'deploy-token', kind: 'credential',
coupling: 'person', boundTo: 'alice',
substitute: 'a group deploy token per project',
substituteVerified: true,
where: 'config/deploy.env',
}],
}
const { readiness, reason, blockers, unproven } = assess(capability)
console.log(formatHandOff(capability)) // report in the evidence-record format
```
`substituteVerified` means someone other than the author has followed the
substitute. Leaving it out counts as `false`, because an unexercised
substitute is a claim. The example above is `transferable`; drop the
substitute and it becomes `blocked`.
`coupling` is one of `person`, `machine`, `account` or `none`. `kind` is one
of `credential`, `runtime`, `knowledge`, `access` or `data`. Every finding
cites the dependency's `where`. The library is pure: no clock and no I/O.
## Install
```
npm install @extant2000/hand-off
```
## License
MIT. See [LICENSE](LICENSE).
+6
View File
@@ -0,0 +1,6 @@
# Security
Please report vulnerabilities privately by email to security@extant2000.com.
Do not open a public issue for a security problem.
We aim to acknowledge every report within 5 business days.
+1503
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@extant2000/hand-off",
"version": "0.1.0",
"private": false,
"description": "Decides whether a capability can be operated by someone other than its builder, from what it depends on.",
"license": "MIT",
"type": "module",
"main": "./dist-cjs/index.js",
"files": [
"dist",
"dist-cjs",
"src",
"README.md",
"LICENSE"
],
"repository": {
"type": "git",
"url": "https://gitlab.com/extant2000/hand-off.git"
},
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist-cjs/index.js"
}
},
"scripts": {
"build": "tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist-cjs/package.json', JSON.stringify({type:'commonjs'}))\"",
"test": "vitest run",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"typescript": "^5.6.0",
"vitest": "^2.1.0"
},
"dependencies": {
"@extant2000/evidence-record": "^0.1.2"
},
"author": "Extant 2000 LLC",
"homepage": "https://gitlab.com/extant2000/hand-off",
"bugs": {
"url": "https://gitlab.com/extant2000/hand-off/-/issues"
}
}
+251
View File
@@ -0,0 +1,251 @@
/**
* HandOff — decide whether a capability can actually be operated by somebody
* other than the person who built it.
*
* The mistake is treating this as a documentation problem. Documentation is
* the easy half. **Operability is the absence of dependence on a specific
* person or machine**, and a thorough runbook does not remove a dependency —
* it describes one.
*
* Three failures:
*
* 1. **A personal credential doing institutional work.** Several products
* authenticate with one engineer's personal access token. No runbook
* transfers that; the receiving party cannot obtain it, and it dies
* with the account. This is a blocker, not a documentation gap.
*
* 2. **A dependency on a machine rather than a role.** Scripts that
* hard-code a path on one laptop run on exactly that laptop. They work
* perfectly, forever, for one person.
*
* 3. **A substitute nobody has exercised.** A documented alternative that
* has never been followed by anyone but its author is a claim. It reports
* NOT ASSESSED here — a runbook nobody has followed is a draft.
*
* Readiness is the WORST dependency, never the average. One personal
* credential blocks a transfer regardless of how good everything else is,
* and averaging is how that disappears behind nine green rows.
*
* Pure: no clock, no I/O.
*/
import {
type CapabilityReport,
type Finding,
type FormatOptions,
evidence,
formatReport as renderReport,
} from '@extant2000/evidence-record'
/** What a dependency is bound to, and therefore what blocks the transfer. */
export type Coupling =
/** Tied to an individual. Cannot be transferred, and dies with the account. */
| 'person'
/** Tied to one machine or environment. */
| 'machine'
/** Tied to an account that could in principle be re-issued to a role. */
| 'account'
/** Bound to nothing that has to change hands. */
| 'none'
export type DependencyKind = 'credential' | 'runtime' | 'knowledge' | 'access' | 'data'
export interface Dependency {
name: string
kind: DependencyKind
coupling: Coupling
/** Who or what it is bound to — the person, the host, the account. */
boundTo?: string
/**
* What the receiving party would use instead. A dependency with no
* substitute is not transferable, however well documented it is.
*/
substitute?: string
/**
* Whether anyone OTHER than the author has exercised the substitute.
* Undefined or false means it is a claim, not a capability.
*/
substituteVerified?: boolean
/** Where the dependency lives — a file, a script, a runbook. */
where: string
}
export interface Capability {
name: string
/** The party expected to operate it after transfer. */
recipient?: string
dependencies: Dependency[]
}
/** Couplings that must be broken before anyone else can operate the thing. */
const BLOCKING: Coupling[] = ['person', 'machine']
function depEvidence(d: Dependency) {
return evidence.file(d.where, undefined,
`${d.kind}: ${d.coupling}${d.boundTo ? ` (${d.boundTo})` : ''}`,
d.substitute ? `substitute: ${d.substitute}` : 'no substitute recorded')
}
function findingFor(d: Dependency): Finding {
const id = d.name
const bound = d.boundTo ? ` to ${d.boundTo}` : ''
if (BLOCKING.includes(d.coupling) && !d.substitute) {
return {
id,
summary: `${d.name} is bound${bound} and has no substitute`,
determination: 'fail',
severity: 'critical',
detail: d.coupling === 'person'
? 'A personal binding cannot be handed over. The receiving party cannot obtain it and it dies with the account — this is a blocker, not a documentation gap.'
: 'This runs on one machine. It will keep working perfectly, forever, for one person.',
evidence: [depEvidence(d)],
}
}
if (d.coupling === 'account' && !d.substitute) {
return {
id,
summary: `${d.name} is bound to an account${bound} with no re-issue path recorded`,
determination: 'fail',
severity: 'high',
detail: 'An account binding is transferable in principle, which is not the same as transferred. Record how the recipient obtains their own.',
evidence: [depEvidence(d)],
}
}
if (d.kind === 'knowledge' && !d.substitute) {
return {
id,
summary: `${d.name} exists only as knowledge, with nothing written down`,
determination: 'fail',
severity: 'high',
detail: 'Undocumented operating knowledge transfers only by conversation, and only while the author is available.',
evidence: [depEvidence(d)],
}
}
if (d.substitute && !d.substituteVerified) {
return {
id,
summary: `${d.name} has a substitute nobody else has exercised`,
determination: 'not-assessed',
severity: 'high',
// The failure that survives every review: it is written down, so it
// looks done.
detail: `"${d.substitute}" has not been followed by anyone but its author. A runbook nobody has followed is a draft — it is not evidence that the handover works.`,
evidence: [depEvidence(d)],
}
}
if (d.substitute) {
return {
id,
summary: `${d.name} — substitute exercised: ${d.substitute}`,
determination: 'pass',
severity: 'info',
evidence: [depEvidence(d)],
}
}
return {
id,
summary: `${d.name} is bound to nothing that has to change hands`,
determination: 'pass',
severity: 'info',
evidence: [depEvidence(d)],
}
}
export type Readiness = 'transferable' | 'blocked' | 'unproven' | 'unknown'
export interface HandOffVerdict {
readiness: Readiness
reason: string
/** Dependencies that block the transfer outright. */
blockers: Dependency[]
/** Substitutes that exist on paper and have never been exercised. */
unproven: Dependency[]
}
/**
* Whether the capability can change hands.
*
* Computed from the WORST dependency, never the average: one personal
* credential blocks a transfer regardless of how good everything else is,
* and averaging is how that disappears behind nine green rows.
*/
export function assess(capability: Capability): HandOffVerdict {
const deps = capability.dependencies ?? []
if (deps.length === 0) {
return {
readiness: 'unknown',
reason: 'No dependencies were enumerated. A capability with no listed dependencies has not been examined — it has been assumed.',
blockers: [], unproven: [],
}
}
const blockers = deps.filter(d =>
(BLOCKING.includes(d.coupling) && !d.substitute)
|| (d.coupling === 'account' && !d.substitute)
|| (d.kind === 'knowledge' && !d.substitute))
const unproven = deps.filter(d => d.substitute && !d.substituteVerified)
if (blockers.length > 0) {
return {
readiness: 'blocked',
reason: `${blockers.length} dependenc${blockers.length === 1 ? 'y' : 'ies'} cannot be handed over: ${blockers.map(d => d.name).join(', ')}.`,
blockers, unproven,
}
}
if (unproven.length > 0) {
return {
readiness: 'unproven',
reason: `${unproven.length} substitute(s) exist on paper and have never been exercised by anyone but the author: ${unproven.map(d => d.name).join(', ')}.`,
blockers, unproven,
}
}
return {
readiness: 'transferable',
reason: `All ${deps.length} dependenc${deps.length === 1 ? 'y is' : 'ies are'} either unbound or have an exercised substitute.`,
blockers, unproven,
}
}
/** Report a capability's transfer readiness. */
export function toReport(capability: Capability): CapabilityReport {
const deps = capability.dependencies ?? []
const v = assess(capability)
if (deps.length === 0) {
return {
capability: 'HandOff',
scope: `${capability.name} — no dependencies enumerated`,
examined: 0,
findings: [],
notes: [v.reason],
}
}
return {
capability: 'HandOff',
scope: `${capability.name} → ${capability.recipient ?? 'an unnamed recipient'} — ${deps.length} dependenc${deps.length === 1 ? 'y' : 'ies'}`,
examined: deps.length,
findings: deps.map(findingFor),
notes: [
`readiness: ${v.readiness.toUpperCase()} — ${v.reason}`,
'Readiness is the worst dependency, never the average — one personal credential blocks a transfer however good the rest is.',
],
}
}
/** Render a handover assessment for a terminal, a review or a transfer pack. */
export function formatHandOff(capability: Capability, opts: FormatOptions = {}): string {
return renderReport(toReport(capability), opts)
}
export { BLOCKING }
+126
View File
@@ -0,0 +1,126 @@
import { describe, expect, it } from 'vitest'
import { overall, validateReport } from '@extant2000/evidence-record'
import { assess, formatHandOff, toReport, type Capability, type Dependency } from '../src/index.js'
const dep = (p: Partial<Dependency> = {}): Dependency => ({
name: 'deploy-token',
kind: 'credential',
coupling: 'none',
where: 'config/deploy.env',
...p,
})
const cap = (dependencies: Dependency[]): Capability =>
({ name: 'release pipeline', recipient: 'the partner ops team', dependencies })
describe('a personal credential is a blocker, not a documentation gap', () => {
// Several products authenticating with one engineer's personal token.
const personal = dep({ coupling: 'person', boundTo: 'alice', where: 'config/deploy.env' })
it('blocks regardless of how much is written down', () => {
const v = assess(cap([personal]))
expect(v.readiness).toBe('blocked')
expect(v.blockers.map(d => d.name)).toEqual(['deploy-token'])
})
it('says why documentation does not help', () => {
const f = toReport(cap([personal])).findings[0]!
expect(f.severity).toBe('critical')
expect(f.detail).toContain('dies with the account')
expect(f.detail).toContain('not a documentation gap')
})
it('is satisfied by a recorded and exercised substitute', () => {
const fixed = { ...personal, substitute: 'a group deploy token per project', substituteVerified: true }
expect(assess(cap([fixed])).readiness).toBe('transferable')
})
it('blocks a machine binding for the same reason', () => {
// A script that hard-codes a path on one laptop runs on that laptop only.
const machine = dep({ name: 'node-path', kind: 'runtime', coupling: 'machine', boundTo: 'dev-laptop-1', where: 'scripts/deploy.sh' })
expect(assess(cap([machine])).readiness).toBe('blocked')
expect(toReport(cap([machine])).findings[0]!.detail)
.toContain('forever, for one person')
})
it('treats an account binding as high, not critical — transferable in principle', () => {
const acct = dep({ coupling: 'account', boundTo: 'ops@example.com' })
const f = toReport(cap([acct])).findings[0]!
expect(f.severity).toBe('high')
expect(f.detail).toContain('which is not the same as transferred')
})
it('blocks undocumented operating knowledge', () => {
const k = dep({ name: 'restore-procedure', kind: 'knowledge', coupling: 'none' })
expect(assess(cap([k])).readiness).toBe('blocked')
expect(toReport(cap([k])).findings[0]!.detail).toContain('only while the author is available')
})
})
describe('a runbook nobody has followed is a draft', () => {
const unexercised = dep({
coupling: 'person', boundTo: 'alice',
substitute: 'issue a group deploy token', substituteVerified: false,
})
it('reports an unexercised substitute as not-assessed, never a pass', () => {
// It is written down, so it looks done. That is why it survives review.
const f = toReport(cap([unexercised])).findings[0]!
expect(f.determination).toBe('not-assessed')
expect(f.detail).toContain('A runbook nobody has followed is a draft')
expect(overall(toReport(cap([unexercised])))).toBe('not-assessed')
})
it('gives the capability its own readiness state', () => {
const v = assess(cap([unexercised]))
expect(v.readiness).toBe('unproven')
expect(v.blockers).toHaveLength(0)
expect(v.unproven).toHaveLength(1)
})
it('treats a missing substituteVerified the same as false', () => {
const { substituteVerified, ...rest } = unexercised
expect(assess(cap([rest as Dependency])).readiness).toBe('unproven')
})
})
describe('readiness is the worst dependency, never the average', () => {
it('one blocker sinks nine clean dependencies', () => {
const nineGood = Array.from({ length: 9 }, (_, i) =>
dep({ name: `ok-${i}`, coupling: 'none' }))
const v = assess(cap([...nineGood, dep({ name: 'pat', coupling: 'person', boundTo: 'alice' })]))
expect(v.readiness).toBe('blocked')
expect(formatHandOff(cap([...nineGood, dep({ name: 'pat', coupling: 'person' })])))
.toContain('never the average')
})
it('blocked outranks unproven', () => {
const v = assess(cap([
dep({ name: 'a', coupling: 'person' }),
dep({ name: 'b', coupling: 'person', substitute: 'x' }),
]))
expect(v.readiness).toBe('blocked')
expect(v.unproven).toHaveLength(1) // still reported
})
})
describe('conformance with the evidence-record standard', () => {
it('no dependencies enumerated is unknown, never transferable', () => {
const v = assess(cap([]))
expect(v.readiness).toBe('unknown')
expect(v.reason).toContain('has not been examined — it has been assumed')
expect(overall(toReport(cap([])))).toBe('not-assessed')
})
it('every conclusion carries a citation', () => {
expect(validateReport(toReport(cap([
dep({ name: 'a', coupling: 'person' }),
dep({ name: 'b', coupling: 'none' }),
])))).toEqual([])
})
it('prints the readiness verdict', () => {
expect(formatHandOff(cap([dep()]))).toContain('readiness: TRANSFERABLE')
expect(formatHandOff(cap([dep({ coupling: 'person' })]))).toContain('readiness: BLOCKED')
})
})
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "dist-cjs",
"declaration": false
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2022", "module": "ES2022", "moduleResolution": "bundler",
"declaration": true, "outDir": "dist", "rootDir": "src",
"strict": true, "skipLibCheck": true
},
"include": ["src/**/*.ts"]
}