@ai-agent-forge/agent-forge 0.88.7 → 0.88.8

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
  3. package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
  4. package/bundled-plugins/alignment/agent-forge.json +1 -1
  5. package/bundled-plugins/alignment/dist/engine.d.ts.map +1 -1
  6. package/bundled-plugins/alignment/dist/engine.js +6 -0
  7. package/bundled-plugins/alignment/dist/engine.js.map +1 -1
  8. package/bundled-plugins/alignment/dist/index.d.ts.map +1 -1
  9. package/bundled-plugins/alignment/dist/index.js +1 -0
  10. package/bundled-plugins/alignment/dist/index.js.map +1 -1
  11. package/bundled-plugins/alignment/dist/page.d.ts.map +1 -1
  12. package/bundled-plugins/alignment/dist/page.js +12 -0
  13. package/bundled-plugins/alignment/dist/page.js.map +1 -1
  14. package/bundled-plugins/alignment/dist/types.d.ts +1 -1
  15. package/bundled-plugins/alignment/dist/types.d.ts.map +1 -1
  16. package/bundled-plugins/alignment/dist/types.js.map +1 -1
  17. package/bundled-plugins/alignment/plugin.json +1 -1
  18. package/bundled-plugins/browser-playwright/agent-forge.json +1 -1
  19. package/bundled-plugins/browser-playwright/plugin.json +1 -1
  20. package/bundled-plugins/coding-tools/agent-forge.json +1 -1
  21. package/bundled-plugins/coding-tools/plugin.json +1 -1
  22. package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
  23. package/bundled-plugins/compaction-policy/plugin.json +1 -1
  24. package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
  25. package/bundled-plugins/overflow-policy/plugin.json +1 -1
  26. package/bundled-plugins/provider-failover/agent-forge.json +1 -1
  27. package/bundled-plugins/provider-failover/plugin.json +1 -1
  28. package/bundled-plugins/retry-policy/agent-forge.json +1 -1
  29. package/bundled-plugins/retry-policy/plugin.json +1 -1
  30. package/dist/bundle/.build-manifest.json +1 -1
  31. package/examples/extensions/with-deps/package-lock.json +2 -2
  32. package/examples/extensions/with-deps/package.json +1 -1
  33. package/examples/extensions/with-deps/plugin.json +1 -1
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.88.8] - 2026-10-08
6
+
7
+ ### Fixed
8
+
9
+ - 对齐确认页空环节等待态与防重复确认(2026-10-08 走查):确认推进后 AI 未写入新环节块集时,页面显示"AI 正在准备"等待动画、主按钮禁用(空白页像坏了一样的来源);引擎对空环节的确认结构化拒绝(`empty-step`);prompt guidelines 补步骤推进纪律——用户确认后绝不再要求对已锁定环节重复确认。测试 `plugins/alignment/test/engine.test.ts`,设计 docs/design/需求对齐插件设计.md §5 恢复段。
10
+
5
11
  ## [0.88.7] - 2026-10-08
6
12
 
7
13
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.agent-loop-policy",
4
- "pluginVersion": "0.88.7",
4
+ "pluginVersion": "0.88.8",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.88.0",
7
7
  "platform": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.agent-loop-policy",
