1
0

reliability-checks.mjs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import assert from 'node:assert/strict'
  2. import fs from 'node:fs'
  3. import path from 'node:path'
  4. import { pathToFileURL } from 'node:url'
  5. export async function checkReliability({ root, book, main, ctx, service, execute, git, check, report, withTurn }) {
  6. const core = await import(pathToFileURL(path.join(root, 'native-write-core.mjs')).href)
  7. const key = { 卷: 9, 章: 9001, 章名: '可靠性样本' }
  8. const args = { bookId: 'loader-book', ...key }
  9. const put = (rel, text) => { fs.mkdirSync(path.dirname(path.join(book, rel)), { recursive: true }); fs.writeFileSync(path.join(book, rel), text) }
  10. const read = rel => fs.readFileSync(path.join(book, rel), 'utf8')
  11. const call = async (name, arguments_) => {
  12. const result = await withTurn(main, () => execute(main, name, arguments_))
  13. assert.equal(result.isError, false, JSON.stringify(result))
  14. return result.value
  15. }
  16. await check('纯处置保留证据、拒绝旧记录并允许上游保留', async () => {
  17. const recordRel = '草稿区/审核/卷09-可靠性样本.json'
  18. put('草稿区/草稿/卷09-可靠性样本/稿1.md', core.serializeDocument({ 角色: '待审稿', 版本: 1 }, '中文待审稿。'))
  19. core.writeReviewRecord(book, key, {
  20. schemaVersion: 1, 完成: true, 审稿哈希: 'a'.repeat(64), 审读指纹: 'b'.repeat(64), 模块: { 设定核对: { 完成: true, 失败: false } },
  21. 问题: Array.from({ length: 21 }, (_, i) => core.normalizeFinding({ 发现项编号: `reliable-${i}`, 模块名: '设定核对', 问题说明: `中文记录${i}`, 证据位置: `第${i}段`, 影响范围: i === 0 ? '世界书' : '正文', 处置状态: '待处理' })),
  22. })
  23. const before = JSON.parse(read(recordRel))
  24. const beforeHead = git('rev-parse', 'HEAD')
  25. const status = await call('novel_get_story_status', { bookId: 'loader-book' })
  26. const hash = status.chapters.find(chapter => chapter.key.章名 === key.章名).审核记录哈希
  27. assert.equal(hash, core.reviewRecordHashOf(read(recordRel)))
  28. const result = await call('novel_apply_revision_batch', { ...args, 审核记录哈希: hash, 处置: [{ 发现项编号: 'reliable-0', 处置: '作者保留', 改动说明: '本次不改上游' }] })
  29. assert.equal(result.ok, true)
  30. assert.equal(result.审核记录哈希, core.reviewRecordHashOf(read(recordRel)))
  31. const after = JSON.parse(read(recordRel))
  32. assert.equal(after.审稿哈希, before.审稿哈希)
  33. assert.equal(after.审读指纹, before.审读指纹)
  34. assert.deepEqual(after.问题.slice(1), before.问题.slice(1))
  35. assert.deepEqual(after.模块, before.模块)
  36. assert.equal(git('rev-parse', 'HEAD'), beforeHead)
  37. const saved = read(recordRel)
  38. const conflict = await call('novel_apply_revision', { ...args, 审核记录哈希: hash, 发现项编号: 'reliable-1', 处置: '已驳回' })
  39. assert.equal(conflict.ok, false)
  40. assert.match(conflict.reason, /已变化/)
  41. const halfPatch = await call('novel_apply_revision', { ...args, 发现项编号: 'reliable-1', 处置: '已解决', 补丁旧文: '中文' })
  42. assert.equal(halfPatch.ok, false)
  43. assert.match(halfPatch.reason, /成对/)
  44. assert.equal(read(recordRel), saved)
  45. report.dispositionReliability = { findings: 21, sourceUnchanged: true, fingerprintsPreserved: true, staleRejected: true, partialPatchRejected: true }
  46. })
  47. await check('当前宿主中文候选生成编辑确认与失败恢复', async () => {
  48. const candidate = '草稿区/章细纲/卷09-可靠性样本.md'
  49. put('大纲/卷规划/卷09/近期窗口.md', '# 近期窗口\n\n- 可靠性样本 〔已确认〕\n')
  50. const unit = i => `### 单元 ${i}\n${core.单元字段.map(field => `- ${field}: 中文第${i}项,“引号”、破折号——😀 é A1。`).join('\n')}`
  51. const body = `# 章细纲\n\n## 定位段\n${core.定位段小节.map(name => `### ${name}\n${name === '章节功能' ? '- 〔硬〕' : ''}主角抵达城门,核对前置。`).join('\n\n')}\n\n## 细纲段\n\n${Array.from({ length: 12 }, (_, i) => unit(i + 1)).join('\n\n')}\n`
  52. const createArgs = { bookId: 'loader-book', 卷: 9, 章名: key.章名, 正文: body, 来源引用: ['作品契约/契约.md@1'] }
  53. assert.equal((await call('novel_new_outline_draft', createArgs)).ok, true)
  54. const initial = read(candidate)
  55. assert.equal(core.parseDocument(initial).data.body.trim(), body.trim())
  56. const file_path = path.join(book, candidate)
  57. assert.equal((await execute(main, 'read', { file_path })).isError, false)
  58. const off = ctx.on('approval/request', async () => 'allowed-once')
  59. const editSizes = []
  60. let expected = initial
  61. try {
  62. for (let i = 1; i <= 5; i++) {
  63. const old_string = unit(i)
  64. const new_string = unit(i).replaceAll('核对', '复核').replaceAll('中文第', '中文修订第')
  65. const editArgs = { file_path, old_string, new_string }
  66. const edited = await withTurn(main, () => execute(main, 'edit', editArgs))
  67. assert.equal(edited.isError, false, JSON.stringify(edited))
  68. expected = expected.replace(old_string, new_string)
  69. assert.equal(read(candidate), expected)
  70. editSizes.push(Buffer.byteLength(JSON.stringify(editArgs)))
  71. }
  72. const beforeFailure = read(candidate)
  73. const failed = await withTurn(main, () => execute(main, 'edit', { file_path, old_string: '不存在的整段中文', new_string: '不可写入' }))
  74. assert.equal(failed.isError, true)
  75. assert.equal(read(candidate), beforeFailure)
  76. assert.equal((await execute(main, 'read', { file_path })).isError, false)
  77. const correction = await withTurn(main, () => execute(main, 'edit', { file_path, old_string: unit(6), new_string: unit(6).replaceAll('中文第', '中文重读第') }))
  78. assert.equal(correction.isError, false)
  79. expected = expected.replace(unit(6), unit(6).replaceAll('中文第', '中文重读第'))
  80. assert.equal(read(candidate), expected)
  81. const confirmArgs = { ...args, summary: '可靠性候选确认' }
  82. const confirmation = await call('novel_confirm_outline', confirmArgs)
  83. assert.equal(confirmation.ok, true, JSON.stringify(confirmation))
  84. assert.equal(fs.existsSync(file_path), false)
  85. const confirmed = read('大纲/卷规划/卷09/章细纲/9001-可靠性样本.md')
  86. assert.equal(core.parseDocument(confirmed).data.body.trim(), core.parseDocument(expected).data.body.trim())
  87. assert.match(git('log', '-1', '--format=%s'), /^design\(ch9001\):/)
  88. report.chineseRoundtrip = { generatedBytes: Buffer.byteLength(body), creationPayloadBytes: Buffer.byteLength(JSON.stringify(createArgs)), maximumEditPayloadBytes: Math.max(...editSizes), confirmationPayloadBytes: Buffer.byteLength(JSON.stringify(confirmArgs)), edits: 6, byteExact: true, failedEditUnchanged: true, backendGeneration: 'not covered by this deterministic Loader test' }
  89. } finally { off() }
  90. })
  91. }