First public release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:56:05 -04:00
co-authored by Claude Opus 5.5
commit be583723ff
14 changed files with 2165 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]
+13
View File
@@ -0,0 +1,13 @@
# Changelog
## 0.3.0 - 2026-09-28
First public release under MIT.
- `compareImplementations()` compares a named function across any number of
copies, ignoring comments and whitespace, and groups them by behaviour.
- A copy where the function cannot be found is a failure, never a pass.
- Fewer than two located copies, with none missing, is not assessed.
- `toReport()` and `formatDrift()` produce and render an
`@extant2000/evidence-record` report that cites every copy.
- `extractFunction()`, `codeOnly()` and `fingerprint()` are exported.
+22
View File
@@ -0,0 +1,22 @@
# Contributing
Issues and merge requests are welcome at
https://gitlab.com/extant2000/blast-radius.
## 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.
+75
View File
@@ -0,0 +1,75 @@
# BlastRadius
Detects when copies of the same function in different codebases drift apart,
and names which copies disagree. It is used in Hitt Hosting products.
## What it does and why
When the same logic lives in more than one place (two services, a vendored
copy, a CommonJS and a TypeScript version), a fix tends to land in only one of
them. Each copy looks fine on its own. Only a comparison shows that they no
longer agree, and by then the unfixed copy has been running for a while.
BlastRadius takes the source text of each copy and the name of the function to
compare. It extracts the function by brace matching, strips comments and
normalises whitespace, fingerprints the result, and groups the copies by
fingerprint. More than one group means drift. The largest group comes first,
which usually tells you which side has the fix and which side missed it.
Comments and formatting are ignored because copies may be documented
differently but must behave the same. Comparing raw text would fire on a typo
fix and train people to update the pinned value without reading, which brings
back the drift the check exists to catch. The extraction is not a parser, so
it works across CommonJS, ESM and TypeScript without a toolchain per dialect.
**A copy where the function cannot be found is a failure, not a pass.** It may
have been renamed, split or deleted, and a guard that cannot find its subject
is not passing. This holds even when only one copy was located.
Fewer than two located copies, with none missing, is reported as not
assessed: one copy cannot disagree with itself, and calling that "in sync"
would let a deleted sibling read as a clean result. An in-sync pass cites the
copies it compared, because a pass that cannot name what it compared is only
asserted.
The library is pure and has no filesystem access; you read the files. Reports
use the `@extant2000/evidence-record` format, and every copy is cited by its
path when you give one, or by its label.
## Usage
```ts
import { compareImplementations, toReport, formatDrift } from '@extant2000/blast-radius'
const withCheck = 'function canSend(c) {\n if (!c.postalAddress) return false\n return true\n}'
const withoutCheck = 'function canSend(c) {\n return true\n}'
const result = compareImplementations([
{ label: 'web', path: 'web/lib/send.ts', source: withCheck },
{ label: 'worker', path: 'worker/lib/send.ts', source: withCheck },
{ label: 'cli', path: 'cli/lib/send.ts', source: withoutCheck },
], 'canSend')
result.inSync // false
result.groups.map(g => g.labels) // [['web', 'worker'], ['cli']]
result.missing // []
const report = toReport(result) // evidence-record report
console.log(formatDrift(result, { evidence: 'full' }))
```
The report has one critical finding, "canSend has drifted across 2 variants",
citing all three files with the variant each belongs to (2 of 3 and 1 of 3).
`extractFunction()`, `codeOnly()` and `fingerprint()` (FNV-1a) are exported
for use on their own.
## Install
```
npm install @extant2000/blast-radius
```
## 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.
+1504
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@extant2000/blast-radius",
"version": "0.3.0",
"private": false,
"description": "Shows which systems a change to shared code will affect.",
"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/blast-radius.git"
},
"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"
},
"module": "./dist/index.js",
"dependencies": {
"@extant2000/evidence-record": "^0.1.2"
},
"author": "Extant 2000 LLC",
"homepage": "https://gitlab.com/extant2000/blast-radius",
"bugs": {
"url": "https://gitlab.com/extant2000/blast-radius/-/issues"
}
}
+244
View File
@@ -0,0 +1,244 @@
/**
* Detect when duplicated implementations of the same logic drift apart.
*
* The failure it guards against: the same logic exists in two places, and a
* fix (a compliance check, say) lands in only one of them. Each copy looks
* fine on its own; only a comparison shows that they no longer agree.
*
* Design:
* - **Pure.** It takes sources instead of reading files, so it is testable
* without a filesystem and usable on any pair of strings.
* - **N-way, not a single hash.** This compares any number of copies and
* reports WHICH ones diverge — a pinned constant tells you something
* moved, not what.
*/
import {
type CapabilityReport,
type Finding,
type FormatOptions,
evidence,
formatReport as renderReport,
} from '@extant2000/evidence-record'
export interface Implementation {
/** How to identify this copy in output, e.g. a repo-relative path. */
label: string
source: string
/**
* Filesystem path this copy was read from, when the caller knows it.
* Carried into citations so a drift finding points at a file a reader can
* open, rather than at a label they have to resolve themselves.
*/
path?: string
}
export interface DriftGroup {
/** Normalised-code fingerprint shared by every member. */
fingerprint: string
labels: string[]
/** Label + path per member, so each copy can be cited individually. */
sources: Array<{ label: string, path?: string }>
}
export interface DriftReport {
functionName: string
/** True when every copy that was found is behaviourally identical. */
inSync: boolean
/** One entry per distinct implementation. More than one means drift. */
groups: DriftGroup[]
/** Copies where the function could not be located at all. */
missing: string[]
/** The same copies with their paths, for citation. */
missingSources: Array<{ label: string, path?: string }>
/**
* True when fewer than two copies were found. Reported separately from
* `inSync`: one copy cannot disagree with itself, and calling that "in sync"
* would let a deleted or renamed sibling read as a clean result.
*/
vacuous: boolean
}
/**
* Extract a named function body by brace matching.
*
* Deliberately not a parser: the point is to work across CommonJS, ESM and
* TypeScript copies of the same logic without a toolchain per dialect.
*/
export function extractFunction(src: string, name: string): string | null {
const re = new RegExp(`^(?:export )?(?:async )?function ${name}\\s*\\([^)]*\\)\\s*\\{`, 'm')
const m = re.exec(src)
if (!m) return null
const start = src.indexOf('{', m.index)
let depth = 0
for (let i = start; i < src.length; i++) {
if (src[i] === '{') depth++
else if (src[i] === '}') {
depth--
if (depth === 0) return src.slice(m.index, i + 1)
}
}
return null
}
/**
* Strip comments and normalise whitespace.
*
* Comments and formatting are ignored on purpose: two copies are allowed to be
* documented differently, but must BEHAVE identically. Comparing raw text would
* make the guard fire on a typo fix and train people to bump the hash without
* reading — which reintroduces exactly the drift it exists to prevent.
*/
export function codeOnly(block: string): string {
return block
.split('\n')
.map(l => l.trim())
.filter(l => l && !l.startsWith('//'))
.map(l => l.replace(/\s*\/\/.*$/, '').replace(/\s+/g, ' '))
.filter(Boolean)
.join('\n')
}
/** FNV-1a — dependency-free and sufficient for equality grouping. */
export function fingerprint(code: string): string {
let h = 0x811c9dc5
for (let i = 0; i < code.length; i++) {
h ^= code.charCodeAt(i)
h = Math.imul(h, 0x01000193) >>> 0
}
return h.toString(16).padStart(8, '0')
}
/**
* Compare a named function across several copies.
*
* `missing` is kept distinct from a drift group. A copy where the function
* cannot be found is not "different" — it may have been renamed, split, or
* deleted — and silently treating it as absent-therefore-fine is how a guard
* stops guarding.
*/
export function compareImplementations(
impls: Implementation[],
functionName: string,
): DriftReport {
const byPrint = new Map<string, Array<{ label: string, path?: string }>>()
const missing: string[] = []
const missingSources: Array<{ label: string, path?: string }> = []
for (const impl of impls) {
const fn = extractFunction(impl.source, functionName)
if (fn == null) {
missing.push(impl.label)
missingSources.push({ label: impl.label, path: impl.path })
continue
}
const fp = fingerprint(codeOnly(fn))
const entry = { label: impl.label, path: impl.path }
const list = byPrint.get(fp)
if (list) list.push(entry)
else byPrint.set(fp, [entry])
}
const groups: DriftGroup[] = [...byPrint.entries()]
.map(([fp, sources]) => ({ fingerprint: fp, labels: sources.map(s => s.label), sources }))
.sort((a, b) => b.labels.length - a.labels.length)
const found = groups.reduce((n, g) => n + g.labels.length, 0)
return {
functionName,
inSync: groups.length <= 1 && missing.length === 0,
groups,
missing,
missingSources,
vacuous: found < 2,
}
}
/**
* Convert a drift comparison into the portfolio-standard report shape.
*
* Every copy is cited individually — the path when the caller knows it, the
* label otherwise — so a DRIFTED verdict names the files that disagree rather
* than asserting that they do.
*/
export function toReport(report: DriftReport): CapabilityReport {
const findings: Finding[] = []
const compared = report.groups.reduce((n, g) => n + g.labels.length, 0)
if (report.missing.length) {
findings.push({
id: 'missing',
summary: `${report.functionName} could not be found in ${report.missing.length} of ${compared + report.missing.length} copies`,
determination: 'fail',
severity: 'critical',
detail: 'Renamed, split, or deleted? A guard that cannot find its subject is not passing.',
evidence: report.missingSources.map(s =>
s.path
? evidence.file(s.path, undefined, undefined, `${report.functionName} not found`)
: evidence.document(s.label, undefined, undefined, `${report.functionName} not found`)),
})
}
if (report.groups.length > 1) {
findings.push({
id: 'drift',
summary: `${report.functionName} has drifted across ${report.groups.length} variants`,
determination: 'fail',
severity: 'critical',
detail: 'Apply the same change to every copy — do not bump a pinned hash on one side.',
// Majority variant first: the sort in compareImplementations already
// puts it there, and naming it first tells a reader which side is
// probably the fix and which is probably the miss.
evidence: report.groups.flatMap(g => g.sources.map(s =>
s.path
? evidence.file(s.path, undefined, undefined, `variant ${g.fingerprint} (${g.labels.length} of ${compared})`)
: evidence.document(s.label, undefined, undefined, `variant ${g.fingerprint} (${g.labels.length} of ${compared})`))),
})
}
if (report.inSync && !report.vacuous) {
const g = report.groups[0]!
findings.push({
id: 'in-sync',
summary: `${report.functionName}: ${g.labels.length} copies identical`,
determination: 'pass',
severity: 'info',
evidence: g.sources.map(s =>
s.path
? evidence.file(s.path, undefined, undefined, `fingerprint ${g.fingerprint}`)
: evidence.document(s.label, undefined, undefined, `fingerprint ${g.fingerprint}`)),
})
}
return {
capability: 'BlastRadius',
scope: `${report.functionName} across ${compared + report.missing.length} copies`,
// Fewer than two LOCATED copies means nothing was compared, which drives
// the report to NOT ASSESSED without a special case. One copy cannot
// disagree with itself, and calling that "in sync" would let a deleted
// sibling read as a clean result.
//
// But `vacuous` counts located copies only, so one copy plus one where
// the function is MISSING is vacuous by that definition — and a missing
// subject is a finding, not an absence of one. It stays assessed.
examined: report.vacuous && report.missing.length === 0
? 0
: compared + report.missing.length,
findings,
notes: report.vacuous && report.missing.length === 0
? ['Fewer than two copies were found, so nothing was compared. One copy cannot disagree with itself.']
: [],
}
}
/**
* Render a drift report for a CI log.
*
* Delegates to the shared renderer. What used to print VACUOUS now prints
* NOT ASSESSED plus the sentence explaining it — same distinction, no
* glossary.
*/
export function formatDrift(report: DriftReport, opts: FormatOptions = {}): string {
return renderReport(toReport(report), opts)
}
+191
View File
@@ -0,0 +1,191 @@
import { describe, expect, it } from 'vitest'
import { overall, validateReport } from '@extant2000/evidence-record'
import {
codeOnly,
toReport,
compareImplementations,
extractFunction,
fingerprint,
formatDrift,
} from '../src/index.js'
const A = `
// header comment
function evaluateSendRules(c) {
if (!c.postalAddress) return { ok: false }
return { ok: true }
}
module.exports = { evaluateSendRules }
`
// Same behaviour, different comments and formatting — must NOT be drift.
const B = `
/* different doc */
function evaluateSendRules(c) {
// a comment the other copy does not have
if (!c.postalAddress) return { ok: false }
return { ok: true } // trailing note
}
`
// A real behavioural change — the postal-address check is gone.
const C = `
function evaluateSendRules(c) {
return { ok: true }
}
`
describe('extractFunction', () => {
it('extracts a function by brace matching', () => {
expect(extractFunction(A, 'evaluateSendRules')).toContain('postalAddress')
})
it('handles nested braces without stopping early', () => {
const src = 'function f() { if (x) { g({ a: 1 }) } return 2 }'
expect(extractFunction(src, 'f')).toBe(src)
})
it('finds async and exported declarations', () => {
expect(extractFunction('async function f() { return 1 }', 'f')).toBeTruthy()
expect(extractFunction('export function f() { return 1 }', 'f')).toBeTruthy()
})
it('returns null when the function is absent', () => {
expect(extractFunction(A, 'nope')).toBeNull()
})
})
describe('codeOnly', () => {
it('ignores comments and whitespace so docs may differ', () => {
expect(codeOnly(extractFunction(A, 'evaluateSendRules')!))
.toBe(codeOnly(extractFunction(B, 'evaluateSendRules')!))
})
it('does NOT ignore a behavioural change', () => {
expect(codeOnly(extractFunction(A, 'evaluateSendRules')!))
.not.toBe(codeOnly(extractFunction(C, 'evaluateSendRules')!))
})
})
describe('compareImplementations', () => {
it('reports identical copies as in sync', () => {
const r = compareImplementations(
[{ label: 'a', source: A }, { label: 'b', source: B }],
'evaluateSendRules',
)
expect(r.inSync).toBe(true)
expect(r.groups).toHaveLength(1)
expect(r.vacuous).toBe(false)
})
it('catches the one-sided fix — the failure this exists for', () => {
// A CAN-SPAM guard removed in one repo only.
const r = compareImplementations(
[{ label: 'hosting', source: A }, { label: 'crm', source: C }],
'evaluateSendRules',
)
expect(r.inSync).toBe(false)
expect(r.groups).toHaveLength(2)
expect(formatDrift(r)).toContain('has drifted across')
})
it('groups three copies as 2-vs-1 and names which side is odd', () => {
const r = compareImplementations(
[{ label: 'a', source: A }, { label: 'b', source: B }, { label: 'c', source: C }],
'evaluateSendRules',
)
// Majority group first.
expect(r.groups[0]!.labels).toEqual(['a', 'b'])
expect(r.groups[1]!.labels).toEqual(['c'])
})
it('treats a MISSING function as not-found, never as passing', () => {
// Renamed or deleted is not "no drift" — a guard that cannot find its
// subject is not passing.
const r = compareImplementations(
[{ label: 'a', source: A }, { label: 'gone', source: 'function other() {}' }],
'evaluateSendRules',
)
expect(r.missing).toEqual(['gone'])
expect(r.inSync).toBe(false)
expect(formatDrift(r)).toContain('could not be found')
})
it('flags a single copy as NOT ASSESSED, not in sync', () => {
// One copy cannot disagree with itself; calling that a pass would let a
// deleted sibling read as clean.
const r = compareImplementations([{ label: 'a', source: A }], 'evaluateSendRules')
expect(r.vacuous).toBe(true)
const text = formatDrift(r)
expect(text).toContain('NOT ASSESSED')
expect(text).toContain('One copy cannot disagree with itself')
// The word it replaced needed a glossary.
expect(text).not.toContain('VACUOUS')
})
it('an empty input set is vacuous', () => {
expect(compareImplementations([], 'f').vacuous).toBe(true)
})
})
describe('fingerprint', () => {
it('is stable and differs on changed code', () => {
expect(fingerprint('abc')).toBe(fingerprint('abc'))
expect(fingerprint('abc')).not.toBe(fingerprint('abd'))
})
})
describe('conformance with the evidence-record standard', () => {
const A = 'function evaluateSendRules(c) {\n if (c.unsub) return false\n return true\n}'
const B = 'function evaluateSendRules(c) {\n return true\n}'
it('cites the file of every copy that disagrees', () => {
const r = compareImplementations([
{ label: 'crm', path: 'service-a/lib/send.js', source: A },
{ label: 'hosting', path: 'service-b/lib/send.js', source: B },
], 'evaluateSendRules')
const text = formatDrift(r, { evidence: 'full' })
expect(text).toContain('service-a/lib/send.js')
expect(text).toContain('service-b/lib/send.js')
expect(overall(toReport(r))).toBe('fail')
})
it('cites the copies behind an in-sync pass', () => {
// A pass that cannot name what it compared is an asserted result.
const r = compareImplementations([
{ label: 'crm', path: 'a/send.js', source: A },
{ label: 'hosting', path: 'b/send.js', source: A },
], 'evaluateSendRules')
expect(overall(toReport(r))).toBe('pass')
expect(formatDrift(r, { evidence: 'full' })).toContain('a/send.js')
})
it('every conclusion carries a citation', () => {
const r = compareImplementations([
{ label: 'crm', path: 'a/send.js', source: A },
{ label: 'hosting', path: 'b/send.js', source: B },
], 'evaluateSendRules')
expect(validateReport(toReport(r))).toEqual([])
})
})
describe('a missing subject is a finding, not an absence of one', () => {
it('stays assessed when one copy is present and one is missing', () => {
// `vacuous` counts LOCATED copies, so this pair is vacuous by that
// definition. Reporting it as NOT ASSESSED would hide a deleted or
// renamed sibling behind "nothing was compared" — the exact failure this
// library exists to catch.
const r = compareImplementations(
[
{ label: 'a', path: 'a/send.js', source: 'function evaluateSendRules(c) { return true }' },
{ label: 'gone', path: 'b/send.js', source: 'function other() {}' },
],
'evaluateSendRules',
)
expect(r.vacuous).toBe(true)
expect(overall(toReport(r))).toBe('fail')
expect(formatDrift(r)).toContain('could not be found')
expect(formatDrift(r)).not.toContain('NOT ASSESSED')
})
})
+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"]
}