3
- "version": "0.88.7",
3
+ "version": "0.88.8",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.88.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.alignment",
4
- "pluginVersion": "0.88.7",
4
+ "pluginVersion": "0.88.8",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.84.4",
7
7
  "platform": {
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure alignment state machine (设计 §5) — no host API access, no I/O.\n *\n * Every function takes JSON-safe state and returns the next state plus the\n * audit transitions to persist; index.ts owns persistence (entries + events)\n * and http-server.ts owns transport. Deterministic by construction: same\n * inputs, same outputs, no clocks, no randomness.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tALIGNMENT_LIMITS,\n\ttype AlignmentBlock,\n\ttype AlignmentBlockKind,\n\ttype AlignmentComment,\n\tAlignmentError,\n\ttype AlignmentRecord,\n\ttype AlignmentRoundPost,\n\ttype AlignmentRoundResult,\n\ttype AlignmentState,\n\ttype AlignmentStepId,\n\ttype AlignmentStepState,\n\ttype AlignmentTransitionEntry,\n\ttype AlignmentVersionSnapshot,\n\ttype GateSnapshot,\n\ttype PendingItem,\n\tSTEP_ORDER,\n} from \"./types.ts\";\n\nexport interface EngineRun {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n/** Model-facing block input (alignment_set); comment ownership is checked against it. */\nexport interface BlockInput {\n\tid: string;\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\t/** New assistant comments for this block (author is forced to assistant). */\n\tcomments?: { id: string; note: string; quote?: string }[];\n\t/** Resolutions the model attaches to existing comments. */\n\tcommentResolutions?: {\n\t\tcommentId: string;\n\t\tkind: \"answered\" | \"dismissed\" | \"incorporated\";\n\t\ttext?: string;\n\t}[];\n}\n\nexport interface AlignmentSetInput {\n\tid: string;\n\ttitle?: string;\n\tgoalId?: string;\n\trevision?: number;\n\t/**\n\t * Explicit confirmation that this write may rewrite blocks the user already\n\t * approved (总则 3): without it, a fingerprint change on an approved block\n\t * is rejected with `approved-blocks-conflict` instead of silently resetting\n\t * it to pending.\n\t */\n\treviseApproved?: boolean;\n\tblocks: BlockInput[];\n}\n\nexport interface AdvanceInput {\n\talignmentId: string;\n\taction: \"inspect\" | \"retreat\" | \"cancel\" | \"rollback\";\n\tstep?: AlignmentStepId;\n\tnote?: string;\n\t/** Rollback target: the active step's snapshot revision anchor; latest snapshot when omitted. */\n\tversionId?: number;\n}\n\nexport interface RoundPostOutcome {\n\trecord: AlignmentRecord;\n\ttransitions: AlignmentTransitionEntry[];\n\tresult: AlignmentRoundResult;\n}\n\nexport interface AdvanceOutcome {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Hashing & validation\n// ---------------------------------------------------------------------------\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport function validAlignmentId(id: string): boolean {\n\t// 入参可能来自未校验的工具输入(空对象 → undefined):test() 会把实参\n\t// 字符串化为 \"undefined\" 而\"通过\"模式匹配,必须先做类型守卫。\n\treturn typeof id === \"string\" && ID_PATTERN.test(id) && id.length <= 128;\n}\n\n/** Fingerprint of the fields whose change must re-ask for approval (总则 3). */\nexport function blockFingerprint(\n\tkind: AlignmentBlockKind,\n\tcontent: string,\n\trationale: string | undefined,\n\timpact: string[] | undefined,\n): string {\n\tconst canonical = JSON.stringify([kind, content, rationale ?? null, impact ?? null]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\nfunction nonEmptyString(value: unknown, name: string, max: number): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} must be a non-empty string`);\n\t}\n\tif (value.length > max) {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} exceeds ${max} characters`);\n\t}\n\treturn value;\n}\n\nfunction validateBlockInput(input: BlockInput): void {\n\tif (typeof input.id !== \"string\" || input.id.trim() === \"\" || input.id.length > 128) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block id must be a non-empty string (≤128 chars): ${String(input.id)}`,\n\t\t);\n\t}\n\tif (![\"fact\", \"assumption\", \"decision\", \"missing\", \"visual\"].includes(input.kind)) {\n\t\tthrow new AlignmentError(\"invalid-block\", `invalid block kind: ${String(input.kind)}`);\n\t}\n\tnonEmptyString(input.content, `block ${input.id} content`, ALIGNMENT_LIMITS.contentMaxLength);\n\tif (input.rationale !== undefined && input.rationale.length > ALIGNMENT_LIMITS.rationaleMaxLength) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} rationale exceeds ${ALIGNMENT_LIMITS.rationaleMaxLength} characters`,\n\t\t);\n\t}\n\tif (input.impact?.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new AlignmentError(\"invalid-block\", `block ${input.id} impact must be a non-empty string array`);\n\t}\n\tif ((input.comments?.length ?? 0) > ALIGNMENT_LIMITS.notesPerBlockMax) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} carries more than ${ALIGNMENT_LIMITS.notesPerBlockMax} comments`,\n\t\t);\n\t}\n\tfor (const comment of input.comments ?? []) {\n\t\tif (typeof comment.id !== \"string\" || comment.id.trim() === \"\") {\n\t\t\tthrow new AlignmentError(\"invalid-comment\", `comment id on block ${input.id} must be a non-empty string`);\n\t\t}\n\t\tnonEmptyString(comment.note, `comment ${comment.id} note`, ALIGNMENT_LIMITS.noteMaxLength);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// State helpers\n// ---------------------------------------------------------------------------\n\nexport function findRecord(state: AlignmentState, id: string): AlignmentRecord | undefined {\n\treturn state.alignments.find((record) => record.id === id);\n}\n\nexport function activeStep(record: AlignmentRecord): AlignmentStepState {\n\tconst found = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!found) throw new AlignmentError(\"invalid-input\", `active step missing: ${record.activeStep}`);\n\treturn found;\n}\n\n/**\n * Gate facts (总则 2): the three server-checkable conditions. A missing block\n * without a stored user answer counts as `missing-empty`; once answered it is\n * just an unapproved block waiting for the model to restructure it.\n */\nexport function gate(record: AlignmentRecord): GateSnapshot {\n\tconst step = activeStep(record);\n\tconst pending: PendingItem[] = [];\n\tlet unapproved = 0;\n\tfor (const block of step.blocks) {\n\t\tif (block.kind === \"missing\" && (block.draftAnswer ?? \"\").trim() === \"\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"missing-empty\" });\n\t\t\tunapproved += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.status !== \"approved\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"unapproved\" });\n\t\t\tunapproved += 1;\n\t\t}\n\t}\n\tlet openComments = 0;\n\tfor (const block of step.blocks) {\n\t\tfor (const comment of block.comments) {\n\t\t\tif (comment.status !== \"open\") continue;\n\t\t\topenComments += 1;\n\t\t\tif (comment.author === \"assistant\") {\n\t\t\t\tpending.push({ blockId: block.id, reason: \"assistant-question-open\" });\n\t\t\t}\n\t\t}\n\t}\n\tconst pendingAdditions = record.pendingFeedback?.additions.length ?? 0;\n\treturn {\n\t\tunapproved,\n\t\topenComments,\n\t\tpendingAdditions,\n\t\tallGreen: unapproved === 0 && openComments === 0 && pendingAdditions === 0,\n\t\tpending,\n\t};\n}\n\nfunction nextStep(step: AlignmentStepId): AlignmentStepId | undefined {\n\tconst index = STEP_ORDER.indexOf(step);\n\treturn index >= 0 && index < STEP_ORDER.length - 1 ? STEP_ORDER[index + 1] : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// alignment_set: create / update (总则 3 hash inheritance, 总则 5 comment merge)\n// ---------------------------------------------------------------------------\n\nfunction materializeBlock(\n\tinput: BlockInput,\n\tprevious: AlignmentBlock | undefined,\n\tcurrentRound: number,\n): AlignmentBlock {\n\tconst fingerprint = blockFingerprint(input.kind, input.content, input.rationale, input.impact);\n\tconst unchanged = previous !== undefined && previous.contentHash === fingerprint;\n\tconst block: AlignmentBlock = {\n\t\tid: input.id,\n\t\tkind: input.kind,\n\t\tcontent: input.content,\n\t\trationale: input.rationale,\n\t\timpact: input.impact,\n\t\t// Hash-identical resent blocks keep their approval (防确认疲劳); changed\n\t\t// content resets to pending and flags the round it changed in.\n\t\tstatus: unchanged ? previous.status : \"pending\",\n\t\tcontentHash: fingerprint,\n\t\t...(unchanged ? {} : { modifiedInRound: currentRound + 1 }),\n\t\t...(previous?.draftAnswer !== undefined ? { draftAnswer: previous.draftAnswer } : {}),\n\t\tcomments: [],\n\t};\n\tblock.comments = mergeComments(previous?.comments ?? [], input, currentRound);\n\treturn block;\n}\n\nfunction mergeComments(existing: AlignmentComment[], input: BlockInput, round: number): AlignmentComment[] {\n\tconst merged: AlignmentComment[] = existing.map((comment) => ({ ...comment }));\n\tconst resolutions = new Map((input.commentResolutions ?? []).map((item) => [item.commentId, item]));\n\t// Existing comments are append-only (总则 5): user text survives resends\n\t// verbatim, open assistant questions cannot silently disappear; only an\n\t// explicit resolution closes them.\n\tfor (const comment of merged) {\n\t\tconst resolution = resolutions.get(comment.id);\n\t\tif (!resolution) continue;\n\t\tresolutions.delete(comment.id);\n\t\tif (comment.status === \"resolved\") {\n\t\t\tthrow new AlignmentError(\"comment-ownership\", `comment ${comment.id} is already resolved`);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = { kind: resolution.kind, text: resolution.text, round };\n\t}\n\tif (resolutions.size > 0) {\n\t\tconst ids = [...resolutions.keys()].join(\", \");\n\t\tthrow new AlignmentError(\"comment-ownership\", `resolutions reference unknown comments: ${ids}`);\n\t}\n\t// New comments are assistant-only and deduped by note so a resent open\n\t// question never duplicates itself.\n\tfor (const fresh of input.comments ?? []) {\n\t\tif (merged.some((comment) => comment.author === \"assistant\" && comment.note === fresh.note)) continue;\n\t\tmerged.push({\n\t\t\tid: fresh.id,\n\t\t\tauthor: \"assistant\",\n\t\t\t...(fresh.quote !== undefined ? { quote: fresh.quote } : {}),\n\t\t\tnote: fresh.note,\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\treturn merged;\n}\n\nexport function applyAlignmentSet(state: AlignmentState, input: AlignmentSetInput): EngineRun {\n\tif (!validAlignmentId(input.id)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-id\",\n\t\t\t`alignment id must match [A-Za-z0-9][A-Za-z0-9._-]* (no leading dot): ${input.id}`,\n\t\t);\n\t}\n\tif (!Array.isArray(input.blocks) || input.blocks.length === 0) {\n\t\tthrow new AlignmentError(\"invalid-input\", \"blocks must be a non-empty array\");\n\t}\n\tif (input.blocks.length > ALIGNMENT_LIMITS.blocksPerStepMax) {\n\t\tthrow new AlignmentError(\"invalid-input\", `a step may carry at most ${ALIGNMENT_LIMITS.blocksPerStepMax} blocks`);\n\t}\n\tconst seen = new Set<string>();\n\tfor (const block of input.blocks) {\n\t\tvalidateBlockInput(block);\n\t\tif (seen.has(block.id)) {\n\t\t\tthrow new AlignmentError(\"invalid-block\", `duplicate block id within the step: ${block.id}`);\n\t\t}\n\t\tseen.add(block.id);\n\t}\n\n\tconst existing = findRecord(state, input.id);\n\tif (!existing) {\n\t\t// Create: created-as-aligning (no draft state) and single-active (总则 9).\n\t\tconst active = state.alignments.find((record) => record.status === \"aligning\" || record.status === \"finalizing\");\n\t\tif (active) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"active-alignment-exists\",\n\t\t\t\t`an alignment is already in progress: ${active.id}; complete or cancel it first`,\n\t\t\t);\n\t\t}\n\t\tconst record: AlignmentRecord = {\n\t\t\tid: input.id,\n\t\t\ttitle: input.title ?? input.id,\n\t\t\t...(input.goalId !== undefined ? { goalId: input.goalId } : {}),\n\t\t\tstatus: \"aligning\",\n\t\t\trevision: 0,\n\t\t\tactiveStep: \"objective\",\n\t\t\tsteps: [\n\t\t\t\t{\n\t\t\t\t\tstep: \"objective\",\n\t\t\t\t\tstatus: \"active\",\n\t\t\t\t\tblocks: input.blocks.map((block) => materializeBlock(block, undefined, 0)),\n\t\t\t\t},\n\t\t\t\t{ step: \"detail\", status: \"pending\", blocks: [] },\n\t\t\t\t{ step: \"design\", status: \"pending\", blocks: [] },\n\t\t\t],\n\t\t\troundCount: 0,\n\t\t};\n\t\treturn { state: { alignments: [...state.alignments, record] }, transitions: [] };\n\t}\n\n\tif (existing.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal and cannot be updated: ${input.id}`);\n\t}\n\tconst revision = input.revision ?? 0;\n\tif (revision <= existing.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-revision\",\n\t\t\t`stale alignment revision: ${input.id} expects > ${existing.revision}, got ${revision}`,\n\t\t);\n\t}\n\tconst step = activeStep(existing);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${existing.activeStep} is ${step.status}; only the active step accepts blocks`,\n\t\t);\n\t}\n\t// Deleting a block that still carries open comments is rejected (总则 3):\n\t// deletion must not become a way to dodge unresolved objections.\n\tfor (const previous of step.blocks) {\n\t\tif (input.blocks.some((block) => block.id === previous.id)) continue;\n\t\tif (previous.comments.some((comment) => comment.status === \"open\")) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"block-has-open-comments\",\n\t\t\t\t`block ${previous.id} carries open comments; resolve them before deleting it (edit the content instead)`,\n\t\t\t);\n\t\t}\n\t}\n\t// 已批准块写入防护(v3.7,2026-10-08 dogfood 根因):全量重写会按内容指纹\n\t// 把已确认块静默重置 pending——未显式确认时结构化拒绝,列出将被重置的块。\n\tif (!input.reviseApproved) {\n\t\tconst conflicts = input.blocks\n\t\t\t.filter((blockInput) => {\n\t\t\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\t\t\treturn (\n\t\t\t\t\tprevious?.status === \"approved\" &&\n\t\t\t\t\tprevious.contentHash !==\n\t\t\t\t\t\tblockFingerprint(blockInput.kind, blockInput.content, blockInput.rationale, blockInput.impact)\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map((blockInput) => blockInput.id);\n\t\tif (conflicts.length > 0) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"approved-blocks-conflict\",\n\t\t\t\t`this write would reset approved blocks to pending: ${conflicts.join(\", \")}; resubmit with reviseApproved: true, or keep the approved block content unchanged`,\n\t\t\t);\n\t\t}\n\t}\n\tconst blocks = input.blocks.map((blockInput) => {\n\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\treturn materializeBlock(blockInput, previous, existing.roundCount);\n\t});\n\t// Version snapshot (走查 2026-10-03): archive the outgoing step block set\n\t// before it is replaced. Append-only, bounded rolling; the page views\n\t// historical versions read-only — only the latest accepts interaction.\n\t// An empty outgoing set has nothing to roll back to — skip it, otherwise\n\t// the version bar fills with chips that render a blank view.\n\tconst archive = JSON.stringify(step.blocks);\n\tconst snapshot: AlignmentVersionSnapshot = {\n\t\trevision: existing.revision,\n\t\tround: existing.roundCount,\n\t\tstep: existing.activeStep,\n\t\tblocks: archive.length <= ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? structuredClone(step.blocks) : [],\n\t\t...(archive.length > ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? { truncated: true as const } : {}),\n\t};\n\tconst priorVersions = (\n\t\tstep.blocks.length === 0 ? (existing.versions ?? []) : [...(existing.versions ?? []), snapshot]\n\t).slice(-ALIGNMENT_LIMITS.versionsMax);\n\tconst updated: AlignmentRecord = {\n\t\t...existing,\n\t\ttitle: input.title ?? existing.title,\n\t\tgoalId: input.goalId ?? existing.goalId,\n\t\trevision,\n\t\tsteps: existing.steps.map((candidate) =>\n\t\t\tcandidate.step === existing.activeStep ? { ...candidate, blocks } : candidate,\n\t\t),\n\t\tpendingFeedback: undefined,\n\t\tversions: priorVersions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((record) => (record.id === updated.id ? updated : record)) },\n\t\ttransitions: [],\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// POST /round: the user feedback round (§5 round flow, v3.2 audits)\n// ---------------------------------------------------------------------------\n\nexport function applyRoundPost(record0: AlignmentRecord, post: AlignmentRoundPost): RoundPostOutcome {\n\tif (record0.status !== \"aligning\" && record0.status !== \"finalizing\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record0.id}`);\n\t}\n\t// Anti-staleness gate (terminal audit D6): the page must confirm against\n\t// the exact state it rendered.\n\tif (post.baseRevision !== record0.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-state\",\n\t\t\t`page snapshot is stale (base ${post.baseRevision}, current ${record0.revision}); reload and resubmit`,\n\t\t);\n\t}\n\tconst hasNewFeedback = post.comments.length > 0 || post.additions.length > 0 || post.answers.length > 0;\n\tif (post.stepConfirmed && hasNewFeedback) {\n\t\tthrow new AlignmentError(\n\t\t\t\"confirmed-with-feedback\",\n\t\t\t\"step confirmation cannot carry new comments/additions/answers; submit the feedback first\",\n\t\t);\n\t}\n\t// 终审阶段唯一合法提交是终审确认(走查 2026-10-03)。此时所有步已锁定,\n\t// 反馈字段没有落点——尤其 approvals 镜像会把已锁定的 design 块整体重置为\n\t// pending,再被无门禁的终审分支带进 completed。显式拒绝,不做静默重置。\n\tif (\n\t\trecord0.status === \"finalizing\" &&\n\t\t(hasNewFeedback || post.commentReplies.length > 0 || post.approvals.length > 0 || !post.stepConfirmed)\n\t) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t\"finalizing accepts only the final confirm (stepConfirmed without feedback fields)\",\n\t\t);\n\t}\n\n\tconst record: AlignmentRecord = structuredClone(record0);\n\tconst step = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!step) throw new AlignmentError(\"invalid-input\", \"active step missing\");\n\tconst blockById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst round = record.roundCount + 1;\n\n\t// New user comments: the quote must match the block's current content.\n\tfor (const comment of post.comments) {\n\t\tconst block = blockById.get(comment.blockId);\n\t\tif (!block) {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `comment targets unknown block: ${comment.blockId}`);\n\t\t}\n\t\tconst note = nonEmptyString(comment.note, \"comment note\", ALIGNMENT_LIMITS.noteMaxLength);\n\t\tif (typeof comment.quote !== \"string\" || !block.content.includes(comment.quote)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"comment-quote-mismatch\",\n\t\t\t\t`comment quote does not match block ${comment.blockId} content`,\n\t\t\t);\n\t\t}\n\t\tblock.comments.push({\n\t\t\tid: `c${round}-${block.comments.length + 1}`,\n\t\t\tauthor: \"user\",\n\t\t\tquote: comment.quote,\n\t\t\tnote,\n\t\t\t...(comment.escalate === true ? { escalate: true } : {}),\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\n\t// Replies to assistant comments (answer/dismiss are mutually exclusive).\n\tfor (const reply of post.commentReplies) {\n\t\tconst block = blockById.get(reply.blockId);\n\t\tconst comment = block?.comments.find((candidate) => candidate.id === reply.commentId);\n\t\tif (!comment || comment.author !== \"assistant\") {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply targets unknown or non-assistant comment: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tif (comment.status !== \"open\") continue; // idempotent resubmission\n\t\tif ((reply.answer !== undefined) === (reply.dismiss === true)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply must carry exactly one of answer or dismiss: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = reply.dismiss\n\t\t\t? { kind: \"dismissed\", text: \"user marked unimportant\", round }\n\t\t\t: {\n\t\t\t\t\tkind: \"answered\",\n\t\t\t\t\ttext: nonEmptyString(reply.answer ?? \"\", \"reply answer\", ALIGNMENT_LIMITS.answerMaxLength),\n\t\t\t\t\tround,\n\t\t\t\t};\n\t}\n\n\t// Fill-in answers for missing blocks: stored verbatim as the user's words;\n\t// the model restructures them into the block on its next alignment_set.\n\tfor (const answer of post.answers) {\n\t\tconst block = blockById.get(answer.blockId);\n\t\tif (!block || block.kind !== \"missing\") {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `answer targets unknown or non-missing block: ${answer.blockId}`);\n\t\t}\n\t\tif ((block.draftAnswer ?? \"\").trim() !== \"\") continue; // idempotent resubmission\n\t\tblock.draftAnswer = nonEmptyString(answer.value, \"answer\", ALIGNMENT_LIMITS.answerMaxLength);\n\t}\n\n\t// Approvals mirror the page's full approved set for the active step.\n\t// Skipped in finalizing (走查 2026-10-03): the step is locked and the\n\t// final confirm carries approvals=[] — mirroring would reopen its blocks.\n\tif (record0.status !== \"finalizing\") {\n\t\tconst approvalSet = new Set(post.approvals);\n\t\tfor (const id of approvalSet) {\n\t\t\tif (!blockById.has(id)) {\n\t\t\t\tthrow new AlignmentError(\"invalid-input\", `approval references unknown block: ${id}`);\n\t\t\t}\n\t\t}\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = approvalSet.has(block.id) ? \"approved\" : \"pending\";\n\t\t}\n\t}\n\n\t// Additions queue as pendingFeedback — the server-side truth of gate\n\t// condition three (terminal audit D7); cleared by the next alignment_set.\n\tconst additions = [...(record.pendingFeedback?.additions ?? [])];\n\tfor (const addition of post.additions) {\n\t\tadditions.push(nonEmptyString(addition, \"addition\", ALIGNMENT_LIMITS.additionMaxLength));\n\t}\n\trecord.pendingFeedback = additions.length > 0 ? { round, additions } : undefined;\n\n\tconst transitions: AlignmentTransitionEntry[] = [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"round.submitted\",\n\t\t\tround,\n\t\t\treason: {\n\t\t\t\tcode: \"round-submitted\",\n\t\t\t\tdetail: `approvals=${post.approvals.length} comments=${post.comments.length} replies=${post.commentReplies.length} answers=${post.answers.length} additions=${post.additions.length}`,\n\t\t\t},\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n\trecord.roundCount = round;\n\n\tlet stepConfirmedNow = false;\n\tif (post.stepConfirmed && record0.status === \"finalizing\") {\n\t\t// 终审总览确认(走查 2026-10-03): the user reviewed the full plan and\n\t\t// confirmed it — the only path to `completed` + baseline write.\n\t\trecord.status = \"completed\";\n\t\trecord.revision += 1;\n\t\tfor (const locked of record.steps) locked.baselineConfirmedAtRound ??= record.roundCount;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"alignment.completed\",\n\t\t\treason: { code: \"final-review-approved\", detail: \"final review confirmed the complete plan\" },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst result: AlignmentRoundResult = {\n\t\t\talignmentId: record.id,\n\t\t\tround: record.roundCount,\n\t\t\tstep: record.activeStep,\n\t\t\trevision: record.revision,\n\t\t\tapprovals: post.approvals,\n\t\t\tcomments: post.comments,\n\t\t\tcommentReplies: post.commentReplies,\n\t\t\tanswers: post.answers,\n\t\t\tadditions: post.additions,\n\t\t\tstepConfirmed: true,\n\t\t};\n\t\treturn { record, transitions, result };\n\t}\n\tif (post.stepConfirmed) {\n\t\tconst snapshot = gate(record);\n\t\tif (!snapshot.allGreen) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"gate-not-satisfied\",\n\t\t\t\t`gate not satisfied: ${snapshot.unapproved} unapproved blocks, ${snapshot.openComments} open comments, ${snapshot.pendingAdditions} pending additions`,\n\t\t\t);\n\t\t}\n\t\tstep.status = \"locked\";\n\t\tstep.baselineConfirmedAtRound = round;\n\t\trecord.revision += 1;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"step.confirmed\",\n\t\t\tround,\n\t\t\treason: { code: \"step-baseline-locked\", detail: `${step.blocks.length} blocks frozen` },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst upcoming = nextStep(record.activeStep);\n\t\tif (upcoming) {\n\t\t\trecord.activeStep = upcoming;\n\t\t\tconst upcomingStep = record.steps.find((candidate) => candidate.step === upcoming);\n\t\t\tif (upcomingStep) upcomingStep.status = \"active\";\n\t\t\trecord.revision += 1;\n\t\t} else {\n\t\t\t// Design locked (方案确认): enter the final-review overview instead\n\t\t\t// of completing (走查 2026-10-03) — the baseline is written only\n\t\t\t// after the user confirms the full plan on the 终审 tab.\n\t\t\trecord.status = \"finalizing\";\n\t\t\trecord.revision += 1;\n\t\t\ttransitions.push({\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"design.locked\",\n\t\t\t\tround,\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"scheme-locked-awaiting-final-review\",\n\t\t\t\t\tdetail: \"all three baselines locked; awaiting final review\",\n\t\t\t\t},\n\t\t\t\trevision: record.revision,\n\t\t\t});\n\t\t}\n\t\tstepConfirmedNow = true;\n\t}\n\n\tconst result: AlignmentRoundResult = {\n\t\talignmentId: record.id,\n\t\tround,\n\t\tstep: record0.activeStep,\n\t\trevision: record.revision,\n\t\tapprovals: post.approvals,\n\t\tcomments: post.comments,\n\t\tcommentReplies: post.commentReplies,\n\t\tanswers: post.answers,\n\t\tadditions: post.additions,\n\t\tstepConfirmed: stepConfirmedNow,\n\t};\n\treturn { record, transitions, result };\n}\n\n/** Injection succeeded — the delivered transition closes the round (terminal audit D1). */\nexport function markDelivered(record: AlignmentRecord, round: number): AlignmentTransitionEntry[] {\n\treturn [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"round.delivered\",\n\t\t\tround,\n\t\t\treason: { code: \"round-delivered\", detail: \"feedback injected into the session\" },\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n}\n\n// ---------------------------------------------------------------------------\n// alignment_advance: inspect / retreat / cancel (总则 6)\n// ---------------------------------------------------------------------------\n\nexport function applyAlignmentAdvance(state: AlignmentState, input: AdvanceInput): AdvanceOutcome {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (input.action === \"inspect\") return { state, transitions: [] };\n\tif (input.action === \"rollback\") {\n\t\t// Rollback is routed through applyVersionRollback (设计 §6); refusing\n\t\t// here keeps the advance branch from ever treating it as a retreat.\n\t\tthrow new AlignmentError(\"invalid-input\", \"rollback routes through applyVersionRollback, not advance\");\n\t}\n\tif (record.status === \"completed\" || record.status === \"cancelled\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record.id}`);\n\t}\n\n\tif (input.action === \"cancel\") {\n\t\tconst cancelled: AlignmentRecord = { ...record, status: \"cancelled\" };\n\t\treturn {\n\t\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? cancelled : item)) },\n\t\t\ttransitions: [\n\t\t\t\t{\n\t\t\t\t\talignmentId: record.id,\n\t\t\t\t\tevent: \"alignment.cancelled\",\n\t\t\t\t\treason: { code: \"alignment-cancelled\" },\n\t\t\t\t\trevision: record.revision,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\t// retreat: comments survive the reset append-only (terminal audit D5); the\n\t// escalate comments that triggered it are resolved here with the note.\n\tconst note = nonEmptyString(input.note ?? \"\", \"retreat note\", ALIGNMENT_LIMITS.noteMaxLength);\n\tconst target = input.step;\n\tif (!target || STEP_ORDER.indexOf(target) >= STEP_ORDER.indexOf(record.activeStep)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"retreat-target-invalid\",\n\t\t\t`retreat target must be an earlier step than ${record.activeStep}`,\n\t\t);\n\t}\n\tconst affected: string[] = [];\n\tconst retreated: AlignmentRecord = { ...record, steps: record.steps.map((step) => ({ ...step })) };\n\tfor (const step of retreated.steps) {\n\t\tif (STEP_ORDER.indexOf(step.step) < STEP_ORDER.indexOf(target)) continue;\n\t\taffected.push(step.step);\n\t\tstep.status = \"reset\";\n\t\tstep.baselineConfirmedAtRound = undefined;\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = \"pending\";\n\t\t\tfor (const comment of block.comments) {\n\t\t\t\tif (comment.status === \"open\" && comment.author === \"user\" && comment.escalate === true) {\n\t\t\t\t\tcomment.status = \"resolved\";\n\t\t\t\t\tcomment.resolution = { kind: \"incorporated\", text: note, round: record.roundCount };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tretreated.activeStep = target;\n\t// Finalizing → retreated: the final-review state is gone with the\n\t// downstream resets; the task is writable at the target step again.\n\tif (retreated.status === \"finalizing\") retreated.status = \"aligning\";\n\tretreated.revision += 1;\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? retreated : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"step.retreated\",\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"step-retreated\",\n\t\t\t\t\tdetail: `target=${target}; reset=${affected.join(\",\")}; note=${note}`,\n\t\t\t\t},\n\t\t\t\trevision: retreated.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\nexport interface VersionRollbackInput {\n\talignmentId: string;\n\t/** Target snapshot revision from `record.versions` (must be the active step's). */\n\ttargetRevision: number;\n}\n\n/**\n * Step-scoped version rollback (走查 2026-10-03): drop the current unconfirmed\n * version (not archived), promote the target snapshot to live, engine-bump the\n * revision (D4). Comments survive append-only — restored blocks carry the union\n * of live and archived comments by comment id, live status winning (a\n * resolution is irreversible). Only the active (unlocked) step accepts it;\n * locked steps go through retreat. Other steps are never touched.\n */\nexport function applyVersionRollback(state: AlignmentState, input: VersionRollbackInput): EngineRun {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (record.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `version rollback needs an aligning task: ${record.id}`);\n\t}\n\tconst step = activeStep(record);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${record.activeStep} is ${step.status}; locked steps go through retreat`,\n\t\t);\n\t}\n\tconst versions = [...(record.versions ?? [])];\n\tconst index = versions.findIndex(\n\t\t(snapshot) => snapshot.revision === input.targetRevision && snapshot.step === record.activeStep,\n\t);\n\tif (index < 0) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`no snapshot of step ${record.activeStep} at revision ${input.targetRevision}`,\n\t\t);\n\t}\n\tconst snapshot = versions[index];\n\tif (snapshot.truncated) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`snapshot r${input.targetRevision} exceeded the size budget and has no blocks`,\n\t\t);\n\t}\n\tconst liveById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst restored = snapshot.blocks.map((archived) => {\n\t\tconst live = liveById.get(archived.id);\n\t\tif (!live) return structuredClone(archived);\n\t\tconst merged = new Map(archived.comments.map((comment) => [comment.id, comment]));\n\t\tfor (const comment of live.comments) merged.set(comment.id, comment); // live status wins\n\t\treturn { ...structuredClone(archived), comments: [...merged.values()] };\n\t});\n\t// The current version is dropped, the target is promoted: remove it from\n\t// the archive rather than re-archiving the live set.\n\tversions.splice(index, 1);\n\tconst rolled: AlignmentRecord = {\n\t\t...record,\n\t\trevision: record.revision + 1,\n\t\tsteps: record.steps.map((candidate) =>\n\t\t\tcandidate.step === record.activeStep\n\t\t\t\t? { ...candidate, blocks: restored, status: candidate.status === \"locked\" ? \"reset\" : candidate.status }\n\t\t\t\t: candidate,\n\t\t),\n\t\tversions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === rolled.id ? rolled : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: rolled.id,\n\t\t\t\tstep: rolled.activeStep,\n\t\t\t\tevent: \"version.rolled-back\",\n\t\t\t\treason: { code: \"version-rolled-back\", detail: `target=r${input.targetRevision}` },\n\t\t\t\trevision: rolled.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\n/** Compact final baseline listing (§5 round flow 4): one line per block, answers first. */\nexport function finalBaselineLines(\n\trecord: AlignmentRecord,\n): { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] | undefined {\n\tif (record.status !== \"completed\") return undefined;\n\tconst lines: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] = [];\n\tfor (const step of record.steps) {\n\t\tfor (const block of step.blocks) {\n\t\t\tconst confirmed = block.content.split(\"\\n\").find((line) => line.includes(\"→ 已确认\"));\n\t\t\tconst text = (confirmed ? confirmed.replace(/^\\s*→\\s*/, \"\") : block.content.split(\"\\n\")[0]).slice(0, 120);\n\t\t\tlines.push({ step: step.step, kind: block.kind, id: block.id, line: text });\n\t\t}\n\t}\n\treturn lines;\n}\n"]}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure alignment state machine (设计 §5) — no host API access, no I/O.\n *\n * Every function takes JSON-safe state and returns the next state plus the\n * audit transitions to persist; index.ts owns persistence (entries + events)\n * and http-server.ts owns transport. Deterministic by construction: same\n * inputs, same outputs, no clocks, no randomness.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tALIGNMENT_LIMITS,\n\ttype AlignmentBlock,\n\ttype AlignmentBlockKind,\n\ttype AlignmentComment,\n\tAlignmentError,\n\ttype AlignmentRecord,\n\ttype AlignmentRoundPost,\n\ttype AlignmentRoundResult,\n\ttype AlignmentState,\n\ttype AlignmentStepId,\n\ttype AlignmentStepState,\n\ttype AlignmentTransitionEntry,\n\ttype AlignmentVersionSnapshot,\n\ttype GateSnapshot,\n\ttype PendingItem,\n\tSTEP_ORDER,\n} from \"./types.ts\";\n\nexport interface EngineRun {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n/** Model-facing block input (alignment_set); comment ownership is checked against it. */\nexport interface BlockInput {\n\tid: string;\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\t/** New assistant comments for this block (author is forced to assistant). */\n\tcomments?: { id: string; note: string; quote?: string }[];\n\t/** Resolutions the model attaches to existing comments. */\n\tcommentResolutions?: {\n\t\tcommentId: string;\n\t\tkind: \"answered\" | \"dismissed\" | \"incorporated\";\n\t\ttext?: string;\n\t}[];\n}\n\nexport interface AlignmentSetInput {\n\tid: string;\n\ttitle?: string;\n\tgoalId?: string;\n\trevision?: number;\n\t/**\n\t * Explicit confirmation that this write may rewrite blocks the user already\n\t * approved (总则 3): without it, a fingerprint change on an approved block\n\t * is rejected with `approved-blocks-conflict` instead of silently resetting\n\t * it to pending.\n\t */\n\treviseApproved?: boolean;\n\tblocks: BlockInput[];\n}\n\nexport interface AdvanceInput {\n\talignmentId: string;\n\taction: \"inspect\" | \"retreat\" | \"cancel\" | \"rollback\";\n\tstep?: AlignmentStepId;\n\tnote?: string;\n\t/** Rollback target: the active step's snapshot revision anchor; latest snapshot when omitted. */\n\tversionId?: number;\n}\n\nexport interface RoundPostOutcome {\n\trecord: AlignmentRecord;\n\ttransitions: AlignmentTransitionEntry[];\n\tresult: AlignmentRoundResult;\n}\n\nexport interface AdvanceOutcome {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Hashing & validation\n// ---------------------------------------------------------------------------\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport function validAlignmentId(id: string): boolean {\n\t// 入参可能来自未校验的工具输入(空对象 → undefined):test() 会把实参\n\t// 字符串化为 \"undefined\" 而\"通过\"模式匹配,必须先做类型守卫。\n\treturn typeof id === \"string\" && ID_PATTERN.test(id) && id.length <= 128;\n}\n\n/** Fingerprint of the fields whose change must re-ask for approval (总则 3). */\nexport function blockFingerprint(\n\tkind: AlignmentBlockKind,\n\tcontent: string,\n\trationale: string | undefined,\n\timpact: string[] | undefined,\n): string {\n\tconst canonical = JSON.stringify([kind, content, rationale ?? null, impact ?? null]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\nfunction nonEmptyString(value: unknown, name: string, max: number): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} must be a non-empty string`);\n\t}\n\tif (value.length > max) {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} exceeds ${max} characters`);\n\t}\n\treturn value;\n}\n\nfunction validateBlockInput(input: BlockInput): void {\n\tif (typeof input.id !== \"string\" || input.id.trim() === \"\" || input.id.length > 128) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block id must be a non-empty string (≤128 chars): ${String(input.id)}`,\n\t\t);\n\t}\n\tif (![\"fact\", \"assumption\", \"decision\", \"missing\", \"visual\"].includes(input.kind)) {\n\t\tthrow new AlignmentError(\"invalid-block\", `invalid block kind: ${String(input.kind)}`);\n\t}\n\tnonEmptyString(input.content, `block ${input.id} content`, ALIGNMENT_LIMITS.contentMaxLength);\n\tif (input.rationale !== undefined && input.rationale.length > ALIGNMENT_LIMITS.rationaleMaxLength) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} rationale exceeds ${ALIGNMENT_LIMITS.rationaleMaxLength} characters`,\n\t\t);\n\t}\n\tif (input.impact?.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new AlignmentError(\"invalid-block\", `block ${input.id} impact must be a non-empty string array`);\n\t}\n\tif ((input.comments?.length ?? 0) > ALIGNMENT_LIMITS.notesPerBlockMax) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} carries more than ${ALIGNMENT_LIMITS.notesPerBlockMax} comments`,\n\t\t);\n\t}\n\tfor (const comment of input.comments ?? []) {\n\t\tif (typeof comment.id !== \"string\" || comment.id.trim() === \"\") {\n\t\t\tthrow new AlignmentError(\"invalid-comment\", `comment id on block ${input.id} must be a non-empty string`);\n\t\t}\n\t\tnonEmptyString(comment.note, `comment ${comment.id} note`, ALIGNMENT_LIMITS.noteMaxLength);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// State helpers\n// ---------------------------------------------------------------------------\n\nexport function findRecord(state: AlignmentState, id: string): AlignmentRecord | undefined {\n\treturn state.alignments.find((record) => record.id === id);\n}\n\nexport function activeStep(record: AlignmentRecord): AlignmentStepState {\n\tconst found = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!found) throw new AlignmentError(\"invalid-input\", `active step missing: ${record.activeStep}`);\n\treturn found;\n}\n\n/**\n * Gate facts (总则 2): the three server-checkable conditions. A missing block\n * without a stored user answer counts as `missing-empty`; once answered it is\n * just an unapproved block waiting for the model to restructure it.\n */\nexport function gate(record: AlignmentRecord): GateSnapshot {\n\tconst step = activeStep(record);\n\tconst pending: PendingItem[] = [];\n\tlet unapproved = 0;\n\tfor (const block of step.blocks) {\n\t\tif (block.kind === \"missing\" && (block.draftAnswer ?? \"\").trim() === \"\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"missing-empty\" });\n\t\t\tunapproved += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.status !== \"approved\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"unapproved\" });\n\t\t\tunapproved += 1;\n\t\t}\n\t}\n\tlet openComments = 0;\n\tfor (const block of step.blocks) {\n\t\tfor (const comment of block.comments) {\n\t\t\tif (comment.status !== \"open\") continue;\n\t\t\topenComments += 1;\n\t\t\tif (comment.author === \"assistant\") {\n\t\t\t\tpending.push({ blockId: block.id, reason: \"assistant-question-open\" });\n\t\t\t}\n\t\t}\n\t}\n\tconst pendingAdditions = record.pendingFeedback?.additions.length ?? 0;\n\treturn {\n\t\tunapproved,\n\t\topenComments,\n\t\tpendingAdditions,\n\t\tallGreen: unapproved === 0 && openComments === 0 && pendingAdditions === 0,\n\t\tpending,\n\t};\n}\n\nfunction nextStep(step: AlignmentStepId): AlignmentStepId | undefined {\n\tconst index = STEP_ORDER.indexOf(step);\n\treturn index >= 0 && index < STEP_ORDER.length - 1 ? STEP_ORDER[index + 1] : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// alignment_set: create / update (总则 3 hash inheritance, 总则 5 comment merge)\n// ---------------------------------------------------------------------------\n\nfunction materializeBlock(\n\tinput: BlockInput,\n\tprevious: AlignmentBlock | undefined,\n\tcurrentRound: number,\n): AlignmentBlock {\n\tconst fingerprint = blockFingerprint(input.kind, input.content, input.rationale, input.impact);\n\tconst unchanged = previous !== undefined && previous.contentHash === fingerprint;\n\tconst block: AlignmentBlock = {\n\t\tid: input.id,\n\t\tkind: input.kind,\n\t\tcontent: input.content,\n\t\trationale: input.rationale,\n\t\timpact: input.impact,\n\t\t// Hash-identical resent blocks keep their approval (防确认疲劳); changed\n\t\t// content resets to pending and flags the round it changed in.\n\t\tstatus: unchanged ? previous.status : \"pending\",\n\t\tcontentHash: fingerprint,\n\t\t...(unchanged ? {} : { modifiedInRound: currentRound + 1 }),\n\t\t...(previous?.draftAnswer !== undefined ? { draftAnswer: previous.draftAnswer } : {}),\n\t\tcomments: [],\n\t};\n\tblock.comments = mergeComments(previous?.comments ?? [], input, currentRound);\n\treturn block;\n}\n\nfunction mergeComments(existing: AlignmentComment[], input: BlockInput, round: number): AlignmentComment[] {\n\tconst merged: AlignmentComment[] = existing.map((comment) => ({ ...comment }));\n\tconst resolutions = new Map((input.commentResolutions ?? []).map((item) => [item.commentId, item]));\n\t// Existing comments are append-only (总则 5): user text survives resends\n\t// verbatim, open assistant questions cannot silently disappear; only an\n\t// explicit resolution closes them.\n\tfor (const comment of merged) {\n\t\tconst resolution = resolutions.get(comment.id);\n\t\tif (!resolution) continue;\n\t\tresolutions.delete(comment.id);\n\t\tif (comment.status === \"resolved\") {\n\t\t\tthrow new AlignmentError(\"comment-ownership\", `comment ${comment.id} is already resolved`);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = { kind: resolution.kind, text: resolution.text, round };\n\t}\n\tif (resolutions.size > 0) {\n\t\tconst ids = [...resolutions.keys()].join(\", \");\n\t\tthrow new AlignmentError(\"comment-ownership\", `resolutions reference unknown comments: ${ids}`);\n\t}\n\t// New comments are assistant-only and deduped by note so a resent open\n\t// question never duplicates itself.\n\tfor (const fresh of input.comments ?? []) {\n\t\tif (merged.some((comment) => comment.author === \"assistant\" && comment.note === fresh.note)) continue;\n\t\tmerged.push({\n\t\t\tid: fresh.id,\n\t\t\tauthor: \"assistant\",\n\t\t\t...(fresh.quote !== undefined ? { quote: fresh.quote } : {}),\n\t\t\tnote: fresh.note,\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\treturn merged;\n}\n\nexport function applyAlignmentSet(state: AlignmentState, input: AlignmentSetInput): EngineRun {\n\tif (!validAlignmentId(input.id)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-id\",\n\t\t\t`alignment id must match [A-Za-z0-9][A-Za-z0-9._-]* (no leading dot): ${input.id}`,\n\t\t);\n\t}\n\tif (!Array.isArray(input.blocks) || input.blocks.length === 0) {\n\t\tthrow new AlignmentError(\"invalid-input\", \"blocks must be a non-empty array\");\n\t}\n\tif (input.blocks.length > ALIGNMENT_LIMITS.blocksPerStepMax) {\n\t\tthrow new AlignmentError(\"invalid-input\", `a step may carry at most ${ALIGNMENT_LIMITS.blocksPerStepMax} blocks`);\n\t}\n\tconst seen = new Set<string>();\n\tfor (const block of input.blocks) {\n\t\tvalidateBlockInput(block);\n\t\tif (seen.has(block.id)) {\n\t\t\tthrow new AlignmentError(\"invalid-block\", `duplicate block id within the step: ${block.id}`);\n\t\t}\n\t\tseen.add(block.id);\n\t}\n\n\tconst existing = findRecord(state, input.id);\n\tif (!existing) {\n\t\t// Create: created-as-aligning (no draft state) and single-active (总则 9).\n\t\tconst active = state.alignments.find((record) => record.status === \"aligning\" || record.status === \"finalizing\");\n\t\tif (active) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"active-alignment-exists\",\n\t\t\t\t`an alignment is already in progress: ${active.id}; complete or cancel it first`,\n\t\t\t);\n\t\t}\n\t\tconst record: AlignmentRecord = {\n\t\t\tid: input.id,\n\t\t\ttitle: input.title ?? input.id,\n\t\t\t...(input.goalId !== undefined ? { goalId: input.goalId } : {}),\n\t\t\tstatus: \"aligning\",\n\t\t\trevision: 0,\n\t\t\tactiveStep: \"objective\",\n\t\t\tsteps: [\n\t\t\t\t{\n\t\t\t\t\tstep: \"objective\",\n\t\t\t\t\tstatus: \"active\",\n\t\t\t\t\tblocks: input.blocks.map((block) => materializeBlock(block, undefined, 0)),\n\t\t\t\t},\n\t\t\t\t{ step: \"detail\", status: \"pending\", blocks: [] },\n\t\t\t\t{ step: \"design\", status: \"pending\", blocks: [] },\n\t\t\t],\n\t\t\troundCount: 0,\n\t\t};\n\t\treturn { state: { alignments: [...state.alignments, record] }, transitions: [] };\n\t}\n\n\tif (existing.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal and cannot be updated: ${input.id}`);\n\t}\n\tconst revision = input.revision ?? 0;\n\tif (revision <= existing.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-revision\",\n\t\t\t`stale alignment revision: ${input.id} expects > ${existing.revision}, got ${revision}`,\n\t\t);\n\t}\n\tconst step = activeStep(existing);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${existing.activeStep} is ${step.status}; only the active step accepts blocks`,\n\t\t);\n\t}\n\t// Deleting a block that still carries open comments is rejected (总则 3):\n\t// deletion must not become a way to dodge unresolved objections.\n\tfor (const previous of step.blocks) {\n\t\tif (input.blocks.some((block) => block.id === previous.id)) continue;\n\t\tif (previous.comments.some((comment) => comment.status === \"open\")) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"block-has-open-comments\",\n\t\t\t\t`block ${previous.id} carries open comments; resolve them before deleting it (edit the content instead)`,\n\t\t\t);\n\t\t}\n\t}\n\t// 已批准块写入防护(v3.7,2026-10-08 dogfood 根因):全量重写会按内容指纹\n\t// 把已确认块静默重置 pending——未显式确认时结构化拒绝,列出将被重置的块。\n\tif (!input.reviseApproved) {\n\t\tconst conflicts = input.blocks\n\t\t\t.filter((blockInput) => {\n\t\t\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\t\t\treturn (\n\t\t\t\t\tprevious?.status === \"approved\" &&\n\t\t\t\t\tprevious.contentHash !==\n\t\t\t\t\t\tblockFingerprint(blockInput.kind, blockInput.content, blockInput.rationale, blockInput.impact)\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map((blockInput) => blockInput.id);\n\t\tif (conflicts.length > 0) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"approved-blocks-conflict\",\n\t\t\t\t`this write would reset approved blocks to pending: ${conflicts.join(\", \")}; resubmit with reviseApproved: true, or keep the approved block content unchanged`,\n\t\t\t);\n\t\t}\n\t}\n\tconst blocks = input.blocks.map((blockInput) => {\n\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\treturn materializeBlock(blockInput, previous, existing.roundCount);\n\t});\n\t// Version snapshot (走查 2026-10-03): archive the outgoing step block set\n\t// before it is replaced. Append-only, bounded rolling; the page views\n\t// historical versions read-only — only the latest accepts interaction.\n\t// An empty outgoing set has nothing to roll back to — skip it, otherwise\n\t// the version bar fills with chips that render a blank view.\n\tconst archive = JSON.stringify(step.blocks);\n\tconst snapshot: AlignmentVersionSnapshot = {\n\t\trevision: existing.revision,\n\t\tround: existing.roundCount,\n\t\tstep: existing.activeStep,\n\t\tblocks: archive.length <= ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? structuredClone(step.blocks) : [],\n\t\t...(archive.length > ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? { truncated: true as const } : {}),\n\t};\n\tconst priorVersions = (\n\t\tstep.blocks.length === 0 ? (existing.versions ?? []) : [...(existing.versions ?? []), snapshot]\n\t).slice(-ALIGNMENT_LIMITS.versionsMax);\n\tconst updated: AlignmentRecord = {\n\t\t...existing,\n\t\ttitle: input.title ?? existing.title,\n\t\tgoalId: input.goalId ?? existing.goalId,\n\t\trevision,\n\t\tsteps: existing.steps.map((candidate) =>\n\t\t\tcandidate.step === existing.activeStep ? { ...candidate, blocks } : candidate,\n\t\t),\n\t\tpendingFeedback: undefined,\n\t\tversions: priorVersions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((record) => (record.id === updated.id ? updated : record)) },\n\t\ttransitions: [],\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// POST /round: the user feedback round (§5 round flow, v3.2 audits)\n// ---------------------------------------------------------------------------\n\nexport function applyRoundPost(record0: AlignmentRecord, post: AlignmentRoundPost): RoundPostOutcome {\n\tif (record0.status !== \"aligning\" && record0.status !== \"finalizing\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record0.id}`);\n\t}\n\t// Anti-staleness gate (terminal audit D6): the page must confirm against\n\t// the exact state it rendered.\n\tif (post.baseRevision !== record0.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-state\",\n\t\t\t`page snapshot is stale (base ${post.baseRevision}, current ${record0.revision}); reload and resubmit`,\n\t\t);\n\t}\n\tconst hasNewFeedback = post.comments.length > 0 || post.additions.length > 0 || post.answers.length > 0;\n\tif (post.stepConfirmed && hasNewFeedback) {\n\t\tthrow new AlignmentError(\n\t\t\t\"confirmed-with-feedback\",\n\t\t\t\"step confirmation cannot carry new comments/additions/answers; submit the feedback first\",\n\t\t);\n\t}\n\t// 终审阶段唯一合法提交是终审确认(走查 2026-10-03)。此时所有步已锁定,\n\t// 反馈字段没有落点——尤其 approvals 镜像会把已锁定的 design 块整体重置为\n\t// pending,再被无门禁的终审分支带进 completed。显式拒绝,不做静默重置。\n\tif (\n\t\trecord0.status === \"finalizing\" &&\n\t\t(hasNewFeedback || post.commentReplies.length > 0 || post.approvals.length > 0 || !post.stepConfirmed)\n\t) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t\"finalizing accepts only the final confirm (stepConfirmed without feedback fields)\",\n\t\t);\n\t}\n\n\tconst record: AlignmentRecord = structuredClone(record0);\n\tconst step = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!step) throw new AlignmentError(\"invalid-input\", \"active step missing\");\n\tconst blockById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst round = record.roundCount + 1;\n\n\t// New user comments: the quote must match the block's current content.\n\tfor (const comment of post.comments) {\n\t\tconst block = blockById.get(comment.blockId);\n\t\tif (!block) {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `comment targets unknown block: ${comment.blockId}`);\n\t\t}\n\t\tconst note = nonEmptyString(comment.note, \"comment note\", ALIGNMENT_LIMITS.noteMaxLength);\n\t\tif (typeof comment.quote !== \"string\" || !block.content.includes(comment.quote)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"comment-quote-mismatch\",\n\t\t\t\t`comment quote does not match block ${comment.blockId} content`,\n\t\t\t);\n\t\t}\n\t\tblock.comments.push({\n\t\t\tid: `c${round}-${block.comments.length + 1}`,\n\t\t\tauthor: \"user\",\n\t\t\tquote: comment.quote,\n\t\t\tnote,\n\t\t\t...(comment.escalate === true ? { escalate: true } : {}),\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\n\t// Replies to assistant comments (answer/dismiss are mutually exclusive).\n\tfor (const reply of post.commentReplies) {\n\t\tconst block = blockById.get(reply.blockId);\n\t\tconst comment = block?.comments.find((candidate) => candidate.id === reply.commentId);\n\t\tif (!comment || comment.author !== \"assistant\") {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply targets unknown or non-assistant comment: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tif (comment.status !== \"open\") continue; // idempotent resubmission\n\t\tif ((reply.answer !== undefined) === (reply.dismiss === true)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply must carry exactly one of answer or dismiss: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = reply.dismiss\n\t\t\t? { kind: \"dismissed\", text: \"user marked unimportant\", round }\n\t\t\t: {\n\t\t\t\t\tkind: \"answered\",\n\t\t\t\t\ttext: nonEmptyString(reply.answer ?? \"\", \"reply answer\", ALIGNMENT_LIMITS.answerMaxLength),\n\t\t\t\t\tround,\n\t\t\t\t};\n\t}\n\n\t// Fill-in answers for missing blocks: stored verbatim as the user's words;\n\t// the model restructures them into the block on its next alignment_set.\n\tfor (const answer of post.answers) {\n\t\tconst block = blockById.get(answer.blockId);\n\t\tif (!block || block.kind !== \"missing\") {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `answer targets unknown or non-missing block: ${answer.blockId}`);\n\t\t}\n\t\tif ((block.draftAnswer ?? \"\").trim() !== \"\") continue; // idempotent resubmission\n\t\tblock.draftAnswer = nonEmptyString(answer.value, \"answer\", ALIGNMENT_LIMITS.answerMaxLength);\n\t}\n\n\t// Approvals mirror the page's full approved set for the active step.\n\t// Skipped in finalizing (走查 2026-10-03): the step is locked and the\n\t// final confirm carries approvals=[] — mirroring would reopen its blocks.\n\tif (record0.status !== \"finalizing\") {\n\t\tconst approvalSet = new Set(post.approvals);\n\t\tfor (const id of approvalSet) {\n\t\t\tif (!blockById.has(id)) {\n\t\t\t\tthrow new AlignmentError(\"invalid-input\", `approval references unknown block: ${id}`);\n\t\t\t}\n\t\t}\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = approvalSet.has(block.id) ? \"approved\" : \"pending\";\n\t\t}\n\t}\n\n\t// Additions queue as pendingFeedback — the server-side truth of gate\n\t// condition three (terminal audit D7); cleared by the next alignment_set.\n\tconst additions = [...(record.pendingFeedback?.additions ?? [])];\n\tfor (const addition of post.additions) {\n\t\tadditions.push(nonEmptyString(addition, \"addition\", ALIGNMENT_LIMITS.additionMaxLength));\n\t}\n\trecord.pendingFeedback = additions.length > 0 ? { round, additions } : undefined;\n\n\tconst transitions: AlignmentTransitionEntry[] = [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"round.submitted\",\n\t\t\tround,\n\t\t\treason: {\n\t\t\t\tcode: \"round-submitted\",\n\t\t\t\tdetail: `approvals=${post.approvals.length} comments=${post.comments.length} replies=${post.commentReplies.length} answers=${post.answers.length} additions=${post.additions.length}`,\n\t\t\t},\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n\trecord.roundCount = round;\n\n\tlet stepConfirmedNow = false;\n\tif (post.stepConfirmed && record0.status === \"finalizing\") {\n\t\t// 终审总览确认(走查 2026-10-03): the user reviewed the full plan and\n\t\t// confirmed it — the only path to `completed` + baseline write.\n\t\trecord.status = \"completed\";\n\t\trecord.revision += 1;\n\t\tfor (const locked of record.steps) locked.baselineConfirmedAtRound ??= record.roundCount;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"alignment.completed\",\n\t\t\treason: { code: \"final-review-approved\", detail: \"final review confirmed the complete plan\" },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst result: AlignmentRoundResult = {\n\t\t\talignmentId: record.id,\n\t\t\tround: record.roundCount,\n\t\t\tstep: record.activeStep,\n\t\t\trevision: record.revision,\n\t\t\tapprovals: post.approvals,\n\t\t\tcomments: post.comments,\n\t\t\tcommentReplies: post.commentReplies,\n\t\t\tanswers: post.answers,\n\t\t\tadditions: post.additions,\n\t\t\tstepConfirmed: true,\n\t\t};\n\t\treturn { record, transitions, result };\n\t}\n\tif (post.stepConfirmed) {\n\t\tif (step.blocks.length === 0) {\n\t\t\t// 空环节不可锁定(2026-10-08 走查,防重复确认的机制兜底):AI 尚未写入\n\t\t\t// 块集的新环节 vacuously 全绿,确认它只会让用户在空环节上反复走确认\n\t\t\t// 流程。等待模型 alignment_set 写入后再确认。\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"empty-step\",\n\t\t\t\t`active step \"${record.activeStep}\" has no blocks yet; wait for the model to write them before confirming`,\n\t\t\t);\n\t\t}\n\t\tconst snapshot = gate(record);\n\t\tif (!snapshot.allGreen) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"gate-not-satisfied\",\n\t\t\t\t`gate not satisfied: ${snapshot.unapproved} unapproved blocks, ${snapshot.openComments} open comments, ${snapshot.pendingAdditions} pending additions`,\n\t\t\t);\n\t\t}\n\t\tstep.status = \"locked\";\n\t\tstep.baselineConfirmedAtRound = round;\n\t\trecord.revision += 1;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"step.confirmed\",\n\t\t\tround,\n\t\t\treason: { code: \"step-baseline-locked\", detail: `${step.blocks.length} blocks frozen` },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst upcoming = nextStep(record.activeStep);\n\t\tif (upcoming) {\n\t\t\trecord.activeStep = upcoming;\n\t\t\tconst upcomingStep = record.steps.find((candidate) => candidate.step === upcoming);\n\t\t\tif (upcomingStep) upcomingStep.status = \"active\";\n\t\t\trecord.revision += 1;\n\t\t} else {\n\t\t\t// Design locked (方案确认): enter the final-review overview instead\n\t\t\t// of completing (走查 2026-10-03) — the baseline is written only\n\t\t\t// after the user confirms the full plan on the 终审 tab.\n\t\t\trecord.status = \"finalizing\";\n\t\t\trecord.revision += 1;\n\t\t\ttransitions.push({\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"design.locked\",\n\t\t\t\tround,\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"scheme-locked-awaiting-final-review\",\n\t\t\t\t\tdetail: \"all three baselines locked; awaiting final review\",\n\t\t\t\t},\n\t\t\t\trevision: record.revision,\n\t\t\t});\n\t\t}\n\t\tstepConfirmedNow = true;\n\t}\n\n\tconst result: AlignmentRoundResult = {\n\t\talignmentId: record.id,\n\t\tround,\n\t\tstep: record0.activeStep,\n\t\trevision: record.revision,\n\t\tapprovals: post.approvals,\n\t\tcomments: post.comments,\n\t\tcommentReplies: post.commentReplies,\n\t\tanswers: post.answers,\n\t\tadditions: post.additions,\n\t\tstepConfirmed: stepConfirmedNow,\n\t};\n\treturn { record, transitions, result };\n}\n\n/** Injection succeeded — the delivered transition closes the round (terminal audit D1). */\nexport function markDelivered(record: AlignmentRecord, round: number): AlignmentTransitionEntry[] {\n\treturn [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"round.delivered\",\n\t\t\tround,\n\t\t\treason: { code: \"round-delivered\", detail: \"feedback injected into the session\" },\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n}\n\n// ---------------------------------------------------------------------------\n// alignment_advance: inspect / retreat / cancel (总则 6)\n// ---------------------------------------------------------------------------\n\nexport function applyAlignmentAdvance(state: AlignmentState, input: AdvanceInput): AdvanceOutcome {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (input.action === \"inspect\") return { state, transitions: [] };\n\tif (input.action === \"rollback\") {\n\t\t// Rollback is routed through applyVersionRollback (设计 §6); refusing\n\t\t// here keeps the advance branch from ever treating it as a retreat.\n\t\tthrow new AlignmentError(\"invalid-input\", \"rollback routes through applyVersionRollback, not advance\");\n\t}\n\tif (record.status === \"completed\" || record.status === \"cancelled\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record.id}`);\n\t}\n\n\tif (input.action === \"cancel\") {\n\t\tconst cancelled: AlignmentRecord = { ...record, status: \"cancelled\" };\n\t\treturn {\n\t\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? cancelled : item)) },\n\t\t\ttransitions: [\n\t\t\t\t{\n\t\t\t\t\talignmentId: record.id,\n\t\t\t\t\tevent: \"alignment.cancelled\",\n\t\t\t\t\treason: { code: \"alignment-cancelled\" },\n\t\t\t\t\trevision: record.revision,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\t// retreat: comments survive the reset append-only (terminal audit D5); the\n\t// escalate comments that triggered it are resolved here with the note.\n\tconst note = nonEmptyString(input.note ?? \"\", \"retreat note\", ALIGNMENT_LIMITS.noteMaxLength);\n\tconst target = input.step;\n\tif (!target || STEP_ORDER.indexOf(target) >= STEP_ORDER.indexOf(record.activeStep)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"retreat-target-invalid\",\n\t\t\t`retreat target must be an earlier step than ${record.activeStep}`,\n\t\t);\n\t}\n\tconst affected: string[] = [];\n\tconst retreated: AlignmentRecord = { ...record, steps: record.steps.map((step) => ({ ...step })) };\n\tfor (const step of retreated.steps) {\n\t\tif (STEP_ORDER.indexOf(step.step) < STEP_ORDER.indexOf(target)) continue;\n\t\taffected.push(step.step);\n\t\tstep.status = \"reset\";\n\t\tstep.baselineConfirmedAtRound = undefined;\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = \"pending\";\n\t\t\tfor (const comment of block.comments) {\n\t\t\t\tif (comment.status === \"open\" && comment.author === \"user\" && comment.escalate === true) {\n\t\t\t\t\tcomment.status = \"resolved\";\n\t\t\t\t\tcomment.resolution = { kind: \"incorporated\", text: note, round: record.roundCount };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tretreated.activeStep = target;\n\t// Finalizing → retreated: the final-review state is gone with the\n\t// downstream resets; the task is writable at the target step again.\n\tif (retreated.status === \"finalizing\") retreated.status = \"aligning\";\n\tretreated.revision += 1;\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? retreated : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"step.retreated\",\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"step-retreated\",\n\t\t\t\t\tdetail: `target=${target}; reset=${affected.join(\",\")}; note=${note}`,\n\t\t\t\t},\n\t\t\t\trevision: retreated.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\nexport interface VersionRollbackInput {\n\talignmentId: string;\n\t/** Target snapshot revision from `record.versions` (must be the active step's). */\n\ttargetRevision: number;\n}\n\n/**\n * Step-scoped version rollback (走查 2026-10-03): drop the current unconfirmed\n * version (not archived), promote the target snapshot to live, engine-bump the\n * revision (D4). Comments survive append-only — restored blocks carry the union\n * of live and archived comments by comment id, live status winning (a\n * resolution is irreversible). Only the active (unlocked) step accepts it;\n * locked steps go through retreat. Other steps are never touched.\n */\nexport function applyVersionRollback(state: AlignmentState, input: VersionRollbackInput): EngineRun {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (record.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `version rollback needs an aligning task: ${record.id}`);\n\t}\n\tconst step = activeStep(record);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${record.activeStep} is ${step.status}; locked steps go through retreat`,\n\t\t);\n\t}\n\tconst versions = [...(record.versions ?? [])];\n\tconst index = versions.findIndex(\n\t\t(snapshot) => snapshot.revision === input.targetRevision && snapshot.step === record.activeStep,\n\t);\n\tif (index < 0) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`no snapshot of step ${record.activeStep} at revision ${input.targetRevision}`,\n\t\t);\n\t}\n\tconst snapshot = versions[index];\n\tif (snapshot.truncated) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`snapshot r${input.targetRevision} exceeded the size budget and has no blocks`,\n\t\t);\n\t}\n\tconst liveById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst restored = snapshot.blocks.map((archived) => {\n\t\tconst live = liveById.get(archived.id);\n\t\tif (!live) return structuredClone(archived);\n\t\tconst merged = new Map(archived.comments.map((comment) => [comment.id, comment]));\n\t\tfor (const comment of live.comments) merged.set(comment.id, comment); // live status wins\n\t\treturn { ...structuredClone(archived), comments: [...merged.values()] };\n\t});\n\t// The current version is dropped, the target is promoted: remove it from\n\t// the archive rather than re-archiving the live set.\n\tversions.splice(index, 1);\n\tconst rolled: AlignmentRecord = {\n\t\t...record,\n\t\trevision: record.revision + 1,\n\t\tsteps: record.steps.map((candidate) =>\n\t\t\tcandidate.step === record.activeStep\n\t\t\t\t? { ...candidate, blocks: restored, status: candidate.status === \"locked\" ? \"reset\" : candidate.status }\n\t\t\t\t: candidate,\n\t\t),\n\t\tversions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === rolled.id ? rolled : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: rolled.id,\n\t\t\t\tstep: rolled.activeStep,\n\t\t\t\tevent: \"version.rolled-back\",\n\t\t\t\treason: { code: \"version-rolled-back\", detail: `target=r${input.targetRevision}` },\n\t\t\t\trevision: rolled.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\n/** Compact final baseline listing (§5 round flow 4): one line per block, answers first. */\nexport function finalBaselineLines(\n\trecord: AlignmentRecord,\n): { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] | undefined {\n\tif (record.status !== \"completed\") return undefined;\n\tconst lines: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] = [];\n\tfor (const step of record.steps) {\n\t\tfor (const block of step.blocks) {\n\t\t\tconst confirmed = block.content.split(\"\\n\").find((line) => line.includes(\"→ 已确认\"));\n\t\t\tconst text = (confirmed ? confirmed.replace(/^\\s*→\\s*/, \"\") : block.content.split(\"\\n\")[0]).slice(0, 120);\n\t\t\tlines.push({ step: step.step, kind: block.kind, id: block.id, line: text });\n\t\t}\n\t}\n\treturn lines;\n}\n"]}
@@ -428,6 +428,12 @@ export function applyRoundPost(record0, post) {
428
428
  return { record, transitions, result };
429
429
  }
