docs: make technical prose concrete

This commit is contained in:
Turtle
2026-08-09 15:27:21 +08:00
parent 673e7cddc5
commit a27efdef36
459 changed files with 1342 additions and 1329 deletions

View File

@@ -72,7 +72,7 @@ function nonNegativeInteger(value: unknown, field: string): number {
/** Decode one canonical blocker explanation. */
function decodeBlockReason(value: unknown): GoalBlockReason {
if (!isRecord(value) || Object.keys(value).sort().join(',') !== 'code,message') {
throw new Error('goal change goal.blockedReason has an invalid shape')
throw new Error('goal change goal.blockedReason must have exactly code and message fields')
}
if (typeof value['code'] !== 'string' || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value['code'])) {
throw new Error('goal change goal.blockedReason.code must be lower-kebab-case')
@@ -102,7 +102,7 @@ function decodeSnapshot(value: unknown): GoalSnapshot {
? 'blockedReason,id,maxGoalRounds,objective,phase,revision'
: 'id,maxGoalRounds,objective,phase,revision'
if (Object.keys(value).sort().join(',') !== expectedKeys) {
throw new Error('goal change goal has an invalid shape')
throw new Error(`goal change goal for phase ${phase} must have exactly ${expectedKeys} fields`)
}
return {
id: GoalId(value['id']),
@@ -117,7 +117,7 @@ function decodeSnapshot(value: unknown): GoalSnapshot {
/** Decode and validate one ref. */
function decodeRef(value: unknown): GoalRef {
if (!isRecord(value) || Object.keys(value).sort().join(',') !== 'id,revision') {
throw new Error('goal clear tombstone has an invalid shape')
throw new Error('goal clear tombstone must have exactly id and revision fields')
}
if (typeof value['id'] !== 'string' || value['id'].length === 0) {
throw new Error('goal clear tombstone id must be a non-empty string')
@@ -139,7 +139,7 @@ export function decodeGoalChange(value: unknown): GoalChangeMeta | undefined {
if (value['operation'] === 'clear') {
const allowed = ['cleared', 'clearedAt', 'kind', 'operation', 'version']
if (Object.keys(value).sort().join(',') !== allowed.sort().join(',')) {
throw new Error('goal clear change has an invalid shape')
throw new Error(`goal clear change must have exactly ${allowed.sort().join(',')} fields`)
}
return {
kind: 'goal/change',
@@ -155,7 +155,7 @@ export function decodeGoalChange(value: unknown): GoalChangeMeta | undefined {
}
const allowed = ['createdAt', 'goal', 'kind', 'operation', 'roundsStarted', 'updatedAt', 'version']
if (Object.keys(value).sort().join(',') !== allowed.sort().join(',')) {
throw new Error('goal snapshot change has an invalid shape')
throw new Error(`goal snapshot change must have exactly ${allowed.sort().join(',')} fields`)
}
const createdAt = nonNegativeInteger(value['createdAt'], 'createdAt')
const updatedAt = nonNegativeInteger(value['updatedAt'], 'updatedAt')

View File

@@ -502,8 +502,8 @@ describe('GoalService mutations', () => {
session.append('goal/change', change)
session.append('goal/change', { ...change, operation: 'edit', extra: true } as never)
expect(() => ctx.goals.get(agent)).toThrow('invalid shape')
expect(() => ctx.goals.get(agent)).toThrow('invalid shape')
expect(() => ctx.goals.get(agent)).toThrow('snapshot change must have exactly')
expect(() => ctx.goals.get(agent)).toThrow('snapshot change must have exactly')
})
})
@@ -609,13 +609,13 @@ describe('goal replay validation', () => {
expect(() => foldGoal(session.events)).toThrow('not the next admitted round')
})
it('rejects unsupported versions, operations, and top-level shapes', () => {
it('rejects unsupported versions, operations, and extra top-level fields', () => {
expect(() => decodeGoalChange({ ...snapshotChange(), version: 2 })).toThrow('unsupported goal change version')
expect(() => decodeGoalChange({ ...snapshotChange(), operation: 'explode' })).toThrow('operation is invalid')
expect(() => decodeGoalChange({ ...snapshotChange(), extra: true })).toThrow('snapshot change has an invalid shape')
expect(() => decodeGoalChange({ ...snapshotChange(), extra: true })).toThrow('snapshot change must have exactly')
expect(() => decodeGoalChange({
kind: 'goal/change', version: 1, operation: 'clear', cleared: { id: 'x', revision: 2 }, clearedAt: 1, extra: true,
})).toThrow('clear change has an invalid shape')
})).toThrow('clear change must have exactly')
})
it('rejects invalid create and missing-current mutation sequences', () => {