iterate-plugin 3.9.2 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/README.zh-CN.md +1 -1
- package/dist/tools/decision-log.js +628 -144
- package/dist/tools/history.js +4 -2
- package/dist/tools/prune.js +85 -97
- package/dist/types.js +24 -1
- package/package.json +20 -10
- package/src/tools/decision-log.ts +736 -148
- package/src/tools/history.ts +4 -2
- package/src/tools/prune.ts +104 -95
- package/src/types.ts +41 -12
package/src/tools/history.ts
CHANGED
|
@@ -140,8 +140,10 @@ export function registerHistoryTool(ctx: { tools: { register: (def: ReturnType<t
|
|
|
140
140
|
type: {
|
|
141
141
|
type: 'string',
|
|
142
142
|
description:
|
|
143
|
-
'Optional entry-type filter
|
|
144
|
-
'revert, round_failed, validation, decision, report, resume
|
|
143
|
+
'Optional entry-type (decision kind) filter. This plugin emits round_start, review_result, ' +
|
|
144
|
+
'atomic_fix, architectural_fix, revert, round_failed, validation, decision, report, resume — ' +
|
|
145
|
+
'a LABEL list, not a closed set: any kind matching the kernel grammar ' +
|
|
146
|
+
'(`^[a-z][a-z0-9_]{0,31}$`) can be recorded and filtered here.',
|
|
145
147
|
},
|
|
146
148
|
since: {
|
|
147
149
|
type: 'string',
|
package/src/tools/prune.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* actually delete.
|
|
7
7
|
*
|
|
8
8
|
* Artifacts managed:
|
|
9
|
-
* - Decision-log entries older than `retainDays` (default 30, via since).
|
|
10
9
|
* - Stale checkpoint files (checkpoint.json).
|
|
11
10
|
* - Fix backups left over from old rounds (backups whose fix-id no longer
|
|
12
11
|
* appears in the registry).
|
|
@@ -18,6 +17,14 @@
|
|
|
18
17
|
* - Defense events older than `retainDays` (stale iteration streams that
|
|
19
18
|
* were never cleared).
|
|
20
19
|
*
|
|
20
|
+
* Artifacts NOT managed (deliberately):
|
|
21
|
+
* - The decision ledger (`.iterate/decision-log.jsonl`). It is a hash CHAIN since the
|
|
22
|
+
* kernel flip: every entry's `prevEntryHash` is the sha256 of the previous line's
|
|
23
|
+
* bytes, so dropping or renumbering one line invalidates every later one. Prune
|
|
24
|
+
* therefore never rewrites it — `pruneDecisionLog` reports what a cutoff WOULD have
|
|
25
|
+
* removed and names the two legal moves instead. The renumbering rewrite that used to
|
|
26
|
+
* live here is deleted, not hidden behind a flag.
|
|
27
|
+
*
|
|
21
28
|
* Security model:
|
|
22
29
|
* - Only operates under the resolved project `.iterate/` directory.
|
|
23
30
|
* - dryRun=true by default — the caller must explicitly opt into deletion.
|
|
@@ -30,16 +37,16 @@ import { join } from 'node:path'
|
|
|
30
37
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
31
38
|
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
32
39
|
import { resolveProjectRootForExec } from '../config-loader.ts'
|
|
33
|
-
import {
|
|
40
|
+
import { writeJsonAtomic } from '../atomic-fs.ts'
|
|
34
41
|
import { withProjectLock } from '../file-lock.ts'
|
|
35
|
-
import { readDecisionEntries, appendDecisionEntry,
|
|
42
|
+
import { readDecisionEntries, appendDecisionEntry, decisionLogContestedLine } from './decision-log.ts'
|
|
36
43
|
import { readRegistry, removeRecord, recomputeRoundCounts } from './fix.ts'
|
|
37
44
|
import { readExperienceBank, writeExperienceBank } from './experience-store.ts'
|
|
38
45
|
import { readDefenseEvents, writeDefenseEvents } from './defense-store.ts'
|
|
39
46
|
import { iterateDir, fixesDir, checkpointPath, fixRegistryPath } from '../paths.ts'
|
|
40
47
|
import type { DefenseEventStream, ExperienceBank, FixRegistry } from '../types.ts'
|
|
41
48
|
|
|
42
|
-
/**
|
|
49
|
+
/** Retention window for the age-based artifacts (checkpoints, defense events). */
|
|
43
50
|
const DEFAULT_RETAIN_DAYS = 30
|
|
44
51
|
const MIN_RETAIN_DAYS = 1
|
|
45
52
|
const MAX_RETAIN_DAYS = 365
|
|
@@ -52,9 +59,6 @@ const MAX_RETAIN_DAYS = 365
|
|
|
52
59
|
*/
|
|
53
60
|
export const MAX_EXPERIENCE_ENTRIES = 2000
|
|
54
61
|
|
|
55
|
-
/** How many compare-and-append retries bound the decision-log rewrite race. */
|
|
56
|
-
const MAX_LOG_REWRITE_RETRIES = 3
|
|
57
|
-
|
|
58
62
|
/** Clamp retainDays to a sane range. */
|
|
59
63
|
export function clampRetainDays(days: number | undefined): number {
|
|
60
64
|
if (typeof days !== 'number' || !Number.isInteger(days) || days <= 0) {
|
|
@@ -163,6 +167,9 @@ export function inspectPrune(
|
|
|
163
167
|
retainDays: number,
|
|
164
168
|
): {
|
|
165
169
|
oldLogEntries: number
|
|
170
|
+
/** 1-based line of the first legacy record AFTER the chain began (a contested ledger),
|
|
171
|
+
* or null. This is the only decision-log CONDITION prune can report but not settle. */
|
|
172
|
+
contestedLogLine: number | null
|
|
166
173
|
hasCheckpoint: boolean
|
|
167
174
|
checkpointStale: boolean
|
|
168
175
|
staleBackups: string[]
|
|
@@ -180,9 +187,12 @@ export function inspectPrune(
|
|
|
180
187
|
} {
|
|
181
188
|
const cutoff = cutoffTimestamp(retainDays)
|
|
182
189
|
|
|
183
|
-
// 1. Decision-log entries older than retainDays
|
|
190
|
+
// 1. Decision-log entries older than retainDays — REPORTED, never removed: the ledger
|
|
191
|
+
// is a hash chain (see `pruneDecisionLog`). `contestedLogLine` is the one ledger
|
|
192
|
+
// condition a human has to settle, so the dry-run preview surfaces it.
|
|
184
193
|
const entries = readDecisionEntries(projectRoot)
|
|
185
194
|
const oldLogEntries = entries.filter((e) => e.timestamp < cutoff).length
|
|
195
|
+
const contestedLogLine = decisionLogContestedLine(projectRoot)
|
|
186
196
|
|
|
187
197
|
// 2. Checkpoint presence. A checkpoint is only STALE once it is older than
|
|
188
198
|
// retainDays — a fresh checkpoint means a run was interrupted recently and
|
|
@@ -280,6 +290,7 @@ export function inspectPrune(
|
|
|
280
290
|
|
|
281
291
|
return {
|
|
282
292
|
oldLogEntries,
|
|
293
|
+
contestedLogLine,
|
|
283
294
|
hasCheckpoint,
|
|
284
295
|
checkpointStale,
|
|
285
296
|
staleBackups,
|
|
@@ -300,91 +311,70 @@ export function inspectPrune(
|
|
|
300
311
|
* Returns a detailed report of what was deleted.
|
|
301
312
|
*/
|
|
302
313
|
/**
|
|
303
|
-
*
|
|
314
|
+
* What a cutoff WOULD have removed from the decision ledger — and why nothing is removed.
|
|
304
315
|
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
316
|
+
* THE RENUMBERING REWRITE IS GONE, NOT HIDDEN BEHIND A FLAG. It used to filter the log to
|
|
317
|
+
* the entries younger than `cutoff` and rewrite the whole file (temp + atomic rename, a
|
|
318
|
+
* bounded retry loop, the cross-process lock). Against the kernel's hash chain that is not
|
|
319
|
+
* a destructive-but-honest trim, it is forgery: entry i+1's `prevEntryHash` is the sha256
|
|
320
|
+
* of entry i's BYTES, so dropping line 3 makes every later line's recorded link wrong —
|
|
321
|
+
* and re-emitting the survivors with renumbered `sequence` and recomputed hashes produces
|
|
322
|
+
* a chain that verifies while proving nothing, because it now claims "these decisions were
|
|
323
|
+
* contiguous" about a file somebody deleted the middle of.
|
|
313
324
|
*
|
|
314
|
-
*
|
|
315
|
-
* and
|
|
316
|
-
* structured error rather than proceeding unlocked — an unlocked rename can
|
|
317
|
-
* drop a concurrent appender's audit line (its fd points at the pre-rename
|
|
318
|
-
* inode) with no way to detect the loss afterwards.
|
|
325
|
+
* So prune reads, reports, and touches nothing. The two legal moves belong to the ledger
|
|
326
|
+
* itself and both are named in the remedy:
|
|
319
327
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
328
|
+
* 1. keep appending — an all-legacy log migrates onto the chain on its next write
|
|
329
|
+
* (verified before the swap, the original kept beside it at mode 0600) and a healthy
|
|
330
|
+
* chain simply grows; a line is bounded by PAYLOAD_MAX_CANONICAL_LENGTH, not by age;
|
|
331
|
+
* 2. settle a CONTESTED ledger by CHOOSING archive-and-restart
|
|
332
|
+
* (`iterate_decision_log({operation:"archive"})` / `archiveContestedDecisionLog`),
|
|
333
|
+
* which renames the file to `…contested-<stamp>.jsonl` at mode 0600 and lets the next
|
|
334
|
+
* append start a fresh chain. It refuses a healthy or an all-legacy ledger, so it can
|
|
335
|
+
* never become a way to erase continuity by accident.
|
|
324
336
|
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* @param options.lock lock wait/stale budgets (tests pass short waits so the
|
|
329
|
-
* lock-unavailable refusal is exercised in milliseconds, not 5s).
|
|
330
|
-
* @returns the number of entries removed across all attempts, or 0 with an
|
|
331
|
-
* `error` when nothing needed removing or the rewrite was refused/failed.
|
|
337
|
+
* `cutoff` stays in the signature because the REPORT needs it: `retained` is exactly the
|
|
338
|
+
* number of lines the old code would have deleted, which is what a human weighs when
|
|
339
|
+
* deciding whether a ledger is worth settling.
|
|
332
340
|
*/
|
|
333
|
-
export function
|
|
341
|
+
export function pruneDecisionLog(
|
|
334
342
|
projectRoot: string,
|
|
335
343
|
cutoff: string,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
// The append-side entry-count cache is keyed by path and assumes "same
|
|
363
|
-
// byte size ⇒ same count"; the rename above replaced the file with a
|
|
364
|
-
// different number of entries, so drop the stale cache or the next
|
|
365
|
-
// append can report a wrong entryCount when the sizes coincidentally
|
|
366
|
-
// match.
|
|
367
|
-
invalidateLogCountCache(logPath)
|
|
368
|
-
// Accumulate THIS attempt's deletions before the re-read: whatever the
|
|
369
|
-
// later attempts (or the exhaustion path) report, these entries are
|
|
370
|
-
// already gone from disk and must count toward the audit total.
|
|
371
|
-
deletedTotal += deleted
|
|
372
|
-
options.afterRewrite?.()
|
|
373
|
-
// A concurrent appender (cross-process, lock-unavailable path) may have
|
|
374
|
-
// landed new lines after our read. If the log grew during the write
|
|
375
|
-
// window, re-read and prune again instead of accepting a lost audit trail.
|
|
376
|
-
const after = readDecisionEntries(projectRoot)
|
|
377
|
-
if (after.length <= kept.length) return { deleted: deletedTotal }
|
|
378
|
-
} catch (err) {
|
|
379
|
-
return { deleted: deletedTotal, error: `failed to rewrite decision log: ${String(err)}` }
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return { deleted: deletedTotal, error: `failed to rewrite decision log after ${MAX_LOG_REWRITE_RETRIES} attempts` }
|
|
383
|
-
} finally {
|
|
384
|
-
release()
|
|
344
|
+
): {
|
|
345
|
+
deleted: number
|
|
346
|
+
retained: number
|
|
347
|
+
contestedLine: number | null
|
|
348
|
+
code: string
|
|
349
|
+
error: string
|
|
350
|
+
remedy: string
|
|
351
|
+
} {
|
|
352
|
+
const entries = readDecisionEntries(projectRoot)
|
|
353
|
+
const retained = entries.filter((e) => e.timestamp < cutoff).length
|
|
354
|
+
const contestedLine = decisionLogContestedLine(projectRoot)
|
|
355
|
+
return {
|
|
356
|
+
deleted: 0,
|
|
357
|
+
retained,
|
|
358
|
+
contestedLine,
|
|
359
|
+
code: 'ITERATE_E_DECISION_LOG_IMMUTABLE',
|
|
360
|
+
error:
|
|
361
|
+
`the decision ledger is a hash chain: ${retained} ` +
|
|
362
|
+
`entr${retained === 1 ? 'y' : 'ies'} older than ${cutoff} are RETAINED, not rewritten`,
|
|
363
|
+
remedy:
|
|
364
|
+
'a chain cannot be trimmed in place — every later line links to the bytes of the line before it, ' +
|
|
365
|
+
'so deleting or renumbering one forges the rest. Two legal moves: (1) keep appending: an all-legacy ' +
|
|
366
|
+
'ledger migrates onto the chain on its next write and a healthy one just grows; (2) if an append ' +
|
|
367
|
+
'reports ITERATE_E_DECISION_LOG_INTERLEAVED, a human settles it with archive-and-restart — ' +
|
|
368
|
+
'iterate_decision_log({operation:"archive"}) moves the contested ledger aside losslessly (mode 0600) ' +
|
|
369
|
+
'and the next append starts a clean chain. Prune never deletes a ledger line.',
|
|
385
370
|
}
|
|
386
371
|
}
|
|
387
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Actually prune the runtime artifacts (only called when dryRun=false).
|
|
375
|
+
* Returns a detailed report of what was deleted.
|
|
376
|
+
*/
|
|
377
|
+
|
|
388
378
|
/**
|
|
389
379
|
* Execute the prune: apply every cleanable item reported by `inspectPrune`.
|
|
390
380
|
* Every step is individually hedged so a single disk failure degrades to a
|
|
@@ -396,6 +386,10 @@ export function executePrune(
|
|
|
396
386
|
report: ReturnType<typeof inspectPrune>,
|
|
397
387
|
): {
|
|
398
388
|
deletedLogEntries: number
|
|
389
|
+
/** Lines older than the cutoff the ledger KEEPS (a hash chain is not age-trimmed). */
|
|
390
|
+
retainedLogEntries: number
|
|
391
|
+
/** The immutable-ledger refusal: code + what was not done + the two legal moves. */
|
|
392
|
+
decisionLog: { code: string; error: string; remedy: string }
|
|
399
393
|
deletedCheckpoint: boolean
|
|
400
394
|
deletedBackups: string[]
|
|
401
395
|
deletedTemps: string[]
|
|
@@ -405,8 +399,13 @@ export function executePrune(
|
|
|
405
399
|
errors: string[]
|
|
406
400
|
} {
|
|
407
401
|
const cutoff = cutoffTimestamp(retainDays)
|
|
402
|
+
const ledger = pruneDecisionLog(projectRoot, cutoff)
|
|
408
403
|
const result = {
|
|
404
|
+
// Always 0: a ledger line is never deleted by prune. The field stays because the
|
|
405
|
+
// prune summary (and the audit entry it logs) has always carried it.
|
|
409
406
|
deletedLogEntries: 0,
|
|
407
|
+
retainedLogEntries: ledger.retained,
|
|
408
|
+
decisionLog: { code: ledger.code, error: ledger.error, remedy: ledger.remedy },
|
|
410
409
|
deletedCheckpoint: false,
|
|
411
410
|
deletedBackups: [] as string[],
|
|
412
411
|
deletedTemps: [] as string[],
|
|
@@ -416,15 +415,15 @@ export function executePrune(
|
|
|
416
415
|
errors: [] as string[],
|
|
417
416
|
}
|
|
418
417
|
|
|
419
|
-
// 1.
|
|
420
|
-
// (
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
418
|
+
// 1. The decision ledger is READ-ONLY here: nothing is deleted, nothing is rewritten
|
|
419
|
+
// (see `pruneDecisionLog`). The refusal is always reported; a CONTESTED ledger is
|
|
420
|
+
// additionally escalated into `errors`, because that is a broken state only a human can
|
|
421
|
+
// settle — archive-and-restart is offered, never taken.
|
|
422
|
+
if (ledger.contestedLine !== null) {
|
|
423
|
+
result.errors.push(
|
|
424
|
+
`line ${ledger.contestedLine} of the decision ledger is a legacy record appended after the chain ` +
|
|
425
|
+
`began (ITERATE_E_DECISION_LOG_INTERLEAVED). ${ledger.remedy}`,
|
|
426
|
+
)
|
|
428
427
|
}
|
|
429
428
|
|
|
430
429
|
// 2. Remove checkpoint — ONLY when it is actually stale (older than
|
|
@@ -548,9 +547,12 @@ export function registerPruneTool(ctx: { tools: { register: (def: ReturnType<typ
|
|
|
548
547
|
description:
|
|
549
548
|
'Inspect or clean up old iterate runtime artifacts (.iterate/). ' +
|
|
550
549
|
'Defaults to dry-run (report-only, no deletion). Pass `dryRun: false` to actually prune. ' +
|
|
551
|
-
'Manages:
|
|
550
|
+
'Manages: stale checkpoints, orphaned fix backups, empty fix rounds, ' +
|
|
552
551
|
'stray temp files left by crashed atomic writes, experience-bank entries beyond the entry cap, ' +
|
|
553
552
|
'and stale defense events. ' +
|
|
553
|
+
'The decision ledger is NEVER trimmed: it is a hash chain, so a removed or renumbered line ' +
|
|
554
|
+
'would invalidate every later one. Prune reports how many lines a cutoff would have removed ' +
|
|
555
|
+
'and names the two legal moves (keep appending / human-chosen archive-and-restart). ' +
|
|
554
556
|
'A non-dry-run appends ONE aggregate summary entry (counts per artifact class) to the decision log.',
|
|
555
557
|
parameters: {
|
|
556
558
|
dryRun: {
|
|
@@ -588,7 +590,10 @@ export function registerPruneTool(ctx: { tools: { register: (def: ReturnType<typ
|
|
|
588
590
|
const registryError = report?.registryError as string | null | undefined
|
|
589
591
|
const lines = [
|
|
590
592
|
`[dry-run] prune report (retainDays=${value.retainDays}):`,
|
|
591
|
-
` Decision-log entries
|
|
593
|
+
` Decision-log entries older than retainDays: ${report?.oldLogEntries ?? '?'} (of ${report?.totalLogEntries ?? '?'}) — RETAINED, a hash chain is never trimmed`,
|
|
594
|
+
report?.contestedLogLine
|
|
595
|
+
? ` CONTESTED ledger: line ${report.contestedLogLine} is a legacy record after the chain — settle it with iterate_decision_log({operation:"archive"})`
|
|
596
|
+
: ' Ledger continuity: no interleaved legacy records',
|
|
592
597
|
` Checkpoint to delete: ${report?.checkpointStale ? 'yes (stale)' : report?.hasCheckpoint ? 'no (fresh — resume point, kept)' : 'none'}`,
|
|
593
598
|
registryError
|
|
594
599
|
? ` Stale backups to delete: none — ${registryError}`
|
|
@@ -604,7 +609,7 @@ export function registerPruneTool(ctx: { tools: { register: (def: ReturnType<typ
|
|
|
604
609
|
}
|
|
605
610
|
const lines = [
|
|
606
611
|
`Prune complete (retainDays=${value.retainDays}):`,
|
|
607
|
-
`
|
|
612
|
+
` Decision-log lines deleted: ${result?.deletedLogEntries ?? 0} — ${result?.retainedLogEntries ?? 0} older than the cutoff RETAINED (${(result?.decisionLog as { code?: string } | undefined)?.code ?? 'hash chain'}: never renumbered).`,
|
|
608
613
|
` Checkpoint deleted: ${result?.deletedCheckpoint ? 'yes' : 'no'}`,
|
|
609
614
|
` Deleted ${(result?.deletedBackups as string[] | undefined)?.length ?? 0} stale backups.`,
|
|
610
615
|
` Deleted ${(result?.deletedTemps as string[] | undefined)?.length ?? 0} stray temp files.`,
|
|
@@ -652,6 +657,10 @@ export function registerPruneTool(ctx: { tools: { register: (def: ReturnType<typ
|
|
|
652
657
|
action: 'prune',
|
|
653
658
|
retainDays,
|
|
654
659
|
deletedLogEntries: result.deletedLogEntries,
|
|
660
|
+
retainedLogEntries: result.retainedLogEntries,
|
|
661
|
+
// The ledger itself was not touched; record WHY, so an audit reader can tell
|
|
662
|
+
// "prune deleted nothing" apart from "prune was refused by an immutable chain".
|
|
663
|
+
decisionLogRefusal: result.decisionLog.code,
|
|
655
664
|
deletedCheckpoint: result.deletedCheckpoint,
|
|
656
665
|
deletedBackups: result.deletedBackups.length,
|
|
657
666
|
trimmedEmptyRounds: result.trimmedEmptyRounds,
|
package/src/types.ts
CHANGED
|
@@ -53,21 +53,50 @@ export interface IterateConfig {
|
|
|
53
53
|
personalization?: Record<string, unknown>
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* The decision kinds THIS plugin's own producers write.
|
|
58
|
+
*
|
|
59
|
+
* DISPLAY AND DOCUMENTATION ONLY — never a validity rule. Membership in this list
|
|
60
|
+
* used to be the gate on `iterate_decision_log` appends, and it disagreed with the
|
|
61
|
+
* Python harness's own list (which rejected `round_failed` and `resume` outright):
|
|
62
|
+
* two shells, two closed sets, one shared ledger. The disagreement WAS the bug.
|
|
63
|
+
* Validity is now the kernel's kind GRAMMAR (`DECISION_KIND_PATTERN`,
|
|
64
|
+
* `^[a-z][a-z0-9_]{0,31}$`) — see `src/tools/decision-log.ts` `isDecisionKind` —
|
|
65
|
+
* deliberately open so adding a kind does not need a kernel release, and so a kind
|
|
66
|
+
* produced by either shell is readable by the other.
|
|
67
|
+
*/
|
|
68
|
+
export const KNOWN_DECISION_KINDS = [
|
|
69
|
+
'round_start',
|
|
70
|
+
'review_result',
|
|
71
|
+
'atomic_fix',
|
|
72
|
+
'architectural_fix',
|
|
73
|
+
'revert',
|
|
74
|
+
'validation',
|
|
75
|
+
'decision',
|
|
76
|
+
'report',
|
|
77
|
+
'round_failed',
|
|
78
|
+
'resume',
|
|
79
|
+
] as const
|
|
80
|
+
|
|
81
|
+
/** One of the kinds this plugin emits itself (a label, not an exhaustive set). */
|
|
82
|
+
export type KnownDecisionKind = (typeof KNOWN_DECISION_KINDS)[number]
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* One entry in the append-only decision log — the loop's RECORD shape.
|
|
86
|
+
*
|
|
87
|
+
* This is what a producer hands to `appendDecisionEntry` and what every reader sees
|
|
88
|
+
* back. It is NOT the shape on disk: the ledger stores the kernel's canonical
|
|
89
|
+
* hash-chained entry, and `decision-log.ts` transcribes on the way in
|
|
90
|
+
* (`type`→`kind`, `data`→`payload`, `timestamp`→`createdAt`) and projects back out
|
|
91
|
+
* on the way read (`projectDecisionRecord`).
|
|
92
|
+
*
|
|
93
|
+
* `type` is an open string (kernel grammar) — see {@link KNOWN_DECISION_KINDS} for
|
|
94
|
+
* what this plugin actually emits.
|
|
95
|
+
*/
|
|
57
96
|
export interface DecisionLogEntry {
|
|
58
97
|
timestamp: string
|
|
59
98
|
round: number
|
|
60
|
-
type:
|
|
61
|
-
| 'round_start'
|
|
62
|
-
| 'review_result'
|
|
63
|
-
| 'atomic_fix'
|
|
64
|
-
| 'architectural_fix'
|
|
65
|
-
| 'revert'
|
|
66
|
-
| 'validation'
|
|
67
|
-
| 'decision'
|
|
68
|
-
| 'report'
|
|
69
|
-
| 'round_failed'
|
|
70
|
-
| 'resume'
|
|
99
|
+
type: string
|
|
71
100
|
data: Record<string, unknown>
|
|
72
101
|
}
|
|
73
102
|
|