430
430
  if (post.stepConfirmed) {
431
+ if (step.blocks.length === 0) {
432
+ // 空环节不可锁定(2026-10-08 走查,防重复确认的机制兜底):AI 尚未写入
433
+ // 块集的新环节 vacuously 全绿,确认它只会让用户在空环节上反复走确认
434
+ // 流程。等待模型 alignment_set 写入后再确认。
435
+ throw new AlignmentError("empty-step", `active step "${record.activeStep}" has no blocks yet; wait for the model to write them before confirming`);
436
+ }
431
437
  const snapshot = gate(record);
432
438
  if (!snapshot.allGreen) {
433
439
  throw new AlignmentError("gate-not-satisfied", `gate not satisfied: ${snapshot.unapproved} unapproved blocks, ${snapshot.openComments} open comments, ${snapshot.pendingAdditions} pending additions`);
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACN,gBAAgB,EAIhB,cAAc,EAWd,UAAU,GACV,MAAM,YAAY,CAAC;AA2DpB,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,MAAM,UAAU,GAAG,8BAA8B,CAAC;AAElD,MAAM,UAAU,gBAAgB,CAAC,EAAU,EAAW;IACrD,gGAA8C;IAC9C,oFAAwC;IACxC,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,GAAG,CAAC;AAAA,CACzE;AAED,kFAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAC/B,IAAwB,EACxB,OAAe,EACf,SAA6B,EAC7B,MAA4B,EACnB;IACT,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;IACrF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAAA,CACpE;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,IAAY,EAAE,GAAW,EAAU;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtD,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,GAAG,IAAI,6BAA6B,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACxB,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,GAAG,IAAI,YAAY,GAAG,aAAa,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,kBAAkB,CAAC,KAAiB,EAAQ;IACpD,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACrF,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,uDAAqD,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CACvE,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,uBAAuB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,KAAK,CAAC,EAAE,UAAU,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAC9F,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;QACnG,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,SAAS,KAAK,CAAC,EAAE,sBAAsB,gBAAgB,CAAC,kBAAkB,aAAa,CACvF,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,SAAS,KAAK,CAAC,EAAE,0CAA0C,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;QACvE,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,SAAS,KAAK,CAAC,EAAE,sBAAsB,gBAAgB,CAAC,gBAAgB,WAAW,CACnF,CAAC;IACH,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC5C,IAAI,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAChE,MAAM,IAAI,cAAc,CAAC,iBAAiB,EAAE,uBAAuB,KAAK,CAAC,EAAE,6BAA6B,CAAC,CAAC;QAC3G,CAAC;QACD,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,OAAO,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;IAC5F,CAAC;AAAA,CACD;AAED,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,UAAU,UAAU,CAAC,KAAqB,EAAE,EAAU,EAA+B;IAC1F,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAAA,CAC3D;AAED,MAAM,UAAU,UAAU,CAAC,MAAuB,EAAsB;IACvE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC;IACrF,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,wBAAwB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnG,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,MAAuB,EAAgB;IAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACzE,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;YAC7D,UAAU,IAAI,CAAC,CAAC;YAChB,SAAS;QACV,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;YAC1D,UAAU,IAAI,CAAC,CAAC;QACjB,CAAC;IACF,CAAC;IACD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAS;YACxC,YAAY,IAAI,CAAC,CAAC;YAClB,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACpC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;YACxE,CAAC;QACF,CAAC;IACF,CAAC;IACD,MAAM,gBAAgB,GAAG,MAAM,CAAC,eAAe,EAAE,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC;IACvE,OAAO;QACN,UAAU;QACV,YAAY;QACZ,gBAAgB;QAChB,QAAQ,EAAE,UAAU,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,IAAI,gBAAgB,KAAK,CAAC;QAC1E,OAAO;KACP,CAAC;AAAA,CACF;AAED,SAAS,QAAQ,CAAC,IAAqB,EAA+B;IACrE,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACvF;AAED,8EAA8E;AAC9E,qFAA6E;AAC7E,8EAA8E;AAE9E,SAAS,gBAAgB,CACxB,KAAiB,EACjB,QAAoC,EACpC,YAAoB,EACH;IACjB,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/F,MAAM,SAAS,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,WAAW,KAAK,WAAW,CAAC;IACjF,MAAM,KAAK,GAAmB;QAC7B,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,8EAAoE;QACpE,+DAA+D;QAC/D,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAC/C,WAAW,EAAE,WAAW;QACxB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,YAAY,GAAG,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,QAAQ,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,QAAQ,EAAE,EAAE;KACZ,CAAC;IACF,KAAK,CAAC,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,IAAI,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9E,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,aAAa,CAAC,QAA4B,EAAE,KAAiB,EAAE,KAAa,EAAsB;IAC1G,MAAM,MAAM,GAAuB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACpG,2EAAuE;IACvE,wEAAwE;IACxE,mCAAmC;IACnC,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC,mBAAmB,EAAE,WAAW,OAAO,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;QAC5B,OAAO,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC9E,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,IAAI,cAAc,CAAC,mBAAmB,EAAE,2CAA2C,GAAG,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,uEAAuE;IACvE,oCAAoC;IACpC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QACtG,MAAM,CAAC,IAAI,CAAC;YACX,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,MAAM,EAAE,WAAW;YACnB,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,MAAM;YACd,cAAc,EAAE,KAAK;SACrB,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAqB,EAAE,KAAwB,EAAa;IAC7F,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,cAAc,CACvB,YAAY,EACZ,wEAAwE,KAAK,CAAC,EAAE,EAAE,CAClF,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;QAC7D,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,4BAA4B,gBAAgB,CAAC,gBAAgB,SAAS,CAAC,CAAC;IACnH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAClC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,uCAAuC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,6EAAyE;QACzE,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;QACjH,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,cAAc,CACvB,yBAAyB,EACzB,wCAAwC,MAAM,CAAC,EAAE,+BAA+B,CAChF,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAoB;YAC/B,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE;YAC9B,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,EAAE,UAAU;YAClB,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,WAAW;YACvB,KAAK,EAAE;gBACN;oBACC,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,QAAQ;oBAChB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;iBAC1E;gBACD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;gBACjD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;aACjD;YACD,UAAU,EAAE,CAAC;SACb,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAClF,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACpC,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,gDAAgD,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;IACrC,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,cAAc,CACvB,gBAAgB,EAChB,6BAA6B,KAAK,CAAC,EAAE,cAAc,QAAQ,CAAC,QAAQ,SAAS,QAAQ,EAAE,CACvF,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QACzD,MAAM,IAAI,cAAc,CACvB,mBAAmB,EACnB,QAAQ,QAAQ,CAAC,UAAU,OAAO,IAAI,CAAC,MAAM,uCAAuC,CACpF,CAAC;IACH,CAAC;IACD,4EAAwE;IACxE,iEAAiE;IACjE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC;YAAE,SAAS;QACrE,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,cAAc,CACvB,yBAAyB,EACzB,SAAS,QAAQ,CAAC,EAAE,oFAAoF,CACxG,CAAC;QACH,CAAC;IACF,CAAC;IACD,kGAAkD;IAClD,2GAA2C;IAC3C,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM;aAC5B,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,UAAU,CAAC,EAAE,CAAC,CAAC;YACjF,OAAO,CACN,QAAQ,EAAE,MAAM,KAAK,UAAU;gBAC/B,QAAQ,CAAC,WAAW;oBACnB,gBAAgB,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,CAC/F,CAAC;QAAA,CACF,CAAC;aACD,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,cAAc,CACvB,0BAA0B,EAC1B,sDAAsD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,oFAAoF,CAC9J,CAAC;QACH,CAAC;IACF,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,UAAU,CAAC,EAAE,CAAC,CAAC;QACjF,OAAO,gBAAgB,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAAA,CACnE,CAAC,CAAC;IACH,4EAAwE;IACxE,sEAAsE;IACtE,yEAAuE;IACvE,2EAAyE;IACzE,6DAA6D;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAA6B;QAC1C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,KAAK,EAAE,QAAQ,CAAC,UAAU;QAC1B,IAAI,EAAE,QAAQ,CAAC,UAAU;QACzB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;QACvG,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnG,CAAC;IACF,MAAM,aAAa,GAAG,CACrB,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAC/F,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,OAAO,GAAoB;QAChC,GAAG,QAAQ;QACX,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK;QACpC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACvC,QAAQ;QACR,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CACvC,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAC7E;QACD,eAAe,EAAE,SAAS;QAC1B,QAAQ,EAAE,aAAa;KACvB,CAAC;IACF,OAAO;QACN,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE;QACtG,WAAW,EAAE,EAAE;KACf,CAAC;AAAA,CACF;AAED,8EAA8E;AAC9E,qEAAoE;AACpE,8EAA8E;AAE9E,MAAM,UAAU,cAAc,CAAC,OAAwB,EAAE,IAAwB,EAAoB;IACpG,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACtE,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,0BAA0B,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,yEAAyE;IACzE,+BAA+B;IAC/B,IAAI,IAAI,CAAC,YAAY,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,cAAc,CACvB,aAAa,EACb,gCAAgC,IAAI,CAAC,YAAY,aAAa,OAAO,CAAC,QAAQ,wBAAwB,CACtG,CAAC;IACH,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACxG,IAAI,IAAI,CAAC,aAAa,IAAI,cAAc,EAAE,CAAC;QAC1C,MAAM,IAAI,cAAc,CACvB,yBAAyB,EACzB,0FAA0F,CAC1F,CAAC;IACH,CAAC;IACD,qGAA2C;IAC3C,oGAAgD;IAChD,kGAA8C;IAC9C,IACC,OAAO,CAAC,MAAM,KAAK,YAAY;QAC/B,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EACrG,CAAC;QACF,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,mFAAmF,CACnF,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAoB,eAAe,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC;IACpF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,qBAAqB,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;IAEpC,uEAAuE;IACvE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,kCAAkC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAC1F,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjF,MAAM,IAAI,cAAc,CACvB,wBAAwB,EACxB,sCAAsC,OAAO,CAAC,OAAO,UAAU,CAC/D,CAAC;QACH,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YACnB,EAAE,EAAE,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YAC5C,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI;YACJ,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,MAAM,EAAE,MAAM;YACd,cAAc,EAAE,KAAK;SACrB,CAAC,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;QACtF,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAChD,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,mDAAmD,KAAK,CAAC,SAAS,EAAE,CACpE,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,SAAS,CAAC,0BAA0B;QACnE,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,sDAAsD,KAAK,CAAC,SAAS,EAAE,CACvE,CAAC;QACH,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;QAC5B,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,OAAO;YACjC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE;YAC/D,CAAC,CAAC;gBACA,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,cAAc,EAAE,gBAAgB,CAAC,eAAe,CAAC;gBAC1F,KAAK;aACL,CAAC;IACL,CAAC;IAED,2EAA2E;IAC3E,wEAAwE;IACxE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,gDAAgD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,SAAS,CAAC,0BAA0B;QACjF,KAAK,CAAC,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAC9F,CAAC;IAED,qEAAqE;IACrE,wEAAoE;IACpE,4EAA0E;IAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5C,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;YACvF,CAAC;QACF,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,CAAC;IACF,CAAC;IAED,uEAAqE;IACrE,0EAA0E;IAC1E,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IACjE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,CAAC,eAAe,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAEjF,MAAM,WAAW,GAA+B;QAC/C;YACC,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,IAAI,EAAE,MAAM,CAAC,UAAU;YACvB,KAAK,EAAE,iBAAiB;YACxB,KAAK;YACL,MAAM,EAAE;gBACP,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EAAE,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,aAAa,IAAI,CAAC,QAAQ,CAAC,MAAM,YAAY,IAAI,CAAC,cAAc,CAAC,MAAM,YAAY,IAAI,CAAC,OAAO,CAAC,MAAM,cAAc,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE;aACrL;YACD,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACzB;KACD,CAAC;IACF,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC;IAE1B,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,IAAI,IAAI,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAC3D,iFAA6D;QAC7D,kEAAgE;QAChE,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC;QAC5B,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACrB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,KAAK;YAAE,MAAM,CAAC,wBAAwB,KAAK,MAAM,CAAC,UAAU,CAAC;QACzF,WAAW,CAAC,IAAI,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,KAAK,EAAE,qBAAqB;YAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,0CAA0C,EAAE;YAC7F,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACzB,CAAC,CAAC;QACH,MAAM,MAAM,GAAyB;YACpC,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,KAAK,EAAE,MAAM,CAAC,UAAU;YACxB,IAAI,EAAE,MAAM,CAAC,UAAU;YACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,aAAa,EAAE,IAAI;SACnB,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxB,MAAM,IAAI,cAAc,CACvB,oBAAoB,EACpB,uBAAuB,QAAQ,CAAC,UAAU,uBAAuB,QAAQ,CAAC,YAAY,mBAAmB,QAAQ,CAAC,gBAAgB,oBAAoB,CACtJ,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QACvB,IAAI,CAAC,wBAAwB,GAAG,KAAK,CAAC;QACtC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACrB,WAAW,CAAC,IAAI,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,IAAI,EAAE,MAAM,CAAC,UAAU;YACvB,KAAK,EAAE,gBAAgB;YACvB,KAAK;YACL,MAAM,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,gBAAgB,EAAE;YACvF,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACzB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,CAAC,UAAU,GAAG,QAAQ,CAAC;YAC7B,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;YACnF,IAAI,YAAY;gBAAE,YAAY,CAAC,MAAM,GAAG,QAAQ,CAAC;YACjD,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACP,wEAAgE;YAChE,qEAA+D;YAC/D,2DAAuD;YACvD,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC;YAC7B,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;YACrB,WAAW,CAAC,IAAI,CAAC;gBAChB,WAAW,EAAE,MAAM,CAAC,EAAE;gBACtB,KAAK,EAAE,eAAe;gBACtB,KAAK;gBACL,MAAM,EAAE;oBACP,IAAI,EAAE,qCAAqC;oBAC3C,MAAM,EAAE,mDAAmD;iBAC3D;gBACD,QAAQ,EAAE,MAAM,CAAC,QAAQ;aACzB,CAAC,CAAC;QACJ,CAAC;QACD,gBAAgB,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,MAAM,MAAM,GAAyB;QACpC,WAAW,EAAE,MAAM,CAAC,EAAE;QACtB,KAAK;QACL,IAAI,EAAE,OAAO,CAAC,UAAU;QACxB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,gBAAgB;KAC/B,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AAAA,CACvC;AAED,6FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,MAAuB,EAAE,KAAa,EAA8B;IACjG,OAAO;QACN;YACC,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,KAAK,EAAE,iBAAiB;YACxB,KAAK;YACL,MAAM,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,oCAAoC,EAAE;YACjF,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACzB;KACD,CAAC;AAAA,CACF;AAED,8EAA8E;AAC9E,2DAAuD;AACvD,8EAA8E;AAE9E,MAAM,UAAU,qBAAqB,CAAC,KAAqB,EAAE,KAAmB,EAAkB;IACjG,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,cAAc,CAAC,WAAW,EAAE,sBAAsB,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,yEAAoE;QACpE,oEAAoE;QACpE,MAAM,IAAI,cAAc,CAAC,eAAe,EAAE,2DAA2D,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACpE,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,0BAA0B,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAoB,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QACtE,OAAO;YACN,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE;YACjG,WAAW,EAAE;gBACZ;oBACC,WAAW,EAAE,MAAM,CAAC,EAAE;oBACtB,KAAK,EAAE,qBAAqB;oBAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE;oBACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ;iBACzB;aACD;SACD,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,cAAc,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;IAC9F,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC;IAC1B,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,cAAc,CACvB,wBAAwB,EACxB,+CAA+C,MAAM,CAAC,UAAU,EAAE,CAClE,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAoB,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IACnG,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;QACpC,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,SAAS;QACzE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;YACzB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACtC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;oBACzF,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;oBAC5B,OAAO,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;gBACrF,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IACD,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC;IAC9B,oEAAkE;IAClE,oEAAoE;IACpE,IAAI,SAAS,CAAC,MAAM,KAAK,YAAY;QAAE,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC;IACrE,SAAS,CAAC,QAAQ,IAAI,CAAC,CAAC;IACxB,OAAO;QACN,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE;QACjG,WAAW,EAAE;YACZ;gBACC,WAAW,EAAE,MAAM,CAAC,EAAE;gBACtB,KAAK,EAAE,gBAAgB;gBACvB,MAAM,EAAE;oBACP,IAAI,EAAE,gBAAgB;oBACtB,MAAM,EAAE,UAAU,MAAM,WAAW,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;iBACrE;gBACD,QAAQ,EAAE,SAAS,CAAC,QAAQ;aAC5B;SACD;KACD,CAAC;AAAA,CACF;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAqB,EAAE,KAA2B,EAAa;IACnG,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,cAAc,CAAC,WAAW,EAAE,sBAAsB,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAClC,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,4CAA4C,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACrG,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QACzD,MAAM,IAAI,cAAc,CACvB,mBAAmB,EACnB,QAAQ,MAAM,CAAC,UAAU,OAAO,IAAI,CAAC,MAAM,mCAAmC,CAC9E,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAC/B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,cAAc,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU,CAC/F,CAAC;IACF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,uBAAuB,MAAM,CAAC,UAAU,gBAAgB,KAAK,CAAC,cAAc,EAAE,CAC9E,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,cAAc,CACvB,eAAe,EACf,aAAa,KAAK,CAAC,cAAc,6CAA6C,CAC9E,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAClF,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB;QACzF,OAAO,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;IAAA,CACxE,CAAC,CAAC;IACH,yEAAyE;IACzE,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,MAAM,GAAoB;QAC/B,GAAG,MAAM;QACT,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,CAAC;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CACrC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,UAAU;YACnC,CAAC,CAAC,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE;YACxG,CAAC,CAAC,SAAS,CACZ;QACD,QAAQ;KACR,CAAC;IACF,OAAO;QACN,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE;QAC9F,WAAW,EAAE;YACZ;gBACC,WAAW,EAAE,MAAM,CAAC,EAAE;gBACtB,IAAI,EAAE,MAAM,CAAC,UAAU;gBACvB,KAAK,EAAE,qBAAqB;gBAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,KAAK,CAAC,cAAc,EAAE,EAAE;gBAClF,QAAQ,EAAE,MAAM,CAAC,QAAQ;aACzB;SACD;KACD,CAAC;AAAA,CACF;AAED,4FAA2F;AAC3F,MAAM,UAAU,kBAAkB,CACjC,MAAuB,EACuE;IAC9F,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,KAAK,GAAoF,EAAE,CAAC;IAClG,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAO,CAAC,CAAC,CAAC;YACnF,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,YAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC1G,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["/**\n * Pure alignment state machine (设计 §5) — no host API access, no I/O.\n *\n * Every function takes JSON-safe state and returns the next state plus the\n * audit transitions to persist; index.ts owns persistence (entries + events)\n * and http-server.ts owns transport. Deterministic by construction: same\n * inputs, same outputs, no clocks, no randomness.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tALIGNMENT_LIMITS,\n\ttype AlignmentBlock,\n\ttype AlignmentBlockKind,\n\ttype AlignmentComment,\n\tAlignmentError,\n\ttype AlignmentRecord,\n\ttype AlignmentRoundPost,\n\ttype AlignmentRoundResult,\n\ttype AlignmentState,\n\ttype AlignmentStepId,\n\ttype AlignmentStepState,\n\ttype AlignmentTransitionEntry,\n\ttype AlignmentVersionSnapshot,\n\ttype GateSnapshot,\n\ttype PendingItem,\n\tSTEP_ORDER,\n} from \"./types.ts\";\n\nexport interface EngineRun {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n/** Model-facing block input (alignment_set); comment ownership is checked against it. */\nexport interface BlockInput {\n\tid: string;\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\t/** New assistant comments for this block (author is forced to assistant). */\n\tcomments?: { id: string; note: string; quote?: string }[];\n\t/** Resolutions the model attaches to existing comments. */\n\tcommentResolutions?: {\n\t\tcommentId: string;\n\t\tkind: \"answered\" | \"dismissed\" | \"incorporated\";\n\t\ttext?: string;\n\t}[];\n}\n\nexport interface AlignmentSetInput {\n\tid: string;\n\ttitle?: string;\n\tgoalId?: string;\n\trevision?: number;\n\t/**\n\t * Explicit confirmation that this write may rewrite blocks the user already\n\t * approved (总则 3): without it, a fingerprint change on an approved block\n\t * is rejected with `approved-blocks-conflict` instead of silently resetting\n\t * it to pending.\n\t */\n\treviseApproved?: boolean;\n\tblocks: BlockInput[];\n}\n\nexport interface AdvanceInput {\n\talignmentId: string;\n\taction: \"inspect\" | \"retreat\" | \"cancel\" | \"rollback\";\n\tstep?: AlignmentStepId;\n\tnote?: string;\n\t/** Rollback target: the active step's snapshot revision anchor; latest snapshot when omitted. */\n\tversionId?: number;\n}\n\nexport interface RoundPostOutcome {\n\trecord: AlignmentRecord;\n\ttransitions: AlignmentTransitionEntry[];\n\tresult: AlignmentRoundResult;\n}\n\nexport interface AdvanceOutcome {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Hashing & validation\n// ---------------------------------------------------------------------------\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport function validAlignmentId(id: string): boolean {\n\t// 入参可能来自未校验的工具输入(空对象 → undefined):test() 会把实参\n\t// 字符串化为 \"undefined\" 而\"通过\"模式匹配,必须先做类型守卫。\n\treturn typeof id === \"string\" && ID_PATTERN.test(id) && id.length <= 128;\n}\n\n/** Fingerprint of the fields whose change must re-ask for approval (总则 3). */\nexport function blockFingerprint(\n\tkind: AlignmentBlockKind,\n\tcontent: string,\n\trationale: string | undefined,\n\timpact: string[] | undefined,\n): string {\n\tconst canonical = JSON.stringify([kind, content, rationale ?? null, impact ?? null]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\nfunction nonEmptyString(value: unknown, name: string, max: number): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} must be a non-empty string`);\n\t}\n\tif (value.length > max) {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} exceeds ${max} characters`);\n\t}\n\treturn value;\n}\n\nfunction validateBlockInput(input: BlockInput): void {\n\tif (typeof input.id !== \"string\" || input.id.trim() === \"\" || input.id.length > 128) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block id must be a non-empty string (≤128 chars): ${String(input.id)}`,\n\t\t);\n\t}\n\tif (![\"fact\", \"assumption\", \"decision\", \"missing\", \"visual\"].includes(input.kind)) {\n\t\tthrow new AlignmentError(\"invalid-block\", `invalid block kind: ${String(input.kind)}`);\n\t}\n\tnonEmptyString(input.content, `block ${input.id} content`, ALIGNMENT_LIMITS.contentMaxLength);\n\tif (input.rationale !== undefined && input.rationale.length > ALIGNMENT_LIMITS.rationaleMaxLength) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} rationale exceeds ${ALIGNMENT_LIMITS.rationaleMaxLength} characters`,\n\t\t);\n\t}\n\tif (input.impact?.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new AlignmentError(\"invalid-block\", `block ${input.id} impact must be a non-empty string array`);\n\t}\n\tif ((input.comments?.length ?? 0) > ALIGNMENT_LIMITS.notesPerBlockMax) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} carries more than ${ALIGNMENT_LIMITS.notesPerBlockMax} comments`,\n\t\t);\n\t}\n\tfor (const comment of input.comments ?? []) {\n\t\tif (typeof comment.id !== \"string\" || comment.id.trim() === \"\") {\n\t\t\tthrow new AlignmentError(\"invalid-comment\", `comment id on block ${input.id} must be a non-empty string`);\n\t\t}\n\t\tnonEmptyString(comment.note, `comment ${comment.id} note`, ALIGNMENT_LIMITS.noteMaxLength);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// State helpers\n// ---------------------------------------------------------------------------\n\nexport function findRecord(state: AlignmentState, id: string): AlignmentRecord | undefined {\n\treturn state.alignments.find((record) => record.id === id);\n}\n\nexport function activeStep(record: AlignmentRecord): AlignmentStepState {\n\tconst found = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!found) throw new AlignmentError(\"invalid-input\", `active step missing: ${record.activeStep}`);\n\treturn found;\n}\n\n/**\n * Gate facts (总则 2): the three server-checkable conditions. A missing block\n * without a stored user answer counts as `missing-empty`; once answered it is\n * just an unapproved block waiting for the model to restructure it.\n */\nexport function gate(record: AlignmentRecord): GateSnapshot {\n\tconst step = activeStep(record);\n\tconst pending: PendingItem[] = [];\n\tlet unapproved = 0;\n\tfor (const block of step.blocks) {\n\t\tif (block.kind === \"missing\" && (block.draftAnswer ?? \"\").trim() === \"\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"missing-empty\" });\n\t\t\tunapproved += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.status !== \"approved\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"unapproved\" });\n\t\t\tunapproved += 1;\n\t\t}\n\t}\n\tlet openComments = 0;\n\tfor (const block of step.blocks) {\n\t\tfor (const comment of block.comments) {\n\t\t\tif (comment.status !== \"open\") continue;\n\t\t\topenComments += 1;\n\t\t\tif (comment.author === \"assistant\") {\n\t\t\t\tpending.push({ blockId: block.id, reason: \"assistant-question-open\" });\n\t\t\t}\n\t\t}\n\t}\n\tconst pendingAdditions = record.pendingFeedback?.additions.length ?? 0;\n\treturn {\n\t\tunapproved,\n\t\topenComments,\n\t\tpendingAdditions,\n\t\tallGreen: unapproved === 0 && openComments === 0 && pendingAdditions === 0,\n\t\tpending,\n\t};\n}\n\nfunction nextStep(step: AlignmentStepId): AlignmentStepId | undefined {\n\tconst index = STEP_ORDER.indexOf(step);\n\treturn index >= 0 && index < STEP_ORDER.length - 1 ? STEP_ORDER[index + 1] : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// alignment_set: create / update (总则 3 hash inheritance, 总则 5 comment merge)\n// ---------------------------------------------------------------------------\n\nfunction materializeBlock(\n\tinput: BlockInput,\n\tprevious: AlignmentBlock | undefined,\n\tcurrentRound: number,\n): AlignmentBlock {\n\tconst fingerprint = blockFingerprint(input.kind, input.content, input.rationale, input.impact);\n\tconst unchanged = previous !== undefined && previous.contentHash === fingerprint;\n\tconst block: AlignmentBlock = {\n\t\tid: input.id,\n\t\tkind: input.kind,\n\t\tcontent: input.content,\n\t\trationale: input.rationale,\n\t\timpact: input.impact,\n\t\t// Hash-identical resent blocks keep their approval (防确认疲劳); changed\n\t\t// content resets to pending and flags the round it changed in.\n\t\tstatus: unchanged ? previous.status : \"pending\",\n\t\tcontentHash: fingerprint,\n\t\t...(unchanged ? {} : { modifiedInRound: currentRound + 1 }),\n\t\t...(previous?.draftAnswer !== undefined ? { draftAnswer: previous.draftAnswer } : {}),\n\t\tcomments: [],\n\t};\n\tblock.comments = mergeComments(previous?.comments ?? [], input, currentRound);\n\treturn block;\n}\n\nfunction mergeComments(existing: AlignmentComment[], input: BlockInput, round: number): AlignmentComment[] {\n\tconst merged: AlignmentComment[] = existing.map((comment) => ({ ...comment }));\n\tconst resolutions = new Map((input.commentResolutions ?? []).map((item) => [item.commentId, item]));\n\t// Existing comments are append-only (总则 5): user text survives resends\n\t// verbatim, open assistant questions cannot silently disappear; only an\n\t// explicit resolution closes them.\n\tfor (const comment of merged) {\n\t\tconst resolution = resolutions.get(comment.id);\n\t\tif (!resolution) continue;\n\t\tresolutions.delete(comment.id);\n\t\tif (comment.status === \"resolved\") {\n\t\t\tthrow new AlignmentError(\"comment-ownership\", `comment ${comment.id} is already resolved`);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = { kind: resolution.kind, text: resolution.text, round };\n\t}\n\tif (resolutions.size > 0) {\n\t\tconst ids = [...resolutions.keys()].join(\", \");\n\t\tthrow new AlignmentError(\"comment-ownership\", `resolutions reference unknown comments: ${ids}`);\n\t}\n\t// New comments are assistant-only and deduped by note so a resent open\n\t// question never duplicates itself.\n\tfor (const fresh of input.comments ?? []) {\n\t\tif (merged.some((comment) => comment.author === \"assistant\" && comment.note === fresh.note)) continue;\n\t\tmerged.push({\n\t\t\tid: fresh.id,\n\t\t\tauthor: \"assistant\",\n\t\t\t...(fresh.quote !== undefined ? { quote: fresh.quote } : {}),\n\t\t\tnote: fresh.note,\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\treturn merged;\n}\n\nexport function applyAlignmentSet(state: AlignmentState, input: AlignmentSetInput): EngineRun {\n\tif (!validAlignmentId(input.id)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-id\",\n\t\t\t`alignment id must match [A-Za-z0-9][A-Za-z0-9._-]* (no leading dot): ${input.id}`,\n\t\t);\n\t}\n\tif (!Array.isArray(input.blocks) || input.blocks.length === 0) {\n\t\tthrow new AlignmentError(\"invalid-input\", \"blocks must be a non-empty array\");\n\t}\n\tif (input.blocks.length > ALIGNMENT_LIMITS.blocksPerStepMax) {\n\t\tthrow new AlignmentError(\"invalid-input\", `a step may carry at most ${ALIGNMENT_LIMITS.blocksPerStepMax} blocks`);\n\t}\n\tconst seen = new Set<string>();\n\tfor (const block of input.blocks) {\n\t\tvalidateBlockInput(block);\n\t\tif (seen.has(block.id)) {\n\t\t\tthrow new AlignmentError(\"invalid-block\", `duplicate block id within the step: ${block.id}`);\n\t\t}\n\t\tseen.add(block.id);\n\t}\n\n\tconst existing = findRecord(state, input.id);\n\tif (!existing) {\n\t\t// Create: created-as-aligning (no draft state) and single-active (总则 9).\n\t\tconst active = state.alignments.find((record) => record.status === \"aligning\" || record.status === \"finalizing\");\n\t\tif (active) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"active-alignment-exists\",\n\t\t\t\t`an alignment is already in progress: ${active.id}; complete or cancel it first`,\n\t\t\t);\n\t\t}\n\t\tconst record: AlignmentRecord = {\n\t\t\tid: input.id,\n\t\t\ttitle: input.title ?? input.id,\n\t\t\t...(input.goalId !== undefined ? { goalId: input.goalId } : {}),\n\t\t\tstatus: \"aligning\",\n\t\t\trevision: 0,\n\t\t\tactiveStep: \"objective\",\n\t\t\tsteps: [\n\t\t\t\t{\n\t\t\t\t\tstep: \"objective\",\n\t\t\t\t\tstatus: \"active\",\n\t\t\t\t\tblocks: input.blocks.map((block) => materializeBlock(block, undefined, 0)),\n\t\t\t\t},\n\t\t\t\t{ step: \"detail\", status: \"pending\", blocks: [] },\n\t\t\t\t{ step: \"design\", status: \"pending\", blocks: [] },\n\t\t\t],\n\t\t\troundCount: 0,\n\t\t};\n\t\treturn { state: { alignments: [...state.alignments, record] }, transitions: [] };\n\t}\n\n\tif (existing.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal and cannot be updated: ${input.id}`);\n\t}\n\tconst revision = input.revision ?? 0;\n\tif (revision <= existing.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-revision\",\n\t\t\t`stale alignment revision: ${input.id} expects > ${existing.revision}, got ${revision}`,\n\t\t);\n\t}\n\tconst step = activeStep(existing);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${existing.activeStep} is ${step.status}; only the active step accepts blocks`,\n\t\t);\n\t}\n\t// Deleting a block that still carries open comments is rejected (总则 3):\n\t// deletion must not become a way to dodge unresolved objections.\n\tfor (const previous of step.blocks) {\n\t\tif (input.blocks.some((block) => block.id === previous.id)) continue;\n\t\tif (previous.comments.some((comment) => comment.status === \"open\")) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"block-has-open-comments\",\n\t\t\t\t`block ${previous.id} carries open comments; resolve them before deleting it (edit the content instead)`,\n\t\t\t);\n\t\t}\n\t}\n\t// 已批准块写入防护(v3.7,2026-10-08 dogfood 根因):全量重写会按内容指纹\n\t// 把已确认块静默重置 pending——未显式确认时结构化拒绝,列出将被重置的块。\n\tif (!input.reviseApproved) {\n\t\tconst conflicts = input.blocks\n\t\t\t.filter((blockInput) => {\n\t\t\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\t\t\treturn (\n\t\t\t\t\tprevious?.status === \"approved\" &&\n\t\t\t\t\tprevious.contentHash !==\n\t\t\t\t\t\tblockFingerprint(blockInput.kind, blockInput.content, blockInput.rationale, blockInput.impact)\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map((blockInput) => blockInput.id);\n\t\tif (conflicts.length > 0) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"approved-blocks-conflict\",\n\t\t\t\t`this write would reset approved blocks to pending: ${conflicts.join(\", \")}; resubmit with reviseApproved: true, or keep the approved block content unchanged`,\n\t\t\t);\n\t\t}\n\t}\n\tconst blocks = input.blocks.map((blockInput) => {\n\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\treturn materializeBlock(blockInput, previous, existing.roundCount);\n\t});\n\t// Version snapshot (走查 2026-10-03): archive the outgoing step block set\n\t// before it is replaced. Append-only, bounded rolling; the page views\n\t// historical versions read-only — only the latest accepts interaction.\n\t// An empty outgoing set has nothing to roll back to — skip it, otherwise\n\t// the version bar fills with chips that render a blank view.\n\tconst archive = JSON.stringify(step.blocks);\n\tconst snapshot: AlignmentVersionSnapshot = {\n\t\trevision: existing.revision,\n\t\tround: existing.roundCount,\n\t\tstep: existing.activeStep,\n\t\tblocks: archive.length <= ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? structuredClone(step.blocks) : [],\n\t\t...(archive.length > ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? { truncated: true as const } : {}),\n\t};\n\tconst priorVersions = (\n\t\tstep.blocks.length === 0 ? (existing.versions ?? []) : [...(existing.versions ?? []), snapshot]\n\t).slice(-ALIGNMENT_LIMITS.versionsMax);\n\tconst updated: AlignmentRecord = {\n\t\t...existing,\n\t\ttitle: input.title ?? existing.title,\n\t\tgoalId: input.goalId ?? existing.goalId,\n\t\trevision,\n\t\tsteps: existing.steps.map((candidate) =>\n\t\t\tcandidate.step === existing.activeStep ? { ...candidate, blocks } : candidate,\n\t\t),\n\t\tpendingFeedback: undefined,\n\t\tversions: priorVersions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((record) => (record.id === updated.id ? updated : record)) },\n\t\ttransitions: [],\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// POST /round: the user feedback round (§5 round flow, v3.2 audits)\n// ---------------------------------------------------------------------------\n\nexport function applyRoundPost(record0: AlignmentRecord, post: AlignmentRoundPost): RoundPostOutcome {\n\tif (record0.status !== \"aligning\" && record0.status !== \"finalizing\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record0.id}`);\n\t}\n\t// Anti-staleness gate (terminal audit D6): the page must confirm against\n\t// the exact state it rendered.\n\tif (post.baseRevision !== record0.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-state\",\n\t\t\t`page snapshot is stale (base ${post.baseRevision}, current ${record0.revision}); reload and resubmit`,\n\t\t);\n\t}\n\tconst hasNewFeedback = post.comments.length > 0 || post.additions.length > 0 || post.answers.length > 0;\n\tif (post.stepConfirmed && hasNewFeedback) {\n\t\tthrow new AlignmentError(\n\t\t\t\"confirmed-with-feedback\",\n\t\t\t\"step confirmation cannot carry new comments/additions/answers; submit the feedback first\",\n\t\t);\n\t}\n\t// 终审阶段唯一合法提交是终审确认(走查 2026-10-03)。此时所有步已锁定,\n\t// 反馈字段没有落点——尤其 approvals 镜像会把已锁定的 design 块整体重置为\n\t// pending,再被无门禁的终审分支带进 completed。显式拒绝,不做静默重置。\n\tif (\n\t\trecord0.status === \"finalizing\" &&\n\t\t(hasNewFeedback || post.commentReplies.length > 0 || post.approvals.length > 0 || !post.stepConfirmed)\n\t) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t\"finalizing accepts only the final confirm (stepConfirmed without feedback fields)\",\n\t\t);\n\t}\n\n\tconst record: AlignmentRecord = structuredClone(record0);\n\tconst step = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!step) throw new AlignmentError(\"invalid-input\", \"active step missing\");\n\tconst blockById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst round = record.roundCount + 1;\n\n\t// New user comments: the quote must match the block's current content.\n\tfor (const comment of post.comments) {\n\t\tconst block = blockById.get(comment.blockId);\n\t\tif (!block) {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `comment targets unknown block: ${comment.blockId}`);\n\t\t}\n\t\tconst note = nonEmptyString(comment.note, \"comment note\", ALIGNMENT_LIMITS.noteMaxLength);\n\t\tif (typeof comment.quote !== \"string\" || !block.content.includes(comment.quote)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"comment-quote-mismatch\",\n\t\t\t\t`comment quote does not match block ${comment.blockId} content`,\n\t\t\t);\n\t\t}\n\t\tblock.comments.push({\n\t\t\tid: `c${round}-${block.comments.length + 1}`,\n\t\t\tauthor: \"user\",\n\t\t\tquote: comment.quote,\n\t\t\tnote,\n\t\t\t...(comment.escalate === true ? { escalate: true } : {}),\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\n\t// Replies to assistant comments (answer/dismiss are mutually exclusive).\n\tfor (const reply of post.commentReplies) {\n\t\tconst block = blockById.get(reply.blockId);\n\t\tconst comment = block?.comments.find((candidate) => candidate.id === reply.commentId);\n\t\tif (!comment || comment.author !== \"assistant\") {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply targets unknown or non-assistant comment: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tif (comment.status !== \"open\") continue; // idempotent resubmission\n\t\tif ((reply.answer !== undefined) === (reply.dismiss === true)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply must carry exactly one of answer or dismiss: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = reply.dismiss\n\t\t\t? { kind: \"dismissed\", text: \"user marked unimportant\", round }\n\t\t\t: {\n\t\t\t\t\tkind: \"answered\",\n\t\t\t\t\ttext: nonEmptyString(reply.answer ?? \"\", \"reply answer\", ALIGNMENT_LIMITS.answerMaxLength),\n\t\t\t\t\tround,\n\t\t\t\t};\n\t}\n\n\t// Fill-in answers for missing blocks: stored verbatim as the user's words;\n\t// the model restructures them into the block on its next alignment_set.\n\tfor (const answer of post.answers) {\n\t\tconst block = blockById.get(answer.blockId);\n\t\tif (!block || block.kind !== \"missing\") {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `answer targets unknown or non-missing block: ${answer.blockId}`);\n\t\t}\n\t\tif ((block.draftAnswer ?? \"\").trim() !== \"\") continue; // idempotent resubmission\n\t\tblock.draftAnswer = nonEmptyString(answer.value, \"answer\", ALIGNMENT_LIMITS.answerMaxLength);\n\t}\n\n\t// Approvals mirror the page's full approved set for the active step.\n\t// Skipped in finalizing (走查 2026-10-03): the step is locked and the\n\t// final confirm carries approvals=[] — mirroring would reopen its blocks.\n\tif (record0.status !== \"finalizing\") {\n\t\tconst approvalSet = new Set(post.approvals);\n\t\tfor (const id of approvalSet) {\n\t\t\tif (!blockById.has(id)) {\n\t\t\t\tthrow new AlignmentError(\"invalid-input\", `approval references unknown block: ${id}`);\n\t\t\t}\n\t\t}\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = approvalSet.has(block.id) ? \"approved\" : \"pending\";\n\t\t}\n\t}\n\n\t// Additions queue as pendingFeedback — the server-side truth of gate\n\t// condition three (terminal audit D7); cleared by the next alignment_set.\n\tconst additions = [...(record.pendingFeedback?.additions ?? [])];\n\tfor (const addition of post.additions) {\n\t\tadditions.push(nonEmptyString(addition, \"addition\", ALIGNMENT_LIMITS.additionMaxLength));\n\t}\n\trecord.pendingFeedback = additions.length > 0 ? { round, additions } : undefined;\n\n\tconst transitions: AlignmentTransitionEntry[] = [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"round.submitted\",\n\t\t\tround,\n\t\t\treason: {\n\t\t\t\tcode: \"round-submitted\",\n\t\t\t\tdetail: `approvals=${post.approvals.length} comments=${post.comments.length} replies=${post.commentReplies.length} answers=${post.answers.length} additions=${post.additions.length}`,\n\t\t\t},\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n\trecord.roundCount = round;\n\n\tlet stepConfirmedNow = false;\n\tif (post.stepConfirmed && record0.status === \"finalizing\") {\n\t\t// 终审总览确认(走查 2026-10-03): the user reviewed the full plan and\n\t\t// confirmed it — the only path to `completed` + baseline write.\n\t\trecord.status = \"completed\";\n\t\trecord.revision += 1;\n\t\tfor (const locked of record.steps) locked.baselineConfirmedAtRound ??= record.roundCount;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"alignment.completed\",\n\t\t\treason: { code: \"final-review-approved\", detail: \"final review confirmed the complete plan\" },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst result: AlignmentRoundResult = {\n\t\t\talignmentId: record.id,\n\t\t\tround: record.roundCount,\n\t\t\tstep: record.activeStep,\n\t\t\trevision: record.revision,\n\t\t\tapprovals: post.approvals,\n\t\t\tcomments: post.comments,\n\t\t\tcommentReplies: post.commentReplies,\n\t\t\tanswers: post.answers,\n\t\t\tadditions: post.additions,\n\t\t\tstepConfirmed: true,\n\t\t};\n\t\treturn { record, transitions, result };\n\t}\n\tif (post.stepConfirmed) {\n\t\tconst snapshot = gate(record);\n\t\tif (!snapshot.allGreen) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"gate-not-satisfied\",\n\t\t\t\t`gate not satisfied: ${snapshot.unapproved} unapproved blocks, ${snapshot.openComments} open comments, ${snapshot.pendingAdditions} pending additions`,\n\t\t\t);\n\t\t}\n\t\tstep.status = \"locked\";\n\t\tstep.baselineConfirmedAtRound = round;\n\t\trecord.revision += 1;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"step.confirmed\",\n\t\t\tround,\n\t\t\treason: { code: \"step-baseline-locked\", detail: `${step.blocks.length} blocks frozen` },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst upcoming = nextStep(record.activeStep);\n\t\tif (upcoming) {\n\t\t\trecord.activeStep = upcoming;\n\t\t\tconst upcomingStep = record.steps.find((candidate) => candidate.step === upcoming);\n\t\t\tif (upcomingStep) upcomingStep.status = \"active\";\n\t\t\trecord.revision += 1;\n\t\t} else {\n\t\t\t// Design locked (方案确认): enter the final-review overview instead\n\t\t\t// of completing (走查 2026-10-03) — the baseline is written only\n\t\t\t// after the user confirms the full plan on the 终审 tab.\n\t\t\trecord.status = \"finalizing\";\n\t\t\trecord.revision += 1;\n\t\t\ttransitions.push({\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"design.locked\",\n\t\t\t\tround,\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"scheme-locked-awaiting-final-review\",\n\t\t\t\t\tdetail: \"all three baselines locked; awaiting final review\",\n\t\t\t\t},\n\t\t\t\trevision: record.revision,\n\t\t\t});\n\t\t}\n\t\tstepConfirmedNow = true;\n\t}\n\n\tconst result: AlignmentRoundResult = {\n\t\talignmentId: record.id,\n\t\tround,\n\t\tstep: record0.activeStep,\n\t\trevision: record.revision,\n\t\tapprovals: post.approvals,\n\t\tcomments: post.comments,\n\t\tcommentReplies: post.commentReplies,\n\t\tanswers: post.answers,\n\t\tadditions: post.additions,\n\t\tstepConfirmed: stepConfirmedNow,\n\t};\n\treturn { record, transitions, result };\n}\n\n/** Injection succeeded — the delivered transition closes the round (terminal audit D1). */\nexport function markDelivered(record: AlignmentRecord, round: number): AlignmentTransitionEntry[] {\n\treturn [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"round.delivered\",\n\t\t\tround,\n\t\t\treason: { code: \"round-delivered\", detail: \"feedback injected into the session\" },\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n}\n\n// ---------------------------------------------------------------------------\n// alignment_advance: inspect / retreat / cancel (总则 6)\n// ---------------------------------------------------------------------------\n\nexport function applyAlignmentAdvance(state: AlignmentState, input: AdvanceInput): AdvanceOutcome {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (input.action === \"inspect\") return { state, transitions: [] };\n\tif (input.action === \"rollback\") {\n\t\t// Rollback is routed through applyVersionRollback (设计 §6); refusing\n\t\t// here keeps the advance branch from ever treating it as a retreat.\n\t\tthrow new AlignmentError(\"invalid-input\", \"rollback routes through applyVersionRollback, not advance\");\n\t}\n\tif (record.status === \"completed\" || record.status === \"cancelled\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record.id}`);\n\t}\n\n\tif (input.action === \"cancel\") {\n\t\tconst cancelled: AlignmentRecord = { ...record, status: \"cancelled\" };\n\t\treturn {\n\t\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? cancelled : item)) },\n\t\t\ttransitions: [\n\t\t\t\t{\n\t\t\t\t\talignmentId: record.id,\n\t\t\t\t\tevent: \"alignment.cancelled\",\n\t\t\t\t\treason: { code: \"alignment-cancelled\" },\n\t\t\t\t\trevision: record.revision,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\t// retreat: comments survive the reset append-only (terminal audit D5); the\n\t// escalate comments that triggered it are resolved here with the note.\n\tconst note = nonEmptyString(input.note ?? \"\", \"retreat note\", ALIGNMENT_LIMITS.noteMaxLength);\n\tconst target = input.step;\n\tif (!target || STEP_ORDER.indexOf(target) >= STEP_ORDER.indexOf(record.activeStep)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"retreat-target-invalid\",\n\t\t\t`retreat target must be an earlier step than ${record.activeStep}`,\n\t\t);\n\t}\n\tconst affected: string[] = [];\n\tconst retreated: AlignmentRecord = { ...record, steps: record.steps.map((step) => ({ ...step })) };\n\tfor (const step of retreated.steps) {\n\t\tif (STEP_ORDER.indexOf(step.step) < STEP_ORDER.indexOf(target)) continue;\n\t\taffected.push(step.step);\n\t\tstep.status = \"reset\";\n\t\tstep.baselineConfirmedAtRound = undefined;\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = \"pending\";\n\t\t\tfor (const comment of block.comments) {\n\t\t\t\tif (comment.status === \"open\" && comment.author === \"user\" && comment.escalate === true) {\n\t\t\t\t\tcomment.status = \"resolved\";\n\t\t\t\t\tcomment.resolution = { kind: \"incorporated\", text: note, round: record.roundCount };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tretreated.activeStep = target;\n\t// Finalizing → retreated: the final-review state is gone with the\n\t// downstream resets; the task is writable at the target step again.\n\tif (retreated.status === \"finalizing\") retreated.status = \"aligning\";\n\tretreated.revision += 1;\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? retreated : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"step.retreated\",\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"step-retreated\",\n\t\t\t\t\tdetail: `target=${target}; reset=${affected.join(\",\")}; note=${note}`,\n\t\t\t\t},\n\t\t\t\trevision: retreated.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\nexport interface VersionRollbackInput {\n\talignmentId: string;\n\t/** Target snapshot revision from `record.versions` (must be the active step's). */\n\ttargetRevision: number;\n}\n\n/**\n * Step-scoped version rollback (走查 2026-10-03): drop the current unconfirmed\n * version (not archived), promote the target snapshot to live, engine-bump the\n * revision (D4). Comments survive append-only — restored blocks carry the union\n * of live and archived comments by comment id, live status winning (a\n * resolution is irreversible). Only the active (unlocked) step accepts it;\n * locked steps go through retreat. Other steps are never touched.\n */\nexport function applyVersionRollback(state: AlignmentState, input: VersionRollbackInput): EngineRun {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (record.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `version rollback needs an aligning task: ${record.id}`);\n\t}\n\tconst step = activeStep(record);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${record.activeStep} is ${step.status}; locked steps go through retreat`,\n\t\t);\n\t}\n\tconst versions = [...(record.versions ?? [])];\n\tconst index = versions.findIndex(\n\t\t(snapshot) => snapshot.revision === input.targetRevision && snapshot.step === record.activeStep,\n\t);\n\tif (index < 0) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`no snapshot of step ${record.activeStep} at revision ${input.targetRevision}`,\n\t\t);\n\t}\n\tconst snapshot = versions[index];\n\tif (snapshot.truncated) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`snapshot r${input.targetRevision} exceeded the size budget and has no blocks`,\n\t\t);\n\t}\n\tconst liveById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst restored = snapshot.blocks.map((archived) => {\n\t\tconst live = liveById.get(archived.id);\n\t\tif (!live) return structuredClone(archived);\n\t\tconst merged = new Map(archived.comments.map((comment) => [comment.id, comment]));\n\t\tfor (const comment of live.comments) merged.set(comment.id, comment); // live status wins\n\t\treturn { ...structuredClone(archived), comments: [...merged.values()] };\n\t});\n\t// The current version is dropped, the target is promoted: remove it from\n\t// the archive rather than re-archiving the live set.\n\tversions.splice(index, 1);\n\tconst rolled: AlignmentRecord = {\n\t\t...record,\n\t\trevision: record.revision + 1,\n\t\tsteps: record.steps.map((candidate) =>\n\t\t\tcandidate.step === record.activeStep\n\t\t\t\t? { ...candidate, blocks: restored, status: candidate.status === \"locked\" ? \"reset\" : candidate.status }\n\t\t\t\t: candidate,\n\t\t),\n\t\tversions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === rolled.id ? rolled : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: rolled.id,\n\t\t\t\tstep: rolled.activeStep,\n\t\t\t\tevent: \"version.rolled-back\",\n\t\t\t\treason: { code: \"version-rolled-back\", detail: `target=r${input.targetRevision}` },\n\t\t\t\trevision: rolled.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\n/** Compact final baseline listing (§5 round flow 4): one line per block, answers first. */\nexport function finalBaselineLines(\n\trecord: AlignmentRecord,\n): { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] | undefined {\n\tif (record.status !== \"completed\") return undefined;\n\tconst lines: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] = [];\n\tfor (const step of record.steps) {\n\t\tfor (const block of step.blocks) {\n\t\t\tconst confirmed = block.content.split(\"\\n\").find((line) => line.includes(\"→ 已确认\"));\n\t\t\tconst text = (confirmed ? confirmed.replace(/^\\s*→\\s*/, \"\") : block.content.split(\"\\n\")[0]).slice(0, 120);\n\t\t\tlines.push({ step: step.step, kind: block.kind, id: block.id, line: text });\n\t\t}\n\t}\n\treturn lines;\n}\n"]}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure alignment state machine (设计 §5) — no host API access, no I/O.\n *\n * Every function takes JSON-safe state and returns the next state plus the\n * audit transitions to persist; index.ts owns persistence (entries + events)\n * and http-server.ts owns transport. Deterministic by construction: same\n * inputs, same outputs, no clocks, no randomness.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tALIGNMENT_LIMITS,\n\ttype AlignmentBlock,\n\ttype AlignmentBlockKind,\n\ttype AlignmentComment,\n\tAlignmentError,\n\ttype AlignmentRecord,\n\ttype AlignmentRoundPost,\n\ttype AlignmentRoundResult,\n\ttype AlignmentState,\n\ttype AlignmentStepId,\n\ttype AlignmentStepState,\n\ttype AlignmentTransitionEntry,\n\ttype AlignmentVersionSnapshot,\n\ttype GateSnapshot,\n\ttype PendingItem,\n\tSTEP_ORDER,\n} from \"./types.ts\";\n\nexport interface EngineRun {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n/** Model-facing block input (alignment_set); comment ownership is checked against it. */\nexport interface BlockInput {\n\tid: string;\n\tkind: AlignmentBlockKind;\n\tcontent: string;\n\trationale?: string;\n\timpact?: string[];\n\t/** New assistant comments for this block (author is forced to assistant). */\n\tcomments?: { id: string; note: string; quote?: string }[];\n\t/** Resolutions the model attaches to existing comments. */\n\tcommentResolutions?: {\n\t\tcommentId: string;\n\t\tkind: \"answered\" | \"dismissed\" | \"incorporated\";\n\t\ttext?: string;\n\t}[];\n}\n\nexport interface AlignmentSetInput {\n\tid: string;\n\ttitle?: string;\n\tgoalId?: string;\n\trevision?: number;\n\t/**\n\t * Explicit confirmation that this write may rewrite blocks the user already\n\t * approved (总则 3): without it, a fingerprint change on an approved block\n\t * is rejected with `approved-blocks-conflict` instead of silently resetting\n\t * it to pending.\n\t */\n\treviseApproved?: boolean;\n\tblocks: BlockInput[];\n}\n\nexport interface AdvanceInput {\n\talignmentId: string;\n\taction: \"inspect\" | \"retreat\" | \"cancel\" | \"rollback\";\n\tstep?: AlignmentStepId;\n\tnote?: string;\n\t/** Rollback target: the active step's snapshot revision anchor; latest snapshot when omitted. */\n\tversionId?: number;\n}\n\nexport interface RoundPostOutcome {\n\trecord: AlignmentRecord;\n\ttransitions: AlignmentTransitionEntry[];\n\tresult: AlignmentRoundResult;\n}\n\nexport interface AdvanceOutcome {\n\tstate: AlignmentState;\n\ttransitions: AlignmentTransitionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Hashing & validation\n// ---------------------------------------------------------------------------\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport function validAlignmentId(id: string): boolean {\n\t// 入参可能来自未校验的工具输入(空对象 → undefined):test() 会把实参\n\t// 字符串化为 \"undefined\" 而\"通过\"模式匹配,必须先做类型守卫。\n\treturn typeof id === \"string\" && ID_PATTERN.test(id) && id.length <= 128;\n}\n\n/** Fingerprint of the fields whose change must re-ask for approval (总则 3). */\nexport function blockFingerprint(\n\tkind: AlignmentBlockKind,\n\tcontent: string,\n\trationale: string | undefined,\n\timpact: string[] | undefined,\n): string {\n\tconst canonical = JSON.stringify([kind, content, rationale ?? null, impact ?? null]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\nfunction nonEmptyString(value: unknown, name: string, max: number): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} must be a non-empty string`);\n\t}\n\tif (value.length > max) {\n\t\tthrow new AlignmentError(\"invalid-input\", `${name} exceeds ${max} characters`);\n\t}\n\treturn value;\n}\n\nfunction validateBlockInput(input: BlockInput): void {\n\tif (typeof input.id !== \"string\" || input.id.trim() === \"\" || input.id.length > 128) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block id must be a non-empty string (≤128 chars): ${String(input.id)}`,\n\t\t);\n\t}\n\tif (![\"fact\", \"assumption\", \"decision\", \"missing\", \"visual\"].includes(input.kind)) {\n\t\tthrow new AlignmentError(\"invalid-block\", `invalid block kind: ${String(input.kind)}`);\n\t}\n\tnonEmptyString(input.content, `block ${input.id} content`, ALIGNMENT_LIMITS.contentMaxLength);\n\tif (input.rationale !== undefined && input.rationale.length > ALIGNMENT_LIMITS.rationaleMaxLength) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} rationale exceeds ${ALIGNMENT_LIMITS.rationaleMaxLength} characters`,\n\t\t);\n\t}\n\tif (input.impact?.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new AlignmentError(\"invalid-block\", `block ${input.id} impact must be a non-empty string array`);\n\t}\n\tif ((input.comments?.length ?? 0) > ALIGNMENT_LIMITS.notesPerBlockMax) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-block\",\n\t\t\t`block ${input.id} carries more than ${ALIGNMENT_LIMITS.notesPerBlockMax} comments`,\n\t\t);\n\t}\n\tfor (const comment of input.comments ?? []) {\n\t\tif (typeof comment.id !== \"string\" || comment.id.trim() === \"\") {\n\t\t\tthrow new AlignmentError(\"invalid-comment\", `comment id on block ${input.id} must be a non-empty string`);\n\t\t}\n\t\tnonEmptyString(comment.note, `comment ${comment.id} note`, ALIGNMENT_LIMITS.noteMaxLength);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// State helpers\n// ---------------------------------------------------------------------------\n\nexport function findRecord(state: AlignmentState, id: string): AlignmentRecord | undefined {\n\treturn state.alignments.find((record) => record.id === id);\n}\n\nexport function activeStep(record: AlignmentRecord): AlignmentStepState {\n\tconst found = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!found) throw new AlignmentError(\"invalid-input\", `active step missing: ${record.activeStep}`);\n\treturn found;\n}\n\n/**\n * Gate facts (总则 2): the three server-checkable conditions. A missing block\n * without a stored user answer counts as `missing-empty`; once answered it is\n * just an unapproved block waiting for the model to restructure it.\n */\nexport function gate(record: AlignmentRecord): GateSnapshot {\n\tconst step = activeStep(record);\n\tconst pending: PendingItem[] = [];\n\tlet unapproved = 0;\n\tfor (const block of step.blocks) {\n\t\tif (block.kind === \"missing\" && (block.draftAnswer ?? \"\").trim() === \"\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"missing-empty\" });\n\t\t\tunapproved += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.status !== \"approved\") {\n\t\t\tpending.push({ blockId: block.id, reason: \"unapproved\" });\n\t\t\tunapproved += 1;\n\t\t}\n\t}\n\tlet openComments = 0;\n\tfor (const block of step.blocks) {\n\t\tfor (const comment of block.comments) {\n\t\t\tif (comment.status !== \"open\") continue;\n\t\t\topenComments += 1;\n\t\t\tif (comment.author === \"assistant\") {\n\t\t\t\tpending.push({ blockId: block.id, reason: \"assistant-question-open\" });\n\t\t\t}\n\t\t}\n\t}\n\tconst pendingAdditions = record.pendingFeedback?.additions.length ?? 0;\n\treturn {\n\t\tunapproved,\n\t\topenComments,\n\t\tpendingAdditions,\n\t\tallGreen: unapproved === 0 && openComments === 0 && pendingAdditions === 0,\n\t\tpending,\n\t};\n}\n\nfunction nextStep(step: AlignmentStepId): AlignmentStepId | undefined {\n\tconst index = STEP_ORDER.indexOf(step);\n\treturn index >= 0 && index < STEP_ORDER.length - 1 ? STEP_ORDER[index + 1] : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// alignment_set: create / update (总则 3 hash inheritance, 总则 5 comment merge)\n// ---------------------------------------------------------------------------\n\nfunction materializeBlock(\n\tinput: BlockInput,\n\tprevious: AlignmentBlock | undefined,\n\tcurrentRound: number,\n): AlignmentBlock {\n\tconst fingerprint = blockFingerprint(input.kind, input.content, input.rationale, input.impact);\n\tconst unchanged = previous !== undefined && previous.contentHash === fingerprint;\n\tconst block: AlignmentBlock = {\n\t\tid: input.id,\n\t\tkind: input.kind,\n\t\tcontent: input.content,\n\t\trationale: input.rationale,\n\t\timpact: input.impact,\n\t\t// Hash-identical resent blocks keep their approval (防确认疲劳); changed\n\t\t// content resets to pending and flags the round it changed in.\n\t\tstatus: unchanged ? previous.status : \"pending\",\n\t\tcontentHash: fingerprint,\n\t\t...(unchanged ? {} : { modifiedInRound: currentRound + 1 }),\n\t\t...(previous?.draftAnswer !== undefined ? { draftAnswer: previous.draftAnswer } : {}),\n\t\tcomments: [],\n\t};\n\tblock.comments = mergeComments(previous?.comments ?? [], input, currentRound);\n\treturn block;\n}\n\nfunction mergeComments(existing: AlignmentComment[], input: BlockInput, round: number): AlignmentComment[] {\n\tconst merged: AlignmentComment[] = existing.map((comment) => ({ ...comment }));\n\tconst resolutions = new Map((input.commentResolutions ?? []).map((item) => [item.commentId, item]));\n\t// Existing comments are append-only (总则 5): user text survives resends\n\t// verbatim, open assistant questions cannot silently disappear; only an\n\t// explicit resolution closes them.\n\tfor (const comment of merged) {\n\t\tconst resolution = resolutions.get(comment.id);\n\t\tif (!resolution) continue;\n\t\tresolutions.delete(comment.id);\n\t\tif (comment.status === \"resolved\") {\n\t\t\tthrow new AlignmentError(\"comment-ownership\", `comment ${comment.id} is already resolved`);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = { kind: resolution.kind, text: resolution.text, round };\n\t}\n\tif (resolutions.size > 0) {\n\t\tconst ids = [...resolutions.keys()].join(\", \");\n\t\tthrow new AlignmentError(\"comment-ownership\", `resolutions reference unknown comments: ${ids}`);\n\t}\n\t// New comments are assistant-only and deduped by note so a resent open\n\t// question never duplicates itself.\n\tfor (const fresh of input.comments ?? []) {\n\t\tif (merged.some((comment) => comment.author === \"assistant\" && comment.note === fresh.note)) continue;\n\t\tmerged.push({\n\t\t\tid: fresh.id,\n\t\t\tauthor: \"assistant\",\n\t\t\t...(fresh.quote !== undefined ? { quote: fresh.quote } : {}),\n\t\t\tnote: fresh.note,\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\treturn merged;\n}\n\nexport function applyAlignmentSet(state: AlignmentState, input: AlignmentSetInput): EngineRun {\n\tif (!validAlignmentId(input.id)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-id\",\n\t\t\t`alignment id must match [A-Za-z0-9][A-Za-z0-9._-]* (no leading dot): ${input.id}`,\n\t\t);\n\t}\n\tif (!Array.isArray(input.blocks) || input.blocks.length === 0) {\n\t\tthrow new AlignmentError(\"invalid-input\", \"blocks must be a non-empty array\");\n\t}\n\tif (input.blocks.length > ALIGNMENT_LIMITS.blocksPerStepMax) {\n\t\tthrow new AlignmentError(\"invalid-input\", `a step may carry at most ${ALIGNMENT_LIMITS.blocksPerStepMax} blocks`);\n\t}\n\tconst seen = new Set<string>();\n\tfor (const block of input.blocks) {\n\t\tvalidateBlockInput(block);\n\t\tif (seen.has(block.id)) {\n\t\t\tthrow new AlignmentError(\"invalid-block\", `duplicate block id within the step: ${block.id}`);\n\t\t}\n\t\tseen.add(block.id);\n\t}\n\n\tconst existing = findRecord(state, input.id);\n\tif (!existing) {\n\t\t// Create: created-as-aligning (no draft state) and single-active (总则 9).\n\t\tconst active = state.alignments.find((record) => record.status === \"aligning\" || record.status === \"finalizing\");\n\t\tif (active) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"active-alignment-exists\",\n\t\t\t\t`an alignment is already in progress: ${active.id}; complete or cancel it first`,\n\t\t\t);\n\t\t}\n\t\tconst record: AlignmentRecord = {\n\t\t\tid: input.id,\n\t\t\ttitle: input.title ?? input.id,\n\t\t\t...(input.goalId !== undefined ? { goalId: input.goalId } : {}),\n\t\t\tstatus: \"aligning\",\n\t\t\trevision: 0,\n\t\t\tactiveStep: \"objective\",\n\t\t\tsteps: [\n\t\t\t\t{\n\t\t\t\t\tstep: \"objective\",\n\t\t\t\t\tstatus: \"active\",\n\t\t\t\t\tblocks: input.blocks.map((block) => materializeBlock(block, undefined, 0)),\n\t\t\t\t},\n\t\t\t\t{ step: \"detail\", status: \"pending\", blocks: [] },\n\t\t\t\t{ step: \"design\", status: \"pending\", blocks: [] },\n\t\t\t],\n\t\t\troundCount: 0,\n\t\t};\n\t\treturn { state: { alignments: [...state.alignments, record] }, transitions: [] };\n\t}\n\n\tif (existing.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal and cannot be updated: ${input.id}`);\n\t}\n\tconst revision = input.revision ?? 0;\n\tif (revision <= existing.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-revision\",\n\t\t\t`stale alignment revision: ${input.id} expects > ${existing.revision}, got ${revision}`,\n\t\t);\n\t}\n\tconst step = activeStep(existing);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${existing.activeStep} is ${step.status}; only the active step accepts blocks`,\n\t\t);\n\t}\n\t// Deleting a block that still carries open comments is rejected (总则 3):\n\t// deletion must not become a way to dodge unresolved objections.\n\tfor (const previous of step.blocks) {\n\t\tif (input.blocks.some((block) => block.id === previous.id)) continue;\n\t\tif (previous.comments.some((comment) => comment.status === \"open\")) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"block-has-open-comments\",\n\t\t\t\t`block ${previous.id} carries open comments; resolve them before deleting it (edit the content instead)`,\n\t\t\t);\n\t\t}\n\t}\n\t// 已批准块写入防护(v3.7,2026-10-08 dogfood 根因):全量重写会按内容指纹\n\t// 把已确认块静默重置 pending——未显式确认时结构化拒绝,列出将被重置的块。\n\tif (!input.reviseApproved) {\n\t\tconst conflicts = input.blocks\n\t\t\t.filter((blockInput) => {\n\t\t\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\t\t\treturn (\n\t\t\t\t\tprevious?.status === \"approved\" &&\n\t\t\t\t\tprevious.contentHash !==\n\t\t\t\t\t\tblockFingerprint(blockInput.kind, blockInput.content, blockInput.rationale, blockInput.impact)\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map((blockInput) => blockInput.id);\n\t\tif (conflicts.length > 0) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"approved-blocks-conflict\",\n\t\t\t\t`this write would reset approved blocks to pending: ${conflicts.join(\", \")}; resubmit with reviseApproved: true, or keep the approved block content unchanged`,\n\t\t\t);\n\t\t}\n\t}\n\tconst blocks = input.blocks.map((blockInput) => {\n\t\tconst previous = step.blocks.find((candidate) => candidate.id === blockInput.id);\n\t\treturn materializeBlock(blockInput, previous, existing.roundCount);\n\t});\n\t// Version snapshot (走查 2026-10-03): archive the outgoing step block set\n\t// before it is replaced. Append-only, bounded rolling; the page views\n\t// historical versions read-only — only the latest accepts interaction.\n\t// An empty outgoing set has nothing to roll back to — skip it, otherwise\n\t// the version bar fills with chips that render a blank view.\n\tconst archive = JSON.stringify(step.blocks);\n\tconst snapshot: AlignmentVersionSnapshot = {\n\t\trevision: existing.revision,\n\t\tround: existing.roundCount,\n\t\tstep: existing.activeStep,\n\t\tblocks: archive.length <= ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? structuredClone(step.blocks) : [],\n\t\t...(archive.length > ALIGNMENT_LIMITS.versionsSnapshotMaxBytes ? { truncated: true as const } : {}),\n\t};\n\tconst priorVersions = (\n\t\tstep.blocks.length === 0 ? (existing.versions ?? []) : [...(existing.versions ?? []), snapshot]\n\t).slice(-ALIGNMENT_LIMITS.versionsMax);\n\tconst updated: AlignmentRecord = {\n\t\t...existing,\n\t\ttitle: input.title ?? existing.title,\n\t\tgoalId: input.goalId ?? existing.goalId,\n\t\trevision,\n\t\tsteps: existing.steps.map((candidate) =>\n\t\t\tcandidate.step === existing.activeStep ? { ...candidate, blocks } : candidate,\n\t\t),\n\t\tpendingFeedback: undefined,\n\t\tversions: priorVersions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((record) => (record.id === updated.id ? updated : record)) },\n\t\ttransitions: [],\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// POST /round: the user feedback round (§5 round flow, v3.2 audits)\n// ---------------------------------------------------------------------------\n\nexport function applyRoundPost(record0: AlignmentRecord, post: AlignmentRoundPost): RoundPostOutcome {\n\tif (record0.status !== \"aligning\" && record0.status !== \"finalizing\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record0.id}`);\n\t}\n\t// Anti-staleness gate (terminal audit D6): the page must confirm against\n\t// the exact state it rendered.\n\tif (post.baseRevision !== record0.revision) {\n\t\tthrow new AlignmentError(\n\t\t\t\"stale-state\",\n\t\t\t`page snapshot is stale (base ${post.baseRevision}, current ${record0.revision}); reload and resubmit`,\n\t\t);\n\t}\n\tconst hasNewFeedback = post.comments.length > 0 || post.additions.length > 0 || post.answers.length > 0;\n\tif (post.stepConfirmed && hasNewFeedback) {\n\t\tthrow new AlignmentError(\n\t\t\t\"confirmed-with-feedback\",\n\t\t\t\"step confirmation cannot carry new comments/additions/answers; submit the feedback first\",\n\t\t);\n\t}\n\t// 终审阶段唯一合法提交是终审确认(走查 2026-10-03)。此时所有步已锁定,\n\t// 反馈字段没有落点——尤其 approvals 镜像会把已锁定的 design 块整体重置为\n\t// pending,再被无门禁的终审分支带进 completed。显式拒绝,不做静默重置。\n\tif (\n\t\trecord0.status === \"finalizing\" &&\n\t\t(hasNewFeedback || post.commentReplies.length > 0 || post.approvals.length > 0 || !post.stepConfirmed)\n\t) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t\"finalizing accepts only the final confirm (stepConfirmed without feedback fields)\",\n\t\t);\n\t}\n\n\tconst record: AlignmentRecord = structuredClone(record0);\n\tconst step = record.steps.find((candidate) => candidate.step === record.activeStep);\n\tif (!step) throw new AlignmentError(\"invalid-input\", \"active step missing\");\n\tconst blockById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst round = record.roundCount + 1;\n\n\t// New user comments: the quote must match the block's current content.\n\tfor (const comment of post.comments) {\n\t\tconst block = blockById.get(comment.blockId);\n\t\tif (!block) {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `comment targets unknown block: ${comment.blockId}`);\n\t\t}\n\t\tconst note = nonEmptyString(comment.note, \"comment note\", ALIGNMENT_LIMITS.noteMaxLength);\n\t\tif (typeof comment.quote !== \"string\" || !block.content.includes(comment.quote)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"comment-quote-mismatch\",\n\t\t\t\t`comment quote does not match block ${comment.blockId} content`,\n\t\t\t);\n\t\t}\n\t\tblock.comments.push({\n\t\t\tid: `c${round}-${block.comments.length + 1}`,\n\t\t\tauthor: \"user\",\n\t\t\tquote: comment.quote,\n\t\t\tnote,\n\t\t\t...(comment.escalate === true ? { escalate: true } : {}),\n\t\t\tstatus: \"open\",\n\t\t\tcreatedAtRound: round,\n\t\t});\n\t}\n\n\t// Replies to assistant comments (answer/dismiss are mutually exclusive).\n\tfor (const reply of post.commentReplies) {\n\t\tconst block = blockById.get(reply.blockId);\n\t\tconst comment = block?.comments.find((candidate) => candidate.id === reply.commentId);\n\t\tif (!comment || comment.author !== \"assistant\") {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply targets unknown or non-assistant comment: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tif (comment.status !== \"open\") continue; // idempotent resubmission\n\t\tif ((reply.answer !== undefined) === (reply.dismiss === true)) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"invalid-input\",\n\t\t\t\t`reply must carry exactly one of answer or dismiss: ${reply.commentId}`,\n\t\t\t);\n\t\t}\n\t\tcomment.status = \"resolved\";\n\t\tcomment.resolution = reply.dismiss\n\t\t\t? { kind: \"dismissed\", text: \"user marked unimportant\", round }\n\t\t\t: {\n\t\t\t\t\tkind: \"answered\",\n\t\t\t\t\ttext: nonEmptyString(reply.answer ?? \"\", \"reply answer\", ALIGNMENT_LIMITS.answerMaxLength),\n\t\t\t\t\tround,\n\t\t\t\t};\n\t}\n\n\t// Fill-in answers for missing blocks: stored verbatim as the user's words;\n\t// the model restructures them into the block on its next alignment_set.\n\tfor (const answer of post.answers) {\n\t\tconst block = blockById.get(answer.blockId);\n\t\tif (!block || block.kind !== \"missing\") {\n\t\t\tthrow new AlignmentError(\"invalid-input\", `answer targets unknown or non-missing block: ${answer.blockId}`);\n\t\t}\n\t\tif ((block.draftAnswer ?? \"\").trim() !== \"\") continue; // idempotent resubmission\n\t\tblock.draftAnswer = nonEmptyString(answer.value, \"answer\", ALIGNMENT_LIMITS.answerMaxLength);\n\t}\n\n\t// Approvals mirror the page's full approved set for the active step.\n\t// Skipped in finalizing (走查 2026-10-03): the step is locked and the\n\t// final confirm carries approvals=[] — mirroring would reopen its blocks.\n\tif (record0.status !== \"finalizing\") {\n\t\tconst approvalSet = new Set(post.approvals);\n\t\tfor (const id of approvalSet) {\n\t\t\tif (!blockById.has(id)) {\n\t\t\t\tthrow new AlignmentError(\"invalid-input\", `approval references unknown block: ${id}`);\n\t\t\t}\n\t\t}\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = approvalSet.has(block.id) ? \"approved\" : \"pending\";\n\t\t}\n\t}\n\n\t// Additions queue as pendingFeedback — the server-side truth of gate\n\t// condition three (terminal audit D7); cleared by the next alignment_set.\n\tconst additions = [...(record.pendingFeedback?.additions ?? [])];\n\tfor (const addition of post.additions) {\n\t\tadditions.push(nonEmptyString(addition, \"addition\", ALIGNMENT_LIMITS.additionMaxLength));\n\t}\n\trecord.pendingFeedback = additions.length > 0 ? { round, additions } : undefined;\n\n\tconst transitions: AlignmentTransitionEntry[] = [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"round.submitted\",\n\t\t\tround,\n\t\t\treason: {\n\t\t\t\tcode: \"round-submitted\",\n\t\t\t\tdetail: `approvals=${post.approvals.length} comments=${post.comments.length} replies=${post.commentReplies.length} answers=${post.answers.length} additions=${post.additions.length}`,\n\t\t\t},\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n\trecord.roundCount = round;\n\n\tlet stepConfirmedNow = false;\n\tif (post.stepConfirmed && record0.status === \"finalizing\") {\n\t\t// 终审总览确认(走查 2026-10-03): the user reviewed the full plan and\n\t\t// confirmed it — the only path to `completed` + baseline write.\n\t\trecord.status = \"completed\";\n\t\trecord.revision += 1;\n\t\tfor (const locked of record.steps) locked.baselineConfirmedAtRound ??= record.roundCount;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"alignment.completed\",\n\t\t\treason: { code: \"final-review-approved\", detail: \"final review confirmed the complete plan\" },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst result: AlignmentRoundResult = {\n\t\t\talignmentId: record.id,\n\t\t\tround: record.roundCount,\n\t\t\tstep: record.activeStep,\n\t\t\trevision: record.revision,\n\t\t\tapprovals: post.approvals,\n\t\t\tcomments: post.comments,\n\t\t\tcommentReplies: post.commentReplies,\n\t\t\tanswers: post.answers,\n\t\t\tadditions: post.additions,\n\t\t\tstepConfirmed: true,\n\t\t};\n\t\treturn { record, transitions, result };\n\t}\n\tif (post.stepConfirmed) {\n\t\tif (step.blocks.length === 0) {\n\t\t\t// 空环节不可锁定(2026-10-08 走查,防重复确认的机制兜底):AI 尚未写入\n\t\t\t// 块集的新环节 vacuously 全绿,确认它只会让用户在空环节上反复走确认\n\t\t\t// 流程。等待模型 alignment_set 写入后再确认。\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"empty-step\",\n\t\t\t\t`active step \"${record.activeStep}\" has no blocks yet; wait for the model to write them before confirming`,\n\t\t\t);\n\t\t}\n\t\tconst snapshot = gate(record);\n\t\tif (!snapshot.allGreen) {\n\t\t\tthrow new AlignmentError(\n\t\t\t\t\"gate-not-satisfied\",\n\t\t\t\t`gate not satisfied: ${snapshot.unapproved} unapproved blocks, ${snapshot.openComments} open comments, ${snapshot.pendingAdditions} pending additions`,\n\t\t\t);\n\t\t}\n\t\tstep.status = \"locked\";\n\t\tstep.baselineConfirmedAtRound = round;\n\t\trecord.revision += 1;\n\t\ttransitions.push({\n\t\t\talignmentId: record.id,\n\t\t\tstep: record.activeStep,\n\t\t\tevent: \"step.confirmed\",\n\t\t\tround,\n\t\t\treason: { code: \"step-baseline-locked\", detail: `${step.blocks.length} blocks frozen` },\n\t\t\trevision: record.revision,\n\t\t});\n\t\tconst upcoming = nextStep(record.activeStep);\n\t\tif (upcoming) {\n\t\t\trecord.activeStep = upcoming;\n\t\t\tconst upcomingStep = record.steps.find((candidate) => candidate.step === upcoming);\n\t\t\tif (upcomingStep) upcomingStep.status = \"active\";\n\t\t\trecord.revision += 1;\n\t\t} else {\n\t\t\t// Design locked (方案确认): enter the final-review overview instead\n\t\t\t// of completing (走查 2026-10-03) — the baseline is written only\n\t\t\t// after the user confirms the full plan on the 终审 tab.\n\t\t\trecord.status = \"finalizing\";\n\t\t\trecord.revision += 1;\n\t\t\ttransitions.push({\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"design.locked\",\n\t\t\t\tround,\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"scheme-locked-awaiting-final-review\",\n\t\t\t\t\tdetail: \"all three baselines locked; awaiting final review\",\n\t\t\t\t},\n\t\t\t\trevision: record.revision,\n\t\t\t});\n\t\t}\n\t\tstepConfirmedNow = true;\n\t}\n\n\tconst result: AlignmentRoundResult = {\n\t\talignmentId: record.id,\n\t\tround,\n\t\tstep: record0.activeStep,\n\t\trevision: record.revision,\n\t\tapprovals: post.approvals,\n\t\tcomments: post.comments,\n\t\tcommentReplies: post.commentReplies,\n\t\tanswers: post.answers,\n\t\tadditions: post.additions,\n\t\tstepConfirmed: stepConfirmedNow,\n\t};\n\treturn { record, transitions, result };\n}\n\n/** Injection succeeded — the delivered transition closes the round (terminal audit D1). */\nexport function markDelivered(record: AlignmentRecord, round: number): AlignmentTransitionEntry[] {\n\treturn [\n\t\t{\n\t\t\talignmentId: record.id,\n\t\t\tevent: \"round.delivered\",\n\t\t\tround,\n\t\t\treason: { code: \"round-delivered\", detail: \"feedback injected into the session\" },\n\t\t\trevision: record.revision,\n\t\t},\n\t];\n}\n\n// ---------------------------------------------------------------------------\n// alignment_advance: inspect / retreat / cancel (总则 6)\n// ---------------------------------------------------------------------------\n\nexport function applyAlignmentAdvance(state: AlignmentState, input: AdvanceInput): AdvanceOutcome {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (input.action === \"inspect\") return { state, transitions: [] };\n\tif (input.action === \"rollback\") {\n\t\t// Rollback is routed through applyVersionRollback (设计 §6); refusing\n\t\t// here keeps the advance branch from ever treating it as a retreat.\n\t\tthrow new AlignmentError(\"invalid-input\", \"rollback routes through applyVersionRollback, not advance\");\n\t}\n\tif (record.status === \"completed\" || record.status === \"cancelled\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `alignment is terminal: ${record.id}`);\n\t}\n\n\tif (input.action === \"cancel\") {\n\t\tconst cancelled: AlignmentRecord = { ...record, status: \"cancelled\" };\n\t\treturn {\n\t\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? cancelled : item)) },\n\t\t\ttransitions: [\n\t\t\t\t{\n\t\t\t\t\talignmentId: record.id,\n\t\t\t\t\tevent: \"alignment.cancelled\",\n\t\t\t\t\treason: { code: \"alignment-cancelled\" },\n\t\t\t\t\trevision: record.revision,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\t// retreat: comments survive the reset append-only (terminal audit D5); the\n\t// escalate comments that triggered it are resolved here with the note.\n\tconst note = nonEmptyString(input.note ?? \"\", \"retreat note\", ALIGNMENT_LIMITS.noteMaxLength);\n\tconst target = input.step;\n\tif (!target || STEP_ORDER.indexOf(target) >= STEP_ORDER.indexOf(record.activeStep)) {\n\t\tthrow new AlignmentError(\n\t\t\t\"retreat-target-invalid\",\n\t\t\t`retreat target must be an earlier step than ${record.activeStep}`,\n\t\t);\n\t}\n\tconst affected: string[] = [];\n\tconst retreated: AlignmentRecord = { ...record, steps: record.steps.map((step) => ({ ...step })) };\n\tfor (const step of retreated.steps) {\n\t\tif (STEP_ORDER.indexOf(step.step) < STEP_ORDER.indexOf(target)) continue;\n\t\taffected.push(step.step);\n\t\tstep.status = \"reset\";\n\t\tstep.baselineConfirmedAtRound = undefined;\n\t\tfor (const block of step.blocks) {\n\t\t\tblock.status = \"pending\";\n\t\t\tfor (const comment of block.comments) {\n\t\t\t\tif (comment.status === \"open\" && comment.author === \"user\" && comment.escalate === true) {\n\t\t\t\t\tcomment.status = \"resolved\";\n\t\t\t\t\tcomment.resolution = { kind: \"incorporated\", text: note, round: record.roundCount };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tretreated.activeStep = target;\n\t// Finalizing → retreated: the final-review state is gone with the\n\t// downstream resets; the task is writable at the target step again.\n\tif (retreated.status === \"finalizing\") retreated.status = \"aligning\";\n\tretreated.revision += 1;\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === record.id ? retreated : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: record.id,\n\t\t\t\tevent: \"step.retreated\",\n\t\t\t\treason: {\n\t\t\t\t\tcode: \"step-retreated\",\n\t\t\t\t\tdetail: `target=${target}; reset=${affected.join(\",\")}; note=${note}`,\n\t\t\t\t},\n\t\t\t\trevision: retreated.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\nexport interface VersionRollbackInput {\n\talignmentId: string;\n\t/** Target snapshot revision from `record.versions` (must be the active step's). */\n\ttargetRevision: number;\n}\n\n/**\n * Step-scoped version rollback (走查 2026-10-03): drop the current unconfirmed\n * version (not archived), promote the target snapshot to live, engine-bump the\n * revision (D4). Comments survive append-only — restored blocks carry the union\n * of live and archived comments by comment id, live status winning (a\n * resolution is irreversible). Only the active (unlocked) step accepts it;\n * locked steps go through retreat. Other steps are never touched.\n */\nexport function applyVersionRollback(state: AlignmentState, input: VersionRollbackInput): EngineRun {\n\tconst record = findRecord(state, input.alignmentId);\n\tif (!record) {\n\t\tthrow new AlignmentError(\"not-found\", `unknown alignment: ${input.alignmentId}`);\n\t}\n\tif (record.status !== \"aligning\") {\n\t\tthrow new AlignmentError(\"terminal-state\", `version rollback needs an aligning task: ${record.id}`);\n\t}\n\tconst step = activeStep(record);\n\tif (step.status !== \"active\" && step.status !== \"reset\") {\n\t\tthrow new AlignmentError(\n\t\t\t\"step-not-writable\",\n\t\t\t`step ${record.activeStep} is ${step.status}; locked steps go through retreat`,\n\t\t);\n\t}\n\tconst versions = [...(record.versions ?? [])];\n\tconst index = versions.findIndex(\n\t\t(snapshot) => snapshot.revision === input.targetRevision && snapshot.step === record.activeStep,\n\t);\n\tif (index < 0) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`no snapshot of step ${record.activeStep} at revision ${input.targetRevision}`,\n\t\t);\n\t}\n\tconst snapshot = versions[index];\n\tif (snapshot.truncated) {\n\t\tthrow new AlignmentError(\n\t\t\t\"invalid-input\",\n\t\t\t`snapshot r${input.targetRevision} exceeded the size budget and has no blocks`,\n\t\t);\n\t}\n\tconst liveById = new Map(step.blocks.map((block) => [block.id, block]));\n\tconst restored = snapshot.blocks.map((archived) => {\n\t\tconst live = liveById.get(archived.id);\n\t\tif (!live) return structuredClone(archived);\n\t\tconst merged = new Map(archived.comments.map((comment) => [comment.id, comment]));\n\t\tfor (const comment of live.comments) merged.set(comment.id, comment); // live status wins\n\t\treturn { ...structuredClone(archived), comments: [...merged.values()] };\n\t});\n\t// The current version is dropped, the target is promoted: remove it from\n\t// the archive rather than re-archiving the live set.\n\tversions.splice(index, 1);\n\tconst rolled: AlignmentRecord = {\n\t\t...record,\n\t\trevision: record.revision + 1,\n\t\tsteps: record.steps.map((candidate) =>\n\t\t\tcandidate.step === record.activeStep\n\t\t\t\t? { ...candidate, blocks: restored, status: candidate.status === \"locked\" ? \"reset\" : candidate.status }\n\t\t\t\t: candidate,\n\t\t),\n\t\tversions,\n\t};\n\treturn {\n\t\tstate: { alignments: state.alignments.map((item) => (item.id === rolled.id ? rolled : item)) },\n\t\ttransitions: [\n\t\t\t{\n\t\t\t\talignmentId: rolled.id,\n\t\t\t\tstep: rolled.activeStep,\n\t\t\t\tevent: \"version.rolled-back\",\n\t\t\t\treason: { code: \"version-rolled-back\", detail: `target=r${input.targetRevision}` },\n\t\t\t\trevision: rolled.revision,\n\t\t\t},\n\t\t],\n\t};\n}\n\n/** Compact final baseline listing (§5 round flow 4): one line per block, answers first. */\nexport function finalBaselineLines(\n\trecord: AlignmentRecord,\n): { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] | undefined {\n\tif (record.status !== \"completed\") return undefined;\n\tconst lines: { step: AlignmentStepId; kind: AlignmentBlockKind; id: string; line: string }[] = [];\n\tfor (const step of record.steps) {\n\t\tfor (const block of step.blocks) {\n\t\t\tconst confirmed = block.content.split(\"\\n\").find((line) => line.includes(\"→ 已确认\"));\n\t\t\tconst text = (confirmed ? confirmed.replace(/^\\s*→\\s*/, \"\") : block.content.split(\"\\n\")[0]).slice(0, 120);\n\t\t\tlines.push({ step: step.step, kind: block.kind, id: block.id, line: text });\n\t\t}\n\t}\n\treturn lines;\n}\n"]}