67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
/**
|
|
* Worked example: cap by the worst defect.
|
|
*
|
|
* A hand-scored assessment of a fictional product. The scorer rated Security
|
|
* at 70 and noted, in the same row, an open defect that the rubric caps at 45
|
|
* (a secret that was exposed and never rotated). A human reading prose can be
|
|
* charitable and skip the cap. The library cannot: the lowest open cap wins,
|
|
* and the product's overall score is its weakest dimension, not the average.
|
|
*
|
|
* Run: npx tsx examples/cap-by-worst-defect.ts
|
|
*/
|
|
import { scoreProduct, type DimensionAssessment, type DimensionKey } from '../src/index.js'
|
|
|
|
/** The overall score a hand-scored summary might report: roughly the average. */
|
|
const HAND_SCORED_OVERALL = 69
|
|
|
|
const dimensions: Record<DimensionKey, DimensionAssessment> = {
|
|
security: {
|
|
raw: 70,
|
|
// The scorer named this cap and still wrote 70.
|
|
openCaps: [{
|
|
id: 'sec.leaked-secret',
|
|
evidence: [{ ref: 'docs/security-review.md:14', note: 'exposed API key not yet rotated' }],
|
|
}],
|
|
evidence: [{ ref: 'docs/security-review.md' }],
|
|
bindingConstraint: 'exposed API key not yet rotated',
|
|
nextAction: 'rotate the key and redeploy',
|
|
},
|
|
bugs: {
|
|
raw: 68,
|
|
evidence: [{ ref: 'test/api.test.ts' }],
|
|
bindingConstraint: 'API handlers lack behaviour tests',
|
|
},
|
|
compliance: {
|
|
raw: 73,
|
|
evidence: [{ ref: 'docs/compliance-checklist.md' }],
|
|
},
|
|
consistency: {
|
|
raw: 68,
|
|
evidence: [{ ref: 'shared/ui/' }],
|
|
},
|
|
usability: {
|
|
raw: 68,
|
|
evidence: [{ ref: 'reports/accessibility-scan.json' }],
|
|
bindingConstraint: 'accessibility findings not yet triaged',
|
|
},
|
|
}
|
|
|
|
const result = scoreProduct({ product: 'example-app', tier: 'A', dimensions })
|
|
|
|
console.log('Hand-scored vs rubric-enforced\n')
|
|
for (const d of result.dimensions) {
|
|
const flag = d.boundBy ? ` <- CAPPED by ${d.boundBy.id} (${d.boundBy.max})` : ''
|
|
console.log(
|
|
` ${d.dimension.padEnd(12)} raw ${String(d.raw).padStart(3)} -> ${String(d.score).padStart(3)} ${d.band.name}${flag}`,
|
|
)
|
|
}
|
|
console.log(`\n hand-scored overall : ~${HAND_SCORED_OVERALL}`)
|
|
console.log(` enforced overall : ${result.overall} (${result.band.name})`)
|
|
console.log(` weakest : ${result.weakest}`)
|
|
console.log(` average : ${result.average} (reported, never the headline)`)
|
|
console.log(` delta : ${result.overall - HAND_SCORED_OVERALL}`)
|
|
|
|
if (result.overall < HAND_SCORED_OVERALL) {
|
|
console.log('\n The hand-scored number is higher than the rubric permits.')
|
|
}
|