From b0bc7a8745bfd6e35ec1eb46c6b5eb7cf19711c9 Mon Sep 17 00:00:00 2001 From: zizi Date: Fri, 14 Aug 2026 10:24:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A1=86=E6=9E=B6:=20=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E6=8C=89=E5=8A=A8=E4=BD=9C-=E5=AF=B9=E8=B1=A1=E9=87=8D?= =?UTF-8?q?=E7=BB=84=20+=20=E5=85=88=E5=AE=A1=E5=90=8E=E5=85=A5=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、技能重组(动作-对象命名) - 旧目录 clean/confirm/continuation/db/detect/embed/… 重组为 clean-book-text/decide-candidate/write-next-chapter/access-database/ check-content-consistency/embed-knowledge/…(git 识别为 rename,内容保持) - agents/*.md、AGENTS.md/CLAUDE.md 收编、example_skill 登记表同步新名 二、先审后入创作闭环(本次核心) 正文接受从"机械门一过就写正典"改为"机械门+语义审查双通过+用户批准+单事务原子提交", DB 级兜底,编排层跳步即被硬拒。 - candidate_cas.py + example_candidate_cas(109):持久化 CAS 状态链 - fact_delta.py + example_fact_delta/example_fact_ledger(106):结构化事实增量, 模型只提六型闭集增量+正文证据引文,仅用户批准的增量随正文同事务入账本 - projection_registry.py + example_projection_run(107):投影登记与恢复 - acceptance_state.py:接受前置实时状态重读 - lesson_registry.py + example_lesson(108):经验升格链,禁止自动升格 - DDL 105:example_candidate 增 semantic_status/semantic_report_sha256 - write_canonical.accept:语义兜底+同事务合并增量+登记投影; run_writer_pipeline/persist_writer_run/run_writer_semantic_detector/step2 接入全链 - claude_runtime:兼容新 CLI modelUsage 信息字段 三、审查修复(独立子代理四维审查后) - 事实增量 propose→approve 翻态正道,不撞唯一键 - 冻结配置探针重刷(CLI 2.1.211→2.1.231 漂移),profileSha256/adapterVersion 再登记 - 可视化合同悬空路径/五六空间矛盾、 SoT 旧技能名漂移、行尾空白清理 测试:离线 65 套 + 真实库集成 5 套(CAS/接受故障注入/事实增量/投影/经验升格)+ 回放 79 项全绿。 创作内容(docs/design、生成正文 artifacts)按"框架与创作分开"未入本提交。 --- .../docs/architecture/domains/02-实体领域.md | 18 +- .../docs/architecture/domains/03-范式领域.md | 8 +- .../architecture/domains/04-上下文领域.md | 4 +- .../architecture/domains/05-创作流程领域.md | 22 +- .../architecture/domains/06-质量与复利领域.md | 16 +- .../domains/07-Agent与Skill领域.md | 21 +- .../domains/08-数据权威与可视化领域.md | 20 +- .agent/docs/architecture/domains/_index.md | 2 +- .agent/docs/architecture/可视化模块合同.md | 59 +- .claude/agents/detector.md | 4 +- .claude/agents/extractor.md | 10 +- .claude/agents/judge.md | 2 +- .claude/agents/planner.md | 16 +- .claude/agents/writer.md | 5 +- .../skills/{db => access-database}/SKILL.md | 30 +- .../{db => access-database}/scripts/db.py | 4 +- .../scripts/seed_schemas.py | 0 .../scripts/sync_agent_registry.py | 66 +- .../test_authorization_snapshot_ddl.py | 0 .../scripts/test_db_params.py | 2 +- .../scripts/test_skill_catalog.py | 55 + .../SKILL.md | 4 +- .../scripts/assemble_writer_context.py | 0 .../scripts/persist_context_freeze.py | 14 +- .../scripts/retrieve_writer_sources.py | 10 +- .../scripts/test_assemble_writer_context.py | 0 .../scripts/test_fine_outline_reader.py | 0 .../scripts/test_fine_outline_unification.py | 0 .../scripts/test_pattern_binding_reader.py | 0 .../scripts/test_retrieve_writer_sources.py | 26 +- .../scripts/test_style_loader.py | 0 .../scripts/test_writer_contract.py | 50 + .../scripts/writer_contract.py | 0 .../{llm => call-content-model}/SKILL.md | 14 +- .../scripts/llm.py | 14 +- .../scripts/test_call_persistence.py | 0 .../scripts/test_quota.py | 0 .../skills/capture-ai-flavor-cases/SKILL.md | 98 + .../references/case-card-contract.md | 55 + .../references/fixtures/README.md | 13 + .../fixtures/backfill-hash-only.yaml | 80 + .../backfill-inventory-2026-08-13.json | 37139 ++++++++++++++++ .../fixtures/canonical-samples.yaml | 56 + .../fixtures/revalidation-2026-08-14.json | 7977 ++++ .../references/fixtures/rule-candidates.yaml | 13 + .../scripts/capture_cases.py | 988 + .../scripts/persist_cases.py | 267 + .../scripts/test_capture_cases.py | 312 + .../SKILL.md | 10 +- .../scripts/check_writer_candidate.py | 2 +- .../scripts/run_writer_semantic_detector.py | 566 +- .../scripts/test_build_semantic_input.py | 80 + .../scripts/test_check_writer_candidate.py | 4 +- .../test_run_writer_semantic_detector.py | 327 +- .../{clean => clean-book-text}/SKILL.md | 22 +- .../scripts/clean_apply.py | 2 +- .../scripts/clean_batch.py | 2 +- .../scripts/clean_detect.py | 8 +- .../scripts/clean_prep.py | 2 +- .../scripts/clean_sweep.py | 2 +- .../scripts/test_clean_detect_offline.py | 0 .../{confirm => decide-candidate}/SKILL.md | 24 +- .../scripts/acceptance_state.py | 136 + .../scripts/check_writer_acceptance.py | 2 +- .../scripts/confirm_knowledge.py | 4 +- .../decide-candidate/scripts/fact_delta.py | 336 + .../scripts/projection_registry.py | 195 + .../scripts/reconcile_work_metrics.py | 2 +- .../scripts/test_confirm_knowledge_offline.py | 0 .../scripts/test_fact_delta.py | 137 + .../scripts/test_fact_delta_db.py | 301 + .../scripts/test_projection_db.py | 264 + .../scripts/test_write_canonical_db.py | 304 + .../scripts/test_writer_acceptance.py | 4 +- .../scripts/write_canonical.py | 81 +- .../{parse-book => deconstruct-book}/SKILL.md | 28 +- .../scripts/parse_export.py | 4 +- .../scripts/parse_health.py | 4 +- .../scripts/parse_ingest.py | 12 +- .../scripts/parse_llm.py | 13 +- .../scripts/parse_outline.py | 6 +- .../scripts/parse_salvage.py | 2 +- .../scripts/test_parse_llm_offline.py | 0 .../scripts/test_parse_outline_offline.py | 0 .../skills/design-story-foundation/SKILL.md | 136 + .../references/candidate-contract.md | 97 + .../references/serial-merge-contract.md | 57 + .../scripts/serial_merge.py | 296 + .../scripts/test_contract.py | 52 + .../scripts/test_serial_merge.py | 93 + .../scripts/test_validate_candidates.py | 112 + .../scripts/validate_candidates.py | 338 + .../{embed => embed-knowledge}/SKILL.md | 20 +- .../scripts/embed_drafts.py | 2 +- .../scripts/test_embed_drafts_offline.py | 0 .../SKILL.md | 64 +- ...r-gate-a-deep-space-card-selectors-v1.json | 0 .../configs/writer-gate-a-deep-space-v1.json | 60 +- .../scripts/fine_outline_detector.py | 0 .../scripts/load_writer_reference_work.py | 227 +- .../scripts/refresh_runtime_probe.py | 8 +- .../scripts/run_replay.py | 8 +- .../scripts/run_writer_replay/__init__.py | 104 +- .../scripts/run_writer_replay/__main__.py | 0 .../scripts/run_writer_replay/_common.py | 100 +- .../run_writer_replay/authorization.py | 0 .../scripts/run_writer_replay/blind.py | 165 +- .../scripts/run_writer_replay/budget.py | 20 +- .../scripts/run_writer_replay/execute.py | 552 +- .../scripts/run_writer_replay/sample.py | 17 +- .../scripts/test_fine_outline_detector.py | 6 +- .../scripts/test_gate_input_builder.py | 296 + .../test_load_writer_reference_work.py | 43 +- .../test_pattern_reference_injection.py | 19 +- .../scripts/test_refresh_runtime_probe.py | 10 +- .../scripts/test_run_replay.py | 2 +- .../scripts/test_run_writer_replay.py | 19 +- .../scripts/test_writer_eval_preregister.py | 0 .../scripts/test_writer_gate.py | 62 +- .../scripts/write_report.py | 0 .../scripts/writer_eval_preregister.py | 0 .claude/skills/execute-claude-task/SKILL.md | 33 + .../scripts/claude_runtime.py | 25 +- .../scripts/test_claude_runtime.py | 48 + .../{expansion => expand-scene}/SKILL.md | 6 +- .../SKILL.md | 6 +- .../scripts/extract_knowledge.py | 10 +- .../scripts/test_extract_knowledge_offline.py | 0 .../skills/extract-work-knowledge/SKILL.md | 52 + .../scripts/test_parse_upgrade_offline.py | 40 +- .../scripts/test_upgrade_work_lock_offline.py | 0 .../scripts/upgrade.py | 25 +- .../scripts/upgrade_work_lock.py | 0 .../{snapshot => freeze-context}/SKILL.md | 8 +- .../scripts/audit_leakage.py | 0 .../scripts/build_snapshot.py | 0 .../scripts/check_snapshot.py | 0 .../scripts/load_reference_work.py | 0 .../scripts/test_audit_leakage.py | 0 .../scripts/test_build_snapshot.py | 0 .../scripts/test_check_snapshot.py | 0 .../scripts/test_load_reference_work.py | 0 .../skills/{import => import-book}/SKILL.md | 16 +- .../scripts/import_novel.py | 2 +- .../scripts/quality_report.py | 2 +- .../skills/maintain-work-extraction/SKILL.md | 78 + .../scripts/backup_upgrade_work.py | 20 +- .../scripts/maintenance.py | 21 + .../scripts/migrate_upgrade_windows.py | 9 +- .../scripts/parse_rewash.py | 0 .../scripts/reset_upgrade_work.py | 12 +- .../test_backup_upgrade_work_offline.py | 0 .../test_reset_upgrade_work_offline.py | 1 + .../SKILL.md | 10 +- .../{fine-outline => plan-chapter}/SKILL.md | 6 +- .../scripts/test_contract.py | 2 +- .../skills/{planning => plan-story}/SKILL.md | 8 +- .../scripts/persist_planning.py | 6 +- .../scripts/record_planning_execution.py | 5 +- .../scripts/repair_deterministic_receipt.py | 5 +- .../scripts/select_patterns.py | 2 +- .../scripts/test_field_coverage.py | 2 +- .../scripts/test_record_planning_execution.py | 0 .../test_repair_deterministic_receipt.py | 0 .../scripts/test_select_patterns_offline.py | 0 .../skills/{polish => polish-prose}/SKILL.md | 4 +- .claude/skills/record-run-evidence/SKILL.md | 42 + .../scripts/file_cas.py | 0 .../scripts/invariant_checks.py | 4 +- .../scripts/persist_llm_call.py | 0 .../scripts/persist_raw.py | 4 +- .../scripts/raw_vault.py | 181 + .../scripts/record_failed_run.py | 2 +- .../scripts/repair_receipt_evidence.py | 2 +- .../scripts/run_registry.py | 2 +- .../scripts/test_file_cas.py | 0 .../scripts/test_persist_raw.py | 0 .../scripts/test_raw_vault.py | 314 + .../scripts/test_record_failed_run.py | 0 .../scripts/test_repair_receipt_evidence.py | 0 .../scripts/test_run_registry.py | 0 .../scripts/test_gate_input_builder.py | 154 - .../SKILL.md | 10 +- .../golden/scores-cal002-m3旧尺子对照.json | 0 .../golden/scores-cal002.json | 0 .../golden/scores.json | 0 .../golden/金标准-主编-2026-07-13.md | 0 .../golden/金标准-番茄作家-2026-07-13.md | 0 .../golden/金标准-起点作家-2026-07-13.md | 0 .../scripts/review_cards.py | 6 +- .../{rewrite => rewrite-selection}/SKILL.md | 6 +- .claude/skills/runtime/SKILL.md | 40 - .../skills/runtime/scripts/test_raw_vault.py | 165 - .../SKILL.md | 32 +- .../scripts/fine_outline_rubric.py | 0 .../scripts/gate_input_builder.py | 200 +- .../scripts/lesson_registry.py | 172 + .../scripts/run_writer_blind_judge.py | 365 +- .../scripts/test_lesson_registry_db.py | 182 + .../scripts/test_rubric.py | 0 .../scripts/test_run_writer_blind_judge.py | 289 +- .../scripts/test_writer_rubric.py | 39 +- .../scripts/writer_gate.py | 49 +- .../scripts/writer_rubric.py | 181 +- .../{search => search-knowledge}/SKILL.md | 14 +- .../scripts/search.py | 10 +- .../scripts/test_search.py | 0 .claude/skills/upgrade/SKILL.md | 97 - .../SKILL.md | 9 +- .../scripts/candidate_cas.py | 142 + .../scripts/persist_writer_run.py | 78 +- .../scripts/run_writer.py | 28 +- .../scripts/run_writer_pipeline.py | 44 +- .../scripts/test_candidate_cas.py | 249 + .../scripts/test_candidate_cas_db.py | 182 + .../scripts/test_persist_writer_run.py | 0 .../scripts/test_run_writer.py | 24 +- .../scripts/test_run_writer_pipeline.py | 43 +- .../scripts/test_semantic_verdict.py | 109 + .gitignore | 2 + AGENTS.md | 32 +- CLAUDE.md | 41 +- dashboard/server.py | 1702 +- dashboard/test_server_display.py | 148 + db/ddl/102-example智能体技能登记.sql | 4 +- db/ddl/104-example-ai-flavor-cases.sql | 142 + db/ddl/105-example候选语义审查.sql | 28 + db/ddl/106-example事实增量.sql | 97 + db/ddl/107-example投影登记.sql | 66 + db/ddl/108-example经验升格.sql | 67 + db/ddl/109-example候选状态CAS.sql | 97 + db/ddl/92-example清洗日志.sql | 2 +- db/表映射.md | 41 +- docs/2026-07-13-M3清洗拆书执行计划.md | 2 +- docs/2026-07-16-升格卡改造设计.md | 8 +- docs/2026-07-16-批9c-收官与优化方案.md | 4 +- .../2026-07-19-回放评测-细纲首跑设计与计划.md | 42 +- ...026-07-24-Gate-A-正文智能体职责重构记录.md | 2 +- docs/2026-07-30-落库与看板设计.md | 2 +- .../plans/2026-07-20-writer-agent-v1.md | 196 +- docs/write-chapter/probe_sandbox.py | 97 + docs/write-chapter/step1_setup_work.py | 115 + docs/write-chapter/step2_setup_chapter2.py | 92 + docs/write-chapter/step2_setup_chapter3.py | 4 +- docs/write-chapter/step2_write_chapter.py | 326 +- .../step2a_retrofit_ch1_context.py | 141 + docs/write-chapter/step2a_write_chapter1.py | 264 + meta/chains/README.md | 30 +- meta/schemas/README.md | 2 +- meta/schemas/fine_outline.yaml | 2 +- meta/schemas/generation_context.yaml | 2 +- 251 files changed, 59398 insertions(+), 1701 deletions(-) rename .claude/skills/{db => access-database}/SKILL.md (50%) rename .claude/skills/{db => access-database}/scripts/db.py (97%) rename .claude/skills/{db => access-database}/scripts/seed_schemas.py (100%) rename .claude/skills/{db => access-database}/scripts/sync_agent_registry.py (58%) rename .claude/skills/{db => access-database}/scripts/test_authorization_snapshot_ddl.py (100%) rename .claude/skills/{db => access-database}/scripts/test_db_params.py (92%) create mode 100644 .claude/skills/access-database/scripts/test_skill_catalog.py rename .claude/skills/{read-context => assemble-context}/SKILL.md (96%) rename .claude/skills/{read-context => assemble-context}/scripts/assemble_writer_context.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/persist_context_freeze.py (87%) rename .claude/skills/{read-context => assemble-context}/scripts/retrieve_writer_sources.py (99%) rename .claude/skills/{read-context => assemble-context}/scripts/test_assemble_writer_context.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/test_fine_outline_reader.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/test_fine_outline_unification.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/test_pattern_binding_reader.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/test_retrieve_writer_sources.py (94%) rename .claude/skills/{read-context => assemble-context}/scripts/test_style_loader.py (100%) rename .claude/skills/{read-context => assemble-context}/scripts/test_writer_contract.py (90%) rename .claude/skills/{read-context => assemble-context}/scripts/writer_contract.py (100%) rename .claude/skills/{llm => call-content-model}/SKILL.md (82%) rename .claude/skills/{llm => call-content-model}/scripts/llm.py (98%) rename .claude/skills/{llm => call-content-model}/scripts/test_call_persistence.py (100%) rename .claude/skills/{llm => call-content-model}/scripts/test_quota.py (100%) create mode 100644 .claude/skills/capture-ai-flavor-cases/SKILL.md create mode 100644 .claude/skills/capture-ai-flavor-cases/references/case-card-contract.md create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/README.md create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-hash-only.yaml create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-inventory-2026-08-13.json create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/canonical-samples.yaml create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/revalidation-2026-08-14.json create mode 100644 .claude/skills/capture-ai-flavor-cases/references/fixtures/rule-candidates.yaml create mode 100644 .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py create mode 100644 .claude/skills/capture-ai-flavor-cases/scripts/persist_cases.py create mode 100644 .claude/skills/capture-ai-flavor-cases/scripts/test_capture_cases.py rename .claude/skills/{detect => check-content-consistency}/SKILL.md (81%) rename .claude/skills/{detect => check-content-consistency}/scripts/check_writer_candidate.py (99%) rename .claude/skills/{detect => check-content-consistency}/scripts/run_writer_semantic_detector.py (56%) create mode 100644 .claude/skills/check-content-consistency/scripts/test_build_semantic_input.py rename .claude/skills/{detect => check-content-consistency}/scripts/test_check_writer_candidate.py (97%) rename .claude/skills/{detect => check-content-consistency}/scripts/test_run_writer_semantic_detector.py (50%) rename .claude/skills/{clean => clean-book-text}/SKILL.md (53%) rename .claude/skills/{clean => clean-book-text}/scripts/clean_apply.py (99%) rename .claude/skills/{clean => clean-book-text}/scripts/clean_batch.py (96%) rename .claude/skills/{clean => clean-book-text}/scripts/clean_detect.py (94%) rename .claude/skills/{clean => clean-book-text}/scripts/clean_prep.py (95%) rename .claude/skills/{clean => clean-book-text}/scripts/clean_sweep.py (98%) rename .claude/skills/{clean => clean-book-text}/scripts/test_clean_detect_offline.py (100%) rename .claude/skills/{confirm => decide-candidate}/SKILL.md (57%) create mode 100644 .claude/skills/decide-candidate/scripts/acceptance_state.py rename .claude/skills/{confirm => decide-candidate}/scripts/check_writer_acceptance.py (99%) rename .claude/skills/{confirm => decide-candidate}/scripts/confirm_knowledge.py (99%) create mode 100644 .claude/skills/decide-candidate/scripts/fact_delta.py create mode 100644 .claude/skills/decide-candidate/scripts/projection_registry.py rename .claude/skills/{confirm => decide-candidate}/scripts/reconcile_work_metrics.py (96%) rename .claude/skills/{confirm => decide-candidate}/scripts/test_confirm_knowledge_offline.py (100%) create mode 100644 .claude/skills/decide-candidate/scripts/test_fact_delta.py create mode 100644 .claude/skills/decide-candidate/scripts/test_fact_delta_db.py create mode 100644 .claude/skills/decide-candidate/scripts/test_projection_db.py create mode 100644 .claude/skills/decide-candidate/scripts/test_write_canonical_db.py rename .claude/skills/{confirm => decide-candidate}/scripts/test_writer_acceptance.py (99%) rename .claude/skills/{confirm => decide-candidate}/scripts/write_canonical.py (72%) rename .claude/skills/{parse-book => deconstruct-book}/SKILL.md (79%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_export.py (98%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_health.py (98%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_ingest.py (99%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_llm.py (98%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_outline.py (98%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/parse_salvage.py (99%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/test_parse_llm_offline.py (100%) rename .claude/skills/{parse-book => deconstruct-book}/scripts/test_parse_outline_offline.py (100%) create mode 100644 .claude/skills/design-story-foundation/SKILL.md create mode 100644 .claude/skills/design-story-foundation/references/candidate-contract.md create mode 100644 .claude/skills/design-story-foundation/references/serial-merge-contract.md create mode 100644 .claude/skills/design-story-foundation/scripts/serial_merge.py create mode 100644 .claude/skills/design-story-foundation/scripts/test_contract.py create mode 100644 .claude/skills/design-story-foundation/scripts/test_serial_merge.py create mode 100644 .claude/skills/design-story-foundation/scripts/test_validate_candidates.py create mode 100644 .claude/skills/design-story-foundation/scripts/validate_candidates.py rename .claude/skills/{embed => embed-knowledge}/SKILL.md (82%) rename .claude/skills/{embed => embed-knowledge}/scripts/embed_drafts.py (99%) rename .claude/skills/{embed => embed-knowledge}/scripts/test_embed_drafts_offline.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/SKILL.md (53%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/configs/writer-gate-a-deep-space-card-selectors-v1.json (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/configs/writer-gate-a-deep-space-v1.json (94%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/fine_outline_detector.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/load_writer_reference_work.py (88%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/refresh_runtime_probe.py (98%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_replay.py (99%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/__init__.py (83%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/__main__.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/_common.py (62%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/authorization.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/blind.py (59%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/budget.py (91%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/execute.py (64%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/run_writer_replay/sample.py (96%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_fine_outline_detector.py (96%) create mode 100644 .claude/skills/evaluate-frozen-replay/scripts/test_gate_input_builder.py rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_load_writer_reference_work.py (97%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_pattern_reference_injection.py (97%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_refresh_runtime_probe.py (99%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_run_replay.py (99%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_run_writer_replay.py (99%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_writer_eval_preregister.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/test_writer_gate.py (92%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/write_report.py (100%) rename .claude/skills/{replay-eval => evaluate-frozen-replay}/scripts/writer_eval_preregister.py (100%) create mode 100644 .claude/skills/execute-claude-task/SKILL.md rename .claude/skills/{runtime => execute-claude-task}/scripts/claude_runtime.py (97%) rename .claude/skills/{runtime => execute-claude-task}/scripts/test_claude_runtime.py (94%) rename .claude/skills/{expansion => expand-scene}/SKILL.md (73%) rename .claude/skills/{extract-knowledge => extract-chapter-knowledge}/SKILL.md (77%) rename .claude/skills/{extract-knowledge => extract-chapter-knowledge}/scripts/extract_knowledge.py (98%) rename .claude/skills/{extract-knowledge => extract-chapter-knowledge}/scripts/test_extract_knowledge_offline.py (100%) create mode 100644 .claude/skills/extract-work-knowledge/SKILL.md rename .claude/skills/{upgrade => extract-work-knowledge}/scripts/test_parse_upgrade_offline.py (99%) rename .claude/skills/{upgrade => extract-work-knowledge}/scripts/test_upgrade_work_lock_offline.py (100%) rename .claude/skills/{upgrade => extract-work-knowledge}/scripts/upgrade.py (99%) rename .claude/skills/{upgrade => extract-work-knowledge}/scripts/upgrade_work_lock.py (100%) rename .claude/skills/{snapshot => freeze-context}/SKILL.md (62%) rename .claude/skills/{snapshot => freeze-context}/scripts/audit_leakage.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/build_snapshot.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/check_snapshot.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/load_reference_work.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/test_audit_leakage.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/test_build_snapshot.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/test_check_snapshot.py (100%) rename .claude/skills/{snapshot => freeze-context}/scripts/test_load_reference_work.py (100%) rename .claude/skills/{import => import-book}/SKILL.md (72%) rename .claude/skills/{import => import-book}/scripts/import_novel.py (99%) rename .claude/skills/{import => import-book}/scripts/quality_report.py (98%) create mode 100644 .claude/skills/maintain-work-extraction/SKILL.md rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/backup_upgrade_work.py (98%) create mode 100644 .claude/skills/maintain-work-extraction/scripts/maintenance.py rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/migrate_upgrade_windows.py (98%) rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/parse_rewash.py (100%) rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/reset_upgrade_work.py (97%) rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/test_backup_upgrade_work_offline.py (100%) rename .claude/skills/{upgrade => maintain-work-extraction}/scripts/test_reset_upgrade_work_offline.py (99%) rename .claude/skills/{eval => optimize-content-quality}/SKILL.md (64%) rename .claude/skills/{fine-outline => plan-chapter}/SKILL.md (93%) rename .claude/skills/{fine-outline => plan-chapter}/scripts/test_contract.py (97%) rename .claude/skills/{planning => plan-story}/SKILL.md (86%) rename .claude/skills/{planning => plan-story}/scripts/persist_planning.py (97%) rename .claude/skills/{planning => plan-story}/scripts/record_planning_execution.py (98%) rename .claude/skills/{planning => plan-story}/scripts/repair_deterministic_receipt.py (97%) rename .claude/skills/{planning => plan-story}/scripts/select_patterns.py (97%) rename .claude/skills/{planning => plan-story}/scripts/test_field_coverage.py (99%) rename .claude/skills/{planning => plan-story}/scripts/test_record_planning_execution.py (100%) rename .claude/skills/{planning => plan-story}/scripts/test_repair_deterministic_receipt.py (100%) rename .claude/skills/{planning => plan-story}/scripts/test_select_patterns_offline.py (100%) rename .claude/skills/{polish => polish-prose}/SKILL.md (86%) create mode 100644 .claude/skills/record-run-evidence/SKILL.md rename .claude/skills/{runtime => record-run-evidence}/scripts/file_cas.py (100%) rename .claude/skills/{runtime => record-run-evidence}/scripts/invariant_checks.py (96%) rename .claude/skills/{runtime => record-run-evidence}/scripts/persist_llm_call.py (100%) rename .claude/skills/{runtime => record-run-evidence}/scripts/persist_raw.py (97%) rename .claude/skills/{runtime => record-run-evidence}/scripts/raw_vault.py (66%) rename .claude/skills/{runtime => record-run-evidence}/scripts/record_failed_run.py (98%) rename .claude/skills/{runtime => record-run-evidence}/scripts/repair_receipt_evidence.py (98%) rename .claude/skills/{runtime => record-run-evidence}/scripts/run_registry.py (98%) rename .claude/skills/{runtime => record-run-evidence}/scripts/test_file_cas.py (100%) rename .claude/skills/{runtime => record-run-evidence}/scripts/test_persist_raw.py (100%) create mode 100644 .claude/skills/record-run-evidence/scripts/test_raw_vault.py rename .claude/skills/{runtime => record-run-evidence}/scripts/test_record_failed_run.py (100%) rename .claude/skills/{runtime => record-run-evidence}/scripts/test_repair_receipt_evidence.py (100%) rename .claude/skills/{runtime => record-run-evidence}/scripts/test_run_registry.py (100%) delete mode 100644 .claude/skills/replay-eval/scripts/test_gate_input_builder.py rename .claude/skills/{review-cards => review-knowledge-cards}/SKILL.md (86%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/scores-cal002-m3旧尺子对照.json (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/scores-cal002.json (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/scores.json (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/金标准-主编-2026-07-13.md (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/金标准-番茄作家-2026-07-13.md (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/golden/金标准-起点作家-2026-07-13.md (100%) rename .claude/skills/{review-cards => review-knowledge-cards}/scripts/review_cards.py (98%) rename .claude/skills/{rewrite => rewrite-selection}/SKILL.md (76%) delete mode 100644 .claude/skills/runtime/SKILL.md delete mode 100644 .claude/skills/runtime/scripts/test_raw_vault.py rename .claude/skills/{quality-gate => score-content-quality}/SKILL.md (56%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/fine_outline_rubric.py (100%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/gate_input_builder.py (82%) create mode 100644 .claude/skills/score-content-quality/scripts/lesson_registry.py rename .claude/skills/{quality-gate => score-content-quality}/scripts/run_writer_blind_judge.py (71%) create mode 100644 .claude/skills/score-content-quality/scripts/test_lesson_registry_db.py rename .claude/skills/{quality-gate => score-content-quality}/scripts/test_rubric.py (100%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/test_run_writer_blind_judge.py (60%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/test_writer_rubric.py (86%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/writer_gate.py (95%) rename .claude/skills/{quality-gate => score-content-quality}/scripts/writer_rubric.py (74%) rename .claude/skills/{search => search-knowledge}/SKILL.md (61%) rename .claude/skills/{search => search-knowledge}/scripts/search.py (96%) rename .claude/skills/{search => search-knowledge}/scripts/test_search.py (100%) delete mode 100644 .claude/skills/upgrade/SKILL.md rename .claude/skills/{continuation => write-next-chapter}/SKILL.md (74%) create mode 100644 .claude/skills/write-next-chapter/scripts/candidate_cas.py rename .claude/skills/{continuation => write-next-chapter}/scripts/persist_writer_run.py (66%) rename .claude/skills/{continuation => write-next-chapter}/scripts/run_writer.py (90%) rename .claude/skills/{continuation => write-next-chapter}/scripts/run_writer_pipeline.py (92%) create mode 100644 .claude/skills/write-next-chapter/scripts/test_candidate_cas.py create mode 100644 .claude/skills/write-next-chapter/scripts/test_candidate_cas_db.py rename .claude/skills/{continuation => write-next-chapter}/scripts/test_persist_writer_run.py (100%) rename .claude/skills/{continuation => write-next-chapter}/scripts/test_run_writer.py (92%) rename .claude/skills/{continuation => write-next-chapter}/scripts/test_run_writer_pipeline.py (85%) create mode 100644 .claude/skills/write-next-chapter/scripts/test_semantic_verdict.py create mode 100644 dashboard/test_server_display.py create mode 100644 db/ddl/104-example-ai-flavor-cases.sql create mode 100644 db/ddl/105-example候选语义审查.sql create mode 100644 db/ddl/106-example事实增量.sql create mode 100644 db/ddl/107-example投影登记.sql create mode 100644 db/ddl/108-example经验升格.sql create mode 100644 db/ddl/109-example候选状态CAS.sql create mode 100644 docs/write-chapter/probe_sandbox.py create mode 100644 docs/write-chapter/step1_setup_work.py create mode 100644 docs/write-chapter/step2_setup_chapter2.py create mode 100644 docs/write-chapter/step2a_retrofit_ch1_context.py create mode 100644 docs/write-chapter/step2a_write_chapter1.py diff --git a/.agent/docs/architecture/domains/02-实体领域.md b/.agent/docs/architecture/domains/02-实体领域.md index fd1644d..8b9fd37 100644 --- a/.agent/docs/architecture/domains/02-实体领域.md +++ b/.agent/docs/architecture/domains/02-实体领域.md @@ -46,12 +46,14 @@ - 来源要能回到不可变位置和内容哈希,不能只写“某章某段”。 - 会随剧情变化的事实必须带生效章区间,不能用终态覆盖历史时点。 +> 边界说明:正文提交时随事务登记的**事实增量账本**(`example_fact_delta` / `example_fact_ledger`,见 [08-数据权威](08-数据权威与可视化领域.md) §10)记录的是"哪次提交批准了哪条类型化变更"的变更流证据,绑正文块 revision;**实体当前事实**(人物位置、关系、知情范围等的现行态)的 owner 仍是本领域。增量账本不是实体事实的第二事实源,二者是"变更流水"与"现行状态"的分工。 + ## 4. 实体与作品关系 - 作品侧只引用实体 `id`,不复制实体的详细字段。 - 作品级设定可以定义创作边界和主题,但不得成为人物、地点、事件的第二事实源。 - 正文保存或候选接受后可以产生实体草稿;**草稿不进入后续正式上下文**。 -- 草稿到正式是一次**库内状态流转**:由用户确认(`confirm`)把草稿从 `pending` 翻成 `confirmed`,并落出正式表 `active` 行、`revision` 加一。没有用户确认,草稿不会自己变正式。 +- 草稿到正式是一次**库内状态流转**:由用户确认(`decide-candidate`)把草稿从 `pending` 翻成 `confirmed`,并落出正式表 `active` 行、`revision` 加一。没有用户确认,草稿不会自己变正式。 - 实体的演进历史由库与 Git 共同留痕(库留当前合同与必要时态区间,Git 留 DDL、代码与变更历史),不在实体行里另写一份不可校验的历史叙述副本。 ## 5. 拆书与知识草稿入口 @@ -74,7 +76,7 @@ - **灾备导出**:正式层可导出,配合数据库备份用于恢复。 - 参考作品的出处与导入信息以 `example_reference_work` / `muse_knowledge_document` 为准(领域索引 §9:不做多租户授权机制,96 授权快照不启用)。 -> 注意区分两个“升格”:本节是**作品面实体升格**——把参考书的人物/关系抽进库的正式层。06 质量与复利领域的“经验升格”是另一件事——把写法经验从写法→范式→Skill 逐级固化。两者只是都叫“升格”,对象和去向完全不同,不要混为一谈。作品面升格对应实现:upgrade skill 加 `db/ddl/94-example作品面升格.sql`。 +> 注意区分两个“升格”:本节是**作品面实体升格**——把参考书的人物/关系抽进库的正式层。06 质量与复利领域的“经验升格”是另一件事——把写法经验从写法→范式→Skill 逐级固化。两者只是都叫“升格”,对象和去向完全不同,不要混为一谈。作品面升格对应实现:`extract-work-knowledge`、`maintain-work-extraction` 加 `db/ddl/94-example作品面升格.sql`。 ## 7. 关系与状态 @@ -113,7 +115,7 @@ - 义务对象:`meta/schemas/` 登记了 演变历程 的六个型——`character` / `event` / `faction` / `location` / `item` / `power_system`。未登记 演变历程 的型(如 `narrative_state`、`character_relation`)不受此义务约束。 - 闭环定义:一条设定的“已发生台阶(演变历程)”与“未来计划”合起来必须覆盖 登场 → … → 结局。即有明确的 登场 台阶,并有声明的 结局 方向——未来记在该型的未来计划字段,已发生的结局记在 演变历程(周期=结局)。“有终”指计划明写这条设定如何收场(角色的结局、力量体系的退场或湮灭、物品的归属),不是只有成长台阶。 - 机械执行时点: - - **入库硬门禁**:设定草稿首次经 `confirm` 成为正式行时(draft→canonical 确认路径,归 `confirm` skill),校验计划弧线有始有终,缺一则确认失败、不落正式行。机械失败不可被 Agent 主观覆判(与 [06-质量与复利领域](06-质量与复利领域.md) 质量链一致)。弧线的合法变更只走用户确认的正式修订(`revision` 加一)或带审计的 `retired`(§3 三态),这两条不是绕过门禁。 + - **入库硬门禁**:设定草稿首次经 `decide-candidate` 成为正式行时(draft→canonical 确认路径),校验计划弧线有始有终,缺一则确认失败、不落正式行。机械失败不可被 Agent 主观覆判(与 [06-质量与复利领域](06-质量与复利领域.md) 质量链一致)。弧线的合法变更只走用户确认的正式修订(`revision` 加一)或带审计的 `retired`(§3 三态),这两条不是绕过门禁。 - **完本硬门禁**:作品状态转 完本(`completed` 流转归 [01-作品领域](01-作品领域.md))前,校验所有义务设定已在 演变历程 落到 结局(实现收口)或经 `retired` 带审计;存在 未收口 则不得转 完本。 - **常设不变量**:台账(②)持续运行,把 未被碰/未收口 项曝为创作健康度项(与 06 长期验证轴一致),违反即报警。 @@ -144,10 +146,10 @@ ### 后果 -- `confirm` 路径新增一项入库门禁:设定首次确认必须带 有始有终 的计划弧线,否则不落正式行。 +- `decide-candidate` 路径新增一项入库门禁:设定首次确认必须带 有始有终 的计划弧线,否则不落正式行。 - 作品新增一个全书常设视图(台账),由 08 只读看板渲染;完本新增一项实现收口硬门禁(流转归 01 作品领域,校验归本领域)。 - 演变历程的消费语义不变:续写仍不给 演变历程 全线(专题-07 §5 与其验收条款 6);台账是全书健康度视图,不改逐章上下文注入。 -- 待建:`confirm` 的计划弧线门禁、台账视图、完本收口门禁均未建成;建成前闭环不被机械校验(登记于本文件 附·待建)。 +- 待建:`decide-candidate` 的计划弧线门禁、台账视图、完本收口门禁均未建成;建成前闭环不被机械校验(登记于本文件 附·待建)。 ## 9. 检索 @@ -164,7 +166,7 @@ 2. 同一事实只有一个 owner 行,不要求同时维护 Markdown 与 JSON 两份人工事实。 3. 按目标章冻结上下文时,不会读取该章之后才成立的状态。 4. 实体草稿不会静默进入正式创作上下文(可机械验证:未被用户确认的草稿不出现在冻结的正式上下文里)。 -5. 参考书导入产生的实体草稿与正文产生的实体草稿走同一条确认链(可机械验证:两类草稿的确认都经 `confirm` 触发、都产生正式表行)。 +5. 参考书导入产生的实体草稿与正文产生的实体草稿走同一条确认链(可机械验证:两类草稿的确认都经 `decide-candidate` 触发、都产生正式表行)。 6. 向量召回命中后回读的是库行而非文件(可机械验证:命中结果带库行 id 与内容哈希,且哈希与库一致)。 7. 作品面入库管线的每一次覆写都有审计记录,可追到来源原文。 8. 义务型设定草稿首次确认时,计划弧线缺 登场 或 结局 方向则不落正式行(可机械验证:任一义务型正式设定行都能回读出含 登场 台阶与 结局 方向的弧线)。 @@ -189,7 +191,7 @@ 可承认的现有实现(已建成,引用即可): - 拆书与作品面实体入库管线(抽取、别名判重、跨窗留档、覆写审计)。 -- review-cards 三角色审核(番茄作家 / 起点作家 / 主编)作为拆书常设步骤。 +- review-knowledge-cards 三角色审核(番茄作家 / 起点作家 / 主编)作为拆书常设步骤。 - `aiContext` 字段级用途裁剪(按用途只取需要的字段)。 - 参考作品授权快照表(`db/ddl/96`):**决定不启用**(领域索引 §9),DDL 留存不 apply。 @@ -198,6 +200,6 @@ - 草稿→确认的后半段真正跑通(当前正式表、绑定表基本为空,卡多在 `pending`)。 - 稳定 `id` 不因改名变化的保证。 - `retired` 退役态。 -- 设定全书闭环与台账(§8):`confirm` 计划弧线入库门禁、全书设定台账视图、完本实现收口门禁;建成前闭环不被机械校验。 +- 设定全书闭环与台账(§8):`decide-candidate` 计划弧线入库门禁、全书设定台账视图、完本实现收口门禁;建成前闭环不被机械校验。 - 五个义务型(`event`/`faction`/`location`/`item`/`power_system`)的未来计划字段补齐(字段合同归 `meta/schemas/`,见 §8 现状标注)。 - 删库可恢复(见 08)。 diff --git a/.agent/docs/architecture/domains/03-范式领域.md b/.agent/docs/architecture/domains/03-范式领域.md index 62ae6db..43cacc3 100644 --- a/.agent/docs/architecture/domains/03-范式领域.md +++ b/.agent/docs/architecture/domains/03-范式领域.md @@ -48,7 +48,7 @@ observation -> draft -> evaluating -> active -> retired 单章、单作品或一次高分不能直接产生公共 `active`。升格必须说明样本范围、场景选择偏差和替代解释。 -现有实现锚点:公共范式卡由 `parse-book` 窗级聚类出卡,经 `review-cards` 三角色审核(番茄作家 / 起点作家 / 主编)判 pass / revise / reject 后写回卡里,再由用户确认落为正式内容。 +现有实现锚点:公共范式卡由 `deconstruct-book` 窗级聚类出卡,经 `review-knowledge-cards` 三角色审核(番茄作家 / 起点作家 / 主编)判 pass / revise / reject 后写回卡里,再由用户确认落为正式内容。 ## 5. 消费合同 @@ -79,7 +79,7 @@ observation -> draft -> evaluating -> active -> retired 1. **绑定对象与形态**:`pattern_bindings` 承载本作已确认绑定的范式引用集合,每条绑定 = 范式卡库内 `sourceId` + 选定时的版本 / 内容哈希快照。绑定可指公共范式卡(`work_id = 0`,已确认 / `active` 态),也可指作品级范式(非零 `work_id`)。哈希快照冻结绑定语义:范式卡后续被修订不静默改变已绑定内容,是否换绑由用户重新决策。 2. **SoT↔库不一致与补齐方向**:`pattern_bindings` 已是作品行 SoT 合同字段([01-作品领域 §3](01-作品领域.md)),但 `muse_content_work` 尚无承载列——这是 SoT 先于库的债。本领域只定范式侧语义;库表承载位与落库机制归 [01-作品领域](01-作品领域.md) 与 [08-数据权威与可视化领域](08-数据权威与可视化领域.md),不在此写 DDL。补齐方向:在作品行增结构化承载位,存已确认绑定的范式引用集合;承载位建成前,绑定事实必须有临时落库位,不得只活在内存或文件,否则违反“一切落库”横切合同([领域索引 §3](_index.md))。 - - **实验仓落地承载**(遵循 `db/ddl` 「主仓表原样不改列、实验扩展进 example_*」口径):承载位 = 最新一条已确认 assembly 规划行(`example_planning_section`,`section_type=assembly`,`state=confirmed`)的 `patternReferences`;确认 assembly 即激活绑定,不另改 `muse_content_work`。消费侧由 read-context `load_confirmed_pattern_bindings` 只读该已确认绑定(写作期不临场召回)。作品行 `pattern_bindings` 列仍是主仓生产承载方向(归 01/08),届时实验仓承载收敛回作品行。 + - **实验仓落地承载**(遵循 `db/ddl` 「主仓表原样不改列、实验扩展进 example_*」口径):承载位 = 最新一条已确认 assembly 规划行(`example_planning_section`,`section_type=assembly`,`state=confirmed`)的 `patternReferences`;确认 assembly 即激活绑定,不另改 `muse_content_work`。消费侧由 assemble-context `load_confirmed_pattern_bindings` 只读该已确认绑定(写作期不临场召回)。作品行 `pattern_bindings` 列仍是主仓生产承载方向(归 01/08),届时实验仓承载收敛回作品行。 3. **生效时机(Shadow→confirmed)**:规划期 `select_patterns` 从公共范式卡选定本作范式,选择先落 Shadow(规划候选);经用户确认翻 confirmed 后才写入 `pattern_bindings`、才可进生成上下文(横切总闸见 [架构-02](../../../../../design-docs/架构-02-核心数据结构与双轨模型.md))。未经确认的 Shadow 选择不是绑定,不得被写作期消费。 @@ -110,7 +110,7 @@ observation -> draft -> evaluating -> active -> retired 范式的选择靠库内检索:按型别、适用范围、场景和意图从库里过滤候选;库内检索加速(pgvector 向量索引)只负责提高召回速度,不是独立权威,可从库重建。任何命中都必须回读库行并校验内容哈希后才能采用。 - 检索加速失败只影响速度,不改变范式状态语义、不跳过审核。 -- 只读看板查库渲染范式,绝不写库;接受、丢弃等写操作仍由 `confirm` skill 和主会话走。 +- 只读看板查库渲染范式,绝不写库;接受、丢弃等写操作仍由 `decide-candidate` Skill 和主会话走。 - 可恢复性(库备份、快照、可重建脚本)见 [08-数据权威与可视化领域](08-数据权威与可视化领域.md)。 ## 9. 验收条件 @@ -129,7 +129,7 @@ observation -> draft -> evaluating -> active -> retired - 五态生命周期(`evaluating` / `active` / `retired`)的机械判据。 - scope 的 category(品类)层:现状只有公共 / 作品两层,品类层为目标。 -- 规划期绑定的合同已定(见 §6);消费侧取数端(read-context `load_confirmed_pattern_bindings`)与生产编排接线已建(实验仓以已确认 assembly 行承载)。剩余待建只是落地:`muse_content_work` 的 `pattern_bindings` 承载列(库表承载归 [01-作品领域](01-作品领域.md),届时实验仓承载收敛回作品行)、绑定确认门禁、写作期范式须可回指 confirmed 绑定的机械门禁(尚未强制)。 +- 规划期绑定的合同已定(见 §6);消费侧取数端(assemble-context `load_confirmed_pattern_bindings`)与生产编排接线已建(实验仓以已确认 assembly 行承载)。剩余待建只是落地:`muse_content_work` 的 `pattern_bindings` 承载列(库表承载归 [01-作品领域](01-作品领域.md),届时实验仓承载收敛回作品行)、绑定确认门禁、写作期范式须可回指 confirmed 绑定的机械门禁(尚未强制)。 ## 11. 关联 SoT diff --git a/.agent/docs/architecture/domains/04-上下文领域.md b/.agent/docs/architecture/domains/04-上下文领域.md index ca73173..36a138f 100644 --- a/.agent/docs/architecture/domains/04-上下文领域.md +++ b/.agent/docs/architecture/domains/04-上下文领域.md @@ -44,7 +44,7 @@ ## 4. 数据库读取器(核心实现) -数据库读取器是本领域的核心实现,对齐 [`read-context`](../../../../.claude/skills/read-context/SKILL.md) 现状:从库读可信来源、冻结、再投影。它必须能够: +数据库读取器是本领域的核心实现,对齐 [`assemble-context`](../../../../.claude/skills/assemble-context/SKILL.md) 现状:从库读可信来源、冻结、再投影。它必须能够: 1. 从库中枚举作品、实体和范式,按 ID、类型、名称、别名、scope、scenario 和章号过滤。 2. 解析库内 schema 并确认来源可溯(来源能回到已导入参考书或作品自身正式内容);不做多租户授权快照重验(96 不启用,领域索引 §9),确认每条来源可被本次任务合法读取。 @@ -84,5 +84,5 @@ ReAct Agent 可以调用上下文 Skill 请求补充证据,但不能自行访 - 内容来源:[01-作品领域](01-作品领域.md)、[02-实体领域](02-实体领域.md)、[03-范式领域](03-范式领域.md) - 数据权威与 raw:[08-数据权威与可视化领域](08-数据权威与可视化领域.md) -- 当前 Skill:[`read-context`](../../../../.claude/skills/read-context/SKILL.md) +- 当前 Skill:[`assemble-context`](../../../../.claude/skills/assemble-context/SKILL.md) - 上级上下文合同:[专题-03](../../../../../design-docs/专题-03-AI编排上下文与质量评测实现规范.md) diff --git a/.agent/docs/architecture/domains/05-创作流程领域.md b/.agent/docs/architecture/domains/05-创作流程领域.md index 5d9bee3..e69cbfd 100644 --- a/.agent/docs/architecture/domains/05-创作流程领域.md +++ b/.agent/docs/architecture/domains/05-创作流程领域.md @@ -54,21 +54,21 @@ - 规划期 `select_patterns` 从公共范式卡(`work_id=0`)选定本作范式,绑定 `pattern_bindings`;范式内容与消费尺寸合同见 [03-范式领域](03-范式领域.md)。 - 装配 `assembly`:把已选范式与本作事实组织为待装配集合,落 `section_type='assembly'`(装配此前无领域 owner,本节收编,见下"归属收编")。 - `style` 文风画像八字段(叙事人称视角 / 句式 / 叙述配比 / 用词质感 / AI 味黑名单 / 对话风格 / 章末钩子风格 / 达标样张,字段权威见 `meta/schemas/style.yaml`)。 -- 机械门禁:**`assembly` 经用户确认(`confirmed`)才进正文上下文**、**`assemble` 只消费已绑定范式**(对齐 [专题-07](../../../../../design-docs/专题-07-知识消费契约与质量闭环.md):公共范式只走规划期决策、写作期引用,不作写作期临场海选)、**`style` 真注入 writer**——**取数端与生产接线已建**:read-context 三个一等取数端(`load_confirmed_fine_outline` / `load_confirmed_pattern_bindings` / `load_confirmed_style`)已建并由生产编排(`docs/write-chapter/step2_write_chapter.py`)接线;范式只读已确认 assembly 绑定注入(实验仓承载,见 [03-范式领域 §6](03-范式领域.md)),确认文风投影为 `styleConstraints` 随冻结上下文注入 writer(不再写死为空)。**待建**:当前注入的是设定行的一句话文风(书12 现状),结构化 `style` 八字段画像的书级抽取尚未建;写作期范式须可回指 confirmed 绑定的门禁尚未机械强制。评测 A/B/C 臂走独立冻结注入,不读生产绑定。 +- 机械门禁:**`assembly` 经用户确认(`confirmed`)才进正文上下文**、**`assemble` 只消费已绑定范式**(对齐 [专题-07](../../../../../design-docs/专题-07-知识消费契约与质量闭环.md):公共范式只走规划期决策、写作期引用,不作写作期临场海选)、**`style` 真注入 writer**——**取数端与生产接线已建**:assemble-context 三个一等取数端(`load_confirmed_fine_outline` / `load_confirmed_pattern_bindings` / `load_confirmed_style`)已建并由生产编排(`docs/write-chapter/step2_write_chapter.py`)接线;范式只读已确认 assembly 绑定注入(实验仓承载,见 [03-范式领域 §6](03-范式领域.md)),确认文风投影为 `styleConstraints` 随冻结上下文注入 writer(不再写死为空)。**待建**:当前注入的是设定行的一句话文风(书12 现状),结构化 `style` 八字段画像的书级抽取尚未建;写作期范式须可回指 confirmed 绑定的门禁尚未机械强制。评测 A/B/C 臂走独立冻结注入,不读生产绑定。 - 退出条件:`assembly` 已 `confirmed` 并完成 `pattern_bindings` 绑定,`style` 八字段齐备。 **阶段 4 · 细纲** - 进入条件:阶段 3 已确认,且当前卷大纲已确认。 -- 产出:逐章 `fine_outline`(章细纲 ≈ 章正文 3–5%,是结构骨架不是缩写,超比例退回),落 `section_type='fine_outline'` 且必须带 `target_chapter`。细纲字段合同见 [`meta/schemas/`](../../../../meta/schemas/README.md) 与 [fine-outline 合同](../../../../.claude/skills/fine-outline/SKILL.md)。 +- 产出:逐章 `fine_outline`(章细纲 ≈ 章正文 3–5%,是结构骨架不是缩写,超比例退回),落 `section_type='fine_outline'` 且必须带 `target_chapter`。细纲字段合同见 [`meta/schemas/`](../../../../meta/schemas/README.md) 与 [细纲合同(plan-chapter)](../../../../.claude/skills/plan-chapter/SKILL.md)。 - 机械门禁: - **硬门禁(既有,代码失败关闭)**:`section_type='fine_outline'` 必带 `target_chapter`、`payload` 为非空 JSON、细纲须为结构化对象且数组/字符串字段类型稳定(落库即拒)。 - - **内容门禁(文档纪律,待升级)**:硬事件 / 伏笔动作(埋 · 推 · 收)/ 必须出场实体 / 章末钩子齐备;**细纲硬约束覆盖率 100%**(回放评测维度,见 [专题-04](../../../../../design-docs/专题-04-生成质量门控与创作健康度设计方案.md));**细纲产出形与 writer 装配消费形统一——已建**:唯一字段权威 [`meta/schemas/fine_outline.yaml`](../../../../meta/schemas/fine_outline.yaml)(必填集满足装配与机械门、推荐集保留规划表达力),planning 技能与 writer 装配同指它,结束此前两套字段不相交的漂移。 + - **内容门禁(文档纪律,待升级)**:硬事件 / 伏笔动作(埋 · 推 · 收)/ 必须出场实体 / 章末钩子齐备;**细纲硬约束覆盖率 100%**(回放评测维度,见 [专题-04](../../../../../design-docs/专题-04-生成质量门控与创作健康度设计方案.md));**细纲产出形与 writer 装配消费形统一——已建**:唯一字段权威 [`meta/schemas/fine_outline.yaml`](../../../../meta/schemas/fine_outline.yaml)(必填集满足装配与机械门、推荐集保留规划表达力),`plan-chapter` 与 writer 装配同指它,结束此前两套字段不相交的漂移。 - 退出条件:该章细纲 `confirmed`,硬约束覆盖率达标,产出形可被 writer 装配直接消费。 ### 五阶段共用的落库机械门禁(既有,代码失败关闭) -这四条由 `persist_planning` / `confirm` 在库侧强制,不靠调用方自觉: +这四条由 `persist_planning` / `decide-candidate` 在库侧强制,不靠调用方自觉: 1. `section_type` 只接受 `setting / outline / state / assembly / fine_outline`,非法值拒落。 2. `fine_outline` 缺 `target_chapter` 拒落。 @@ -88,7 +88,7 @@ ### 现状与待建 -- **已建成(引用即可)**:五阶段顺序与产出落点、`example_planning_section` 的 `section_type` 白名单与 `fine_outline` 必带章号、`shadow → confirmed` 单通道、公共范式卡与 `select_patterns`;以及本轮落地的——细纲唯一字段合同(`meta/schemas/fine_outline.yaml`,产出形与装配消费形统一)、落库字段覆盖门禁(`fine_outline` 型已强制失败关闭)、read-context 三个一等取数端(`load_confirmed_fine_outline` / `load_confirmed_pattern_bindings` / `load_confirmed_style`)并由生产编排 `step2_write_chapter.py` 接线(细纲统一消费、范式只读已确认 assembly 绑定、文风投影为 `styleConstraints` 注入 writer)。 +- **已建成(引用即可)**:五阶段顺序与产出落点、`example_planning_section` 的 `section_type` 白名单与 `fine_outline` 必带章号、`shadow → confirmed` 单通道、公共范式卡与 `select_patterns`;以及本轮落地的——细纲唯一字段合同(`meta/schemas/fine_outline.yaml`,产出形与装配消费形统一)、落库字段覆盖门禁(`fine_outline` 型已强制失败关闭)、assemble-context 三个一等取数端(`load_confirmed_fine_outline` / `load_confirmed_pattern_bindings` / `load_confirmed_style`)并由生产编排 `step2_write_chapter.py` 接线(细纲统一消费、范式只读已确认 assembly 绑定、文风投影为 `styleConstraints` 注入 writer)。 - **决策已定、机械落地待建(三个架构空白)**:合同见 02/01/03 三域决策记录—— 1. 设定全书闭环校验 + 全书设定台账(把「演变历程」从只追加日志升级为闭环义务,新增设定 × 章消费矩阵视图;见 [02-实体领域 §8](02-实体领域.md))。 2. 卷数合同:`novel_work` 篇幅目标增「分卷数」,`outline` 分卷粗纲卷数须与之一致并机械校验(见 [01-作品领域 §6](01-作品领域.md))。 @@ -145,7 +145,7 @@ DRAFT/CHECKING/PASSED -> DISCARDED(用户明确决策) - `PASSED` 只表示候选可展示、可进入接受前置校验,不表示已成为正式正文。 - 诊断和评测候选固定不可接受(四层机械强制:资格由 run 类型机械派生、合同硬校验拒绝、接受入口硬拒、产出钉死评测区)。 -> **现状标注**:当前实现只有内存四态(DRAFT / CHECKING / PASSED / REJECTED);ACCEPTED、DISCARDED、ARCHIVED 三态与持久化到候选表为目标合同,待建。 +> **现状标注**:候选状态已持久化。生产运行级 CAS 链落 `example_candidate_cas`(`PostgresCasStateStore`,一次运行一条链:DRAFT / CHECKING / PASSED / REJECTED,revision 单调 +1,DB 触发器锁方向闭集与身份不可变),生产编排 `step2_write_chapter.py` 已接它。候选表 `example_candidate` 承载业务态(含 accepted / discarded),接受/丢弃经 `write_canonical` 单通道翻态。ARCHIVED 态仍未启用。 ## 4. 用户决策 @@ -181,11 +181,11 @@ DRAFT/CHECKING/PASSED -> DISCARDED(用户明确决策) - 任一步失败必须进入明确终态,不留下无法判断是否可接受的处理中候选。 - 一切输入产出落库(见索引 §3):用户意图、冻结上下文、候选正文、检测与评分、决策、运行回执、补证与重写记录。 -> **待建(目标合同)**: -> - 写库写入层:当前管线只读不写,正式内容变更仍靠人工 commit;目标是管线直接原子写库。 -> - 状态机持久化与三态(ACCEPTED / DISCARDED / ARCHIVED)落候选表。 -> - 生产链上的质量评分环节:当前盲评只接离线评测,生产链尚无评分落库。 -> - 生成实体/范式草稿的触发接线:当前未接通。 +> **现状与待建**: +> - 写库写入层:**已建**——`write_canonical.accept` 单事务写正文块(revision CAS)+ 来源归因 + 命令幂等 + 决策归档 + 候选翻态,任一失败整体回滚;DB 级兜底复检 `run_type=production`、`state=passed`、`semantic_status=passed`(先审后入,语义未过不得接受)。 +> - 状态机持久化:**已建**——运行级 CAS 链 `example_candidate_cas`,业务态落 `example_candidate`;ARCHIVED 态未启用。 +> - 结构化事实增量:**已建**——`example_fact_delta`(提案)+ `example_fact_ledger`(正典账本);模型只提六型闭集增量且必须带正文证据引文,只有用户批准的增量随正文同事务入账本,抽取结果不自动升格。 +> - 待建:生产链上的质量评分环节(盲评仍只接离线评测);补证重组装在语义缺口下的自动接线(当前缺口失败关闭);章后抽取的异步执行(接受时只登记 pending 投影)。 ## 7. 开发评测边界 diff --git a/.agent/docs/architecture/domains/06-质量与复利领域.md b/.agent/docs/architecture/domains/06-质量与复利领域.md index fb6e0db..1c82771 100644 --- a/.agent/docs/architecture/domains/06-质量与复利领域.md +++ b/.agent/docs/architecture/domains/06-质量与复利领域.md @@ -33,7 +33,7 @@ 每章不能自创完全不同的量表,否则失去跨章比较;也不能用一套固定权重覆盖所有场景。量表、场景策略和阈值必须版本化——改了评分口径,要能读出“这次用的是哪一版量表”。 -现有实现承认(各一句,不展开):六类混淆项报告与新角色比例分层裁决由 [`eval`](../../../../.claude/skills) 质量收敛环承载;卡三角色审核与金标准校准由 [`review-cards`](../../../../.claude/skills) 承载。 +现有实现承认(各一句,不展开):六类混淆项报告与新角色比例分层裁决由 [`optimize-content-quality`](../../../../.claude/skills/optimize-content-quality/SKILL.md) 质量收敛环承载;卡三角色审核与金标准校准由 [`review-knowledge-cards`](../../../../.claude/skills/review-knowledge-cards/SKILL.md) 承载;AI 味案例的来源哈希、Shadow 捕获、来源重验证和反例前置门由 [`capture-ai-flavor-cases`](../../../../.claude/skills/capture-ai-flavor-cases/SKILL.md) 承载。 ## 4. 失败分类 @@ -60,7 +60,7 @@ 正文发生变化后,旧审核不能继续作为接受依据——哈希一变,挂在旧哈希上的审核自动失效。 -> 实现现状标注(诚实保留):当前审核证据散在 `docs/` 的带日期报告里、不绑哈希,正文一变旧报告照常躺着、无法判定是否过期。审核/实验/运行证据落库并绑候选哈希,待建。 +> 实现现状标注(诚实保留):生产链审核证据已落库并绑候选哈希——机械门与语义检测结果随 `persist_writer_execution` 落 `example_quality_result`(`candidate_sha256` 绑定),语义状态另固化到候选行作为接受通道的 DB 兜底。回放评测链的审核报告仍散在 `docs/` 的带日期报告里、不绑哈希,迁库待建。 ## 6. 经验升格 @@ -75,7 +75,9 @@ -> 稳定 Skill / Tool / Agent 规则 ``` -这是本领域的设计目标合同;现状是这条链基本未实现——证据还散在文件、升格靠人手记,承接物(把 lesson/win 接进范式与 Skill 的结构与状态机)还没建起来。 +这是本领域的设计目标合同。现状:承接物骨架已建——`example_lesson` 登记表(`lesson_registry`)承载 lesson/win 证据(绑 run_id 与候选哈希),状态机 `proposed → reviewing → promoted/rejected` 由 DB 触发器强制:跳过评审的自动升格被拒,`promoted` 必须指明目标类型(pattern/skill/tool)与落点,终态不可再流转。仍未建:证据的自动收集(当前靠人/agent 登记)、升格到范式卡与 Skill 合同的具体变更动作(promoted 之后的落地由各 owner 领域承接)。 + +AI 味案例是“单次观察”的一种证据载体:既有作品反向扫描和创作反馈都先自动落库为 `shadow`,不直接进入范式或生产规则。确认、样例投影和规则消费前必须重验来源哈希与位置锚点;哈希变化、来源不可得或锚点不一致时保留历史证据但 fail-closed。`shadow` 只在本卡/本作品作用域内作为待复核提示;只有跨作品重复、同时有“应修”和“不应修/边界/回归”证据,并完成回放与独立评审,才允许生成规则候选;规则候选仍不是 `active`。`canonical` 也不等于规则生效,`rejected`/`archived` 只保留审计。 > 与 02 的同名区分:本节“升格”指经验沿上面这条链从一次观察长成可复用的范式/Skill/Tool。[02-实体领域](02-实体领域.md) 里的“作品面实体入库(也叫升格)”指拆书抽出的实体进入某部作品的正式事实库。两者只是都叫“升格”,对象、链路与 owner 都不同,引用时注意区分。 @@ -98,7 +100,7 @@ - **代价明账**:评测证据与生产共享同一数据库信任根,是单用户场景的刻意取舍——换可观测性与防篡改,放弃证据底座独立性 / 外部可审计性。这是明账,不是纯收益。 - **常设不变量**(从预防迁到发现):没有评测质量结果出现在任何生产视图、没有正式正文来源能追溯到评测候选——持续跑、违反即报警。(harness 改造原则,见 [docs/2026-08-01-评测harness改造设计](../../../../docs/2026-08-01-评测harness改造设计.md)) -现有实现承认(各一句,不展开):预算账本合同与运行探针锁定由 [`db`](../../../../.claude/skills) 与运行回执承载,Gate B 通过回执的哈希链由 [`confirm`](../../../../.claude/skills) 承载。 +现有实现承认(各一句,不展开):预算账本合同与运行探针锁定由 [`access-database`](../../../../.claude/skills/access-database/SKILL.md) 与 `record-run-evidence` 承载,Gate B 通过回执的哈希链由 [`decide-candidate`](../../../../.claude/skills/decide-candidate/SKILL.md) 承载。 > 实现现状标注(诚实保留):当前只有一个作品且评测样本已预注册,Gate B 因此恒判 `insufficient_evidence`(跨作品样本不足)——这是目标态下的正确行为,不是 bug。 @@ -108,14 +110,14 @@ ## 9. 待建 -- 经验升格链承接物:把 lesson/win 接进范式与 Skill/Tool 的结构、状态机与落库字段。 -- 审核证据落库绑哈希:审核/实验/运行记录从 `docs/` 日期报告迁入库表并绑正文或候选哈希。 +- 经验升格链后半段:`example_lesson` 登记与状态机已建(见 §6),promoted 之后到范式卡/Skill 合同的具体变更动作、证据自动收集未建。 +- 评测链审核证据落库绑哈希:生产链审核证据(机械门 + 语义报告)已随 `example_quality_result` 绑候选哈希落库;回放评测的审核报告仍在 `docs/` 日期报告里,迁库绑哈希待建。 - eval 收敛环脚本:把评分、失败分类与升格触发串成可机械复跑的闭环。 ## 10. 验收条件 1. 机械失败、运行失败和内容质量失败可以由原因码明确区分,脚本能从报告里读出类别。 -2. 修改后的候选一定重新检查并绑定新 hash;旧 hash 上的审核对当前正文不再有效。 +2. 修改后的候选一定重新检查并绑定新 hash;旧 hash 上的审核对当前正文不再有效。AI 味案例在确认、样例投影或规则消费前必须有 `verified` 重验证回执。 3. 每个 active 范式或稳定规则都有可追溯到库内记录(含哈希)的证据。 4. 同一章用两套不同场景策略打分时,通用底层指标的分差能被脚本读出并比较;场景策略只改场景权重,不改四个通用维度的定义。 5. 任一审核、实验或运行回执,都能用库内一条记录定位到它所评价的正文或候选哈希。 diff --git a/.agent/docs/architecture/domains/07-Agent与Skill领域.md b/.agent/docs/architecture/domains/07-Agent与Skill领域.md index c7a9b22..aded6ab 100644 --- a/.agent/docs/architecture/domains/07-Agent与Skill领域.md +++ b/.agent/docs/architecture/domains/07-Agent与Skill领域.md @@ -35,6 +35,14 @@ Agent 与 Skill 领域拥有角色职责、可调用能力合同、确定性工 数据库是权威:Skill 对自己读写哪些表负责,声明失败时如何关闭,并确保经手的输入和产出都落库。没落库的输入产出,在系统视角里等于不存在。只读看板只查库渲染,不替 Skill 写任何数据。 +### 3.1 命名与稳定标识 + +- Skill 名统一使用小写 `动作-对象`,直接说明调用者能执行什么;目录名必须与 frontmatter `name` 完全一致。 +- 名称不使用 `db`、`llm`、`runtime`、`eval` 这类内部模块缩写,也不使用 `upgrade`、`planning` 这类无法判断具体动作的阶段词。 +- scenario 与 Skill 名分离:`continuation`、`fine_outline` 等 scenario 由 `meta/chains` 显式映射到动作式 Skill 名,不能假设两者同名。 +- 数据库 `source_type`、creator/updater、错误码和备份逻辑键是历史兼容标识;Skill 改名不自动改写这些值。 +- 一个名称只对应一个目录和一份 `SKILL.md`;改名后不留旧目录、别名 Skill 或重复合同。 + ## 4. Tool 合同 - Tool 放在所属 Skill 的 `scripts/`,不散落一次性脚本。 @@ -78,12 +86,13 @@ ReAct Agent 不能把“扫全库、随意写表”当作通用工具。每次 ## 8. 验收条件 1. 一个 Skill 只有一个明确业务目的。 -2. 每个 Skill 在 SKILL.md 声明数据库读写合同(读哪些表、写哪些表、失败如何关闭)。 -3. 每个 Skill 的输入与产出都落库,只读看板能查到对应记录。 -4. Tool 从不同当前目录调用得到一致结果。 -5. 角色 Prompt 只声明职责边界(说清不做什么),不包含 adapter、hash、状态机等支架职责。 -6. 稳定经验能沿“范式 -> Skill/Tool/Agent”升格且不产生重复规则。 -7. 模型不可用只终止当次 AI 调用,库内已有正式内容不被损坏。 +2. Skill 目录名与 frontmatter `name` 一致,名称符合 `动作-对象`,scenario 通过链登记映射。 +3. 每个 Skill 在 SKILL.md 声明数据库读写合同(读哪些表、写哪些表、失败如何关闭)。 +4. 每个 Skill 的输入与产出都落库,只读看板能查到对应记录。 +5. Tool 从不同当前目录调用得到一致结果。 +6. 角色 Prompt 只声明职责边界(说清不做什么),不包含 adapter、hash、状态机等支架职责。 +7. 稳定经验能沿“范式 -> Skill/Tool/Agent”升格且不产生重复规则。 +8. 模型不可用只终止当次 AI 调用,库内已有正式内容不被损坏。 ## 9. 关联 SoT diff --git a/.agent/docs/architecture/domains/08-数据权威与可视化领域.md b/.agent/docs/architecture/domains/08-数据权威与可视化领域.md index b9f3042..c24863e 100644 --- a/.agent/docs/architecture/domains/08-数据权威与可视化领域.md +++ b/.agent/docs/architecture/domains/08-数据权威与可视化领域.md @@ -41,6 +41,8 @@ Git = 代码 / Skill / Agent 提示词 / meta(schema | 用户意图、规划、细纲、冻结的上下文、范式选择 | 规划与上下文冻结表 | | AI 待审候选正文(未接受) | 候选表(raw 访问控制,见 §5) | | 检测、评分、审核、实验结果 | 质量结果表 + 运行回执(见 §7) | +| AI 味案例卡(既有作品回填 / 创作反馈) | `example_ai_flavor_case`;检测命令完成后自动写入,默认 `shadow` | +| AI 味来源重验证批次与逐卡回执 | `example_ai_flavor_revalidation_batch` + `example_ai_flavor_revalidation`;append-only,记录 `verified/stale/unavailable/card_mismatch` | | 用户的接受 / 合并 / 丢弃决策 | 用户决策记录 | | 每次运行的回执 | 运行回执表(见 §7) | | 补证、重写记录 | 对应的候选 / 回执记录 | @@ -51,7 +53,7 @@ Git = 代码 / Skill / Agent 提示词 / meta(schema - 上表每一类都必须有库内记录;只读看板看不到的,就是没落库。 - 每条记录要能回答“它是谁产生的、针对哪个作品/章/候选、什么时候”。 -- 落库写入必须走 `db` skill 这一条数据库通道,不裸连、不散写一次性脚本(见 §9 与 [07-Agent与Skill领域](07-Agent与Skill领域.md))。 +- 落库写入必须走 `access-database` Skill 这一条数据库通道,不裸连、不散写一次性脚本(见 §9 与 [07-Agent与Skill领域](07-Agent与Skill领域.md))。 ## 4. 写入顺序 @@ -65,6 +67,7 @@ Git = 代码 / Skill / Agent 提示词 / meta(schema - 向量刷新失败**不回滚**已经成功的库写入。向量索引只是加速,可从库重建(见 §8)。 - 检索加速失败只影响速度,不改变内容语义、不跳过审核。 - 模型运行失败可以阻断当次 AI 任务,但不能损坏库里已有的作品、实体、范式。 +- AI 味检测的卡片写入与同次重验证回执在同一持久化事务内完成;数据库写失败时检测命令失败关闭,不返回“只生成成功”的假绿结果。 - 任何外部系统、缓存或投影都不得反过来改写库里的正式内容。 ## 5. raw 进库与访问控制 @@ -85,7 +88,7 @@ raw 指不适合直接当正文、但需要留存可查的完整材料:完整 ## 6. 只读可视化模块 -一个本地 web 看板,把库里的状态随时渲染给人看。核心不变量:纯标准库实现、只对库做只读查询并渲染、**绝不触发任何写**(接受、丢弃等写操作仍由 `confirm` skill 与主会话走,看板只展示结果);看板看到的等于库里的,因此它倒逼 §3 一切落库——看板上空白的地方,就是落库的缺口。 +一个本地 web 看板,把库里的状态随时渲染给人看。核心不变量:纯标准库实现、只对库做只读查询并渲染、**绝不触发任何写**(接受、丢弃等写操作仍由 `decide-candidate` Skill 与主会话走,看板只展示结果);看板看到的等于库里的,因此它倒逼 §3 一切落库——看板上空白的地方,就是落库的缺口。 完整合同(只读硬约束、技术形态、视图清单、raw 访问控制、验收)见 [可视化模块合同](../可视化模块合同.md),那里是本模块的唯一 SoT,本节不复制。 @@ -106,7 +109,7 @@ raw 指不适合直接当正文、但需要留存可查的完整材料:完整 - 回执写入后**不改写**;新的尝试产生新的记录(不可变账本)。 - 回执字段合同由本领域拥有;作品领域只提供一个在作品下的归档关系,质量领域只消费其中的结果摘要。 -- 现有实现里 `runtime` 的不可改回执(内容寻址账本)与租约 raw 保险库(当前服务评测)即属此合同的落地。 +- 现有实现里 `record-run-evidence` 的不可改回执(内容寻址账本)与租约 raw 保险库(当前服务评测)即属此合同的落地。 ## 8. 可恢复性 @@ -124,22 +127,23 @@ raw 指不适合直接当正文、但需要留存可查的完整材料:完整 - Git 保存代码、Skill、Agent 提示词、`meta/`(schema 与 chains)、文档、DDL 的版本历史,提供差异审查、回滚依据和备份。 - Git 不是正式内容权威,也不是实时消息总线。它可以对作品信息与作品文本留痕(历史、备份),但留痕不等于权威;正式内容以库为准,看板只读库,冲突时以库为准。 - 一次 `git commit` **不等于**用户接受。接受语义由创作流程和库内状态决定,见 [05-创作流程领域](05-创作流程领域.md)。 -- 数据库写入、DDL 应用走 `db` skill 单一通道;DDL 先落 `db/ddl/` 审计文件再应用,不绕过。 +- 数据库写入、DDL 应用走 `access-database` Skill 单一通道;DDL 先落 `db/ddl/` 审计文件再应用,不绕过。 可承认的现有实现(引用即可,细节以各自载体为准): -- `db` skill:单一数据库通道。 -- `runtime`:不可改回执(内容寻址账本)与租约 raw 保险库(当前服务评测)。 +- `access-database`:单一数据库通道。 +- `record-run-evidence`:不可改回执(内容寻址账本)与租约 raw 保险库(当前服务评测)。 - 额度账本:`db/ddl/95-example额度账本.sql`。 - 清洗日志:`db/ddl/92-example清洗日志.sql`。 - 参考作品授权快照:`db/ddl/96-example参考作品授权快照.sql`(**决定不启用**,领域索引 §9;DDL 留存不 apply)。 -- `import` skill:参考书 / 旧稿导入落库。 +- `import-book`:参考书 / 旧稿导入落库。 ## 10. 待建(目标合同) 以下为当前尚未满足、但属于本领域目标合同的缺口: -- 一切输入产出落库:运行注册 / 回执 / 质量评判 / 模型调用明细表已建(97/98),写路径未接通;候选正文、用户决策、补证记录、规划与冻结(99/100)尚未建。 +- 一切输入产出落库:AI 味案例三表(104)与 `capture-ai-flavor-cases` 检测写路径已接通;运行注册 / 回执 / 质量评判 / 模型调用明细(97/98)已建;候选、用户决策、规划与冻结(99/100)的写路径已由生产链接通(`persist_writer_execution` / `write_canonical` / `persist_planning` / `persist_freeze`)。本轮新增并已接通:候选语义审查列(105)、事实增量提案与正典账本(106)、投影登记(107)、经验升格登记(108)、候选 CAS 状态链(109)。 +- 投影的异步执行:接受时只登记 pending 投影(章后抽取等),worker 与重建调度未建(登记/失效/重试/巡检合同已建,见 `projection_registry`)。 - 只读可视化看板:首版已建(总览/作品/知识库/运行记录四空间);运行详情下钻、智能体空间、工作区标签待建。 - raw 全文进库的表与访问控制(101)。 - DB 备份与重建脚本。 diff --git a/.agent/docs/architecture/domains/_index.md b/.agent/docs/architecture/domains/_index.md index 16439db..d8cea30 100644 --- a/.agent/docs/architecture/domains/_index.md +++ b/.agent/docs/architecture/domains/_index.md @@ -33,7 +33,7 @@ ## 4. 只读可视化模块 - 一个纯 Python 标准库的本地 web 看板(web 层零框架、零新增依赖;psycopg 复用 `.venv` 现成的,非标准库),内网或 Tailscale 访问,形态参照 open-wenmo。 -- 只对数据库做只读查询并渲染,**不触发任何写操作**;接受、丢弃等写操作仍由 `confirm` skill 和主会话走,看板只展示结果。 +- 只对数据库做只读查询并渲染,**不触发任何写操作**;接受、丢弃等写操作仍由 `decide-candidate` Skill 和主会话走,看板只展示结果。 - 它看到的内容等于库里的内容,因此它倒逼第 3 条“一切落库”。 - 合同细节见 [08-数据权威与可视化领域](08-数据权威与可视化领域.md)。 diff --git a/.agent/docs/architecture/可视化模块合同.md b/.agent/docs/architecture/可视化模块合同.md index f96097f..490f9d6 100644 --- a/.agent/docs/architecture/可视化模块合同.md +++ b/.agent/docs/architecture/可视化模块合同.md @@ -4,20 +4,21 @@ ## 1. 它是什么,不做什么 -看板是一个本地 web 页面,把 `muse-example` 库里的状态随时渲染给你看。 +看板是一个本地 web 页面,把 `muse-example` 库里的状态随时渲染给你看;质量证据的离线 JSON 只作为数据库不可用时的明确回退。 - 它只干两件事:对库做**只读查询**,把结果渲染成人能读的页面。 -- 它**绝不触发任何写操作**。接受、合并、丢弃、确认这些写,仍由 `confirm` skill 和主会话走,看板只展示结果。 +- 它**绝不触发任何写操作**。接受、合并、丢弃、确认这些写,仍由 `decide-candidate` Skill 和主会话走,看板只展示结果。 - 它看到的 = 库里的。看板上空白的地方,就是落库的缺口——所以看板天然是“一切输入产出必须落库”这条纪律的验收面。 +- `/ai-flavor` 也是数据库视图:默认读取 AI 味案例卡与重验证账本表,页面明确标注“候选命中,不是确认结论”;只有数据库不可用时才显示离线回退及原因。其余视图同样以数据库为权威。 - 它服务本机单用户,不做多用户、权限管理、对外分享。 ## 2. 只读硬约束(怎么保证它绝不写) 这是看板的命根子,验收时按机械门查: -- **连接只读**:看板用独立的只读连接,默认事务只读(`SET TRANSACTION READ ONLY` 或库侧只读角色);连接串与写通道(`db` skill)分开。 +- **连接只读**:看板用独立的只读连接,默认事务只读(`SET TRANSACTION READ ONLY` 或库侧只读角色);连接串与写通道(`access-database` Skill)分开。 - **代码无写语句**:全模块只允许 `SELECT`,不得出现任何 `INSERT/UPDATE/DELETE` 或 DDL。**真门禁是库级只读连接**(写语句被 PostgreSQL 直接拒);“grep 无写语句”是辅助门,须用 `\bINSERT\b` / `\bUPDATE\b` / `\bDELETE\b` 词边界查(否则 `deleted=false` 里的 DELETE 子串会误报)。 -- **不接会写的通道**:看板不调用 `confirm` 或任何会写库的 skill,只自己读库。 +- **不接会写的通道**:看板不调用 `decide-candidate` 或任何会写库的 Skill,只自己读库。 - **挂了不牵连**:看板进程崩了、断网了,库内正式内容和创作链不受任何影响。 ## 3. 技术形态 @@ -40,20 +41,29 @@ | 额度账本 | 5 小时额度窗、花费与调用次数水位 | 额度账本表(`db/ddl/95-example额度账本.sql`) | | 结构本体(框架视图,可选) | 23 型合同、字段、aiContext 控制项、保护节点、功能链 | `muse_meta_schema` / `muse_meta_field` / `muse_meta_visibility_policy` / `muse_meta_protection_node` / `muse_meta_function_chain` | | 写命令审计 | 写命令的幂等审计记录 | `muse_content_command_log` | +| AI 味案例回填 | 作品扫描的候选命中、来源哈希/位置、Shadow 状态和复核详情 | `example_ai_flavor_case`(检测命令自动写入) | +| AI 味案例来源重验证 | 当前来源是否仍与案例卡的哈希/位置一致,以及 stale/unavailable/mismatch 结果 | `example_ai_flavor_revalidation_batch` + `example_ai_flavor_revalidation`(append-only) | | 授权快照(不启用) | 单用户本地不做多租户授权机制;参考书出处见“参考书与拆书”视图 | 96 不启用(领域索引 §9) | +| 待审候选 | 候选正文、版本、哈希、机械/语义状态、所属作品/章 | `example_candidate`(生产链 `persist_writer_execution` 落候选,`write_canonical` 翻 accepted/discarded) | +| 候选 CAS 状态链 | 每次生产运行的 DRAFT/CHECKING/PASSED/REJECTED 迁移与 revision | `example_candidate_cas`(109,生产编排实时写) | +| 用户决策 | 接受 / 合并 / 丢弃的历史与依据 | `example_user_decision`(`write_canonical` 单事务 append-only 归档) | +| 运行列表 / 回执 / 质量评判 | 一次运行的作品/触发/终态;逐段身份回执;机械/语义检测结论 | `example_run` / `example_run_receipt` / `example_quality_result`(98,生产与评测写路径已接通) | +| 模型调用 / raw 全文 | 逐次调用的模型/花费/提示词哈希;完整输入输出与供应商响应 | `example_llm_call`(97)/ `example_raw_content`(101,`record-run-evidence` 随调用原子落库) | +| 规划与冻结上下文 | 规划、细纲、冻结上下文清单与授权快照 | `example_planning_section` / `example_context_freeze`(100,`persist_planning` / `persist_freeze` 写) | +| 事实增量 | 候选提出的类型化增量(proposed/accepted/rejected)与已进账本的变更流 | `example_fact_delta` / `example_fact_ledger`(106,接受候选同事务写入) | +| 投影登记 | 摘要/抽取/embedding 等投影的 pending/completed/failed/stale 与重试 | `example_projection_run`(107,接受候选同事务登记) | +| 经验升格 | lesson/win 证据与 proposed→reviewing→promoted/rejected 流转 | `example_lesson`(108,`lesson_registry` 写) | -### 4.2 待落库(合同要求、表尚未建/未接通;看板预留视图,暂显示“无数据 / 待落库”) +### 4.2 待落库 / 待建视图 -| 视图 | 看什么 | 落库去向 | -|---|---|---| -| 待审候选 | 候选正文、版本、哈希、状态、所属作品/章 | 候选表(待建,见 08 §3) | -| 用户决策 | 接受 / 合并 / 丢弃的历史与依据 | 用户决策记录(待建) | -| 运行回执 | 每次运行的回执、结果摘要、raw 指针 | `example_run_receipt`(98 已建,写路径未接通,见 08 §7) | -| 运行列表 | 一次运行的作品 / 触发 / 终态,运行页的索引 | `example_run`(98 已建,写路径未接通) | -| 质量评判 | 每次检测 / 评分 / 审核 / 实验的结论、分值、量表版本、失败类别 | `example_quality_result`(98 已建,写路径未接通,06 §3/§5) | -| 模型调用 | 提示词、执行配置、花费 | `example_llm_call`(97 已建,写路径未接通) | -| raw 全文 | 完整原文、完整问答、标准答案、供应商响应 | raw 表(待建,受 §5 访问控制) | -| 规划与冻结上下文 | 规划、细纲、冻结上下文清单 | 规划与上下文冻结表(待建) | +数据面:上表所列各表均已建表且写路径接通,无“表未建”项。 + +仍待建的是**消费侧**: + +| 项 | 现状 | +|---|---| +| 上述新表(候选/CAS/决策/事实增量/投影/经验)的看板渲染视图 | 看板首版只有总览/作品/知识库/运行记录四空间,新视图待画 | +| 投影的异步执行 worker | 接受时只登记 pending 投影,执行与重建调度未建(合同见 `projection_registry`) | ## 5. raw 全文与访问控制 @@ -63,7 +73,7 @@ ## 6. 与现有通道的关系 -- 看板**不替代** `db` skill。`db` skill 是面向 agent 和主会话的唯一数据库通道(可写可查);看板是面向人的独立只读渲染面。 +- 看板**不替代** `access-database` Skill。后者是面向 agent 和主会话的唯一数据库通道(可写可查);看板是面向人的独立只读渲染面。 - 看板只读库,不经过会写库的通道;它的存在和死活都不影响创作链。 ## 7. 看板必须正确呈现的库内约定(防误导) @@ -89,7 +99,7 @@ 1. 落库表分两类:**运行注册 / 运行回执 / 质量评判(98)与模型调用(97)已建**,但写路径未接通,视图暂空;候选 / 用户决策 / raw 全文 / 规划冻结(99/100/101)尚未建。没有它们 §4.2 的视图就是空的。 2. `db/ddl/96` 授权快照表**决定不启用**(领域索引 §9,单用户本地不做多租户授权),DDL 留存不 apply。 -3. 看板首版已建成(总览 / 作品 / 知识库 / 智能体 / 运行记录五空间,只读 HTTP + §4.1 部分视图 + 待落库占位格;智能体空间读 102 登记表;运行记录含按作品/按角色双维 + 运行详情下钻);待建 = 作品/章工作区标签、沿来源下钻链(raw 下钻依赖 101)。页面与信息架构见 §10。 +3. 看板首版已建成(总览 / 作品 / 知识库 / 质量证据 / 智能体 / 运行记录六空间,只读 HTTP + §4.1 部分视图 + 待落库占位格;AI 味案例查 104 三表;智能体空间读 102 登记表;运行记录含按作品/按角色双维 + 运行详情下钻);待建 = 作品/章工作区标签、沿来源下钻链(raw 下钻依赖 101)。页面与信息架构见 §10。 ## 10. 页面与信息架构(产品形态 SoT) @@ -98,7 +108,7 @@ - **记录留痕的验收面**:一切输入产出落不落库,看板上一目了然;空白处就是缺口,也就是后续抽取优化要补的地方。 - **内容与复利原料的浏览面**:作品正文、知识卡、公共范式、运行产出,可浏览、可沿来源下钻。 -### 10.1 菜单树(对齐 muse 领域布局,五空间) +### 10.1 菜单树(对齐 muse 领域布局,六空间) **两条层级原则**(防止把菜单项和详情页混级): @@ -109,6 +119,8 @@ **标记**:`▸` 菜单项 · `└→` 详情下钻 · `[标签]` 详情页内二级标签 · 数据源 `✓ 已有` / `◌ 待落库` / `⚠ 需登记`。 +AI 味案例的固定入口是 `/ai-flavor`。列表按作品、模式分页,卡片详情显示卡 ID、状态、人工标签、来源许可、全文/命中哈希、行/字符位置、重验证状态、当前用途和作用域;页面只读,不触发重验证。 + ```text 1. 总览 / 〔数据权威 08〕 单页 ├─ 落库账本 各承接表:有数/0行/待落库(验收面) ✓ @@ -155,7 +167,10 @@ └─ 清洗审计(批次/理由/模型/删了几处) ✓ ▸ 结构本体 /knowledge/meta 〔框架视图·次要〕 23型/字段/aiContext/保护节点/功能链 ✓ -4. 智能体 /agents 〔Agent 07〕 +4. 质量证据 /ai-flavor 〔质量 06 · 数据库〕 + ▸ AI 味案例回填 作品/模式筛选、候选卡分页、来源锚点详情;只显示 hash/位置,不显示未授权原文 + +5. 智能体 /agents 〔Agent 07〕 ▸ 角色 /agents 5 角色卡(writer/planner/extractor/detector/judge) └→ 角色详情 /agents/:role ├─ 角色画像 职责/模型归属 ⚠(登记表) @@ -169,7 +184,7 @@ ├─ 读写合同 读/写哪些表 ⚠(登记表) └─ 调用情况 ✓ -5. 运行记录 /runs 〔流程 05 · 质量 06〕 +6. 运行记录 /runs 〔流程 05 · 质量 06〕 ▸ 按作品看运行 /runs/by-work 〔作品选择器〕 ✓ ▸ 按角色看运行 /runs/by-role 〔角色选择器〕 ✓ └→ 运行详情 /runs/:run_id @@ -196,13 +211,13 @@ **智能体页的 ⚠ 来源(A 方案,2026-07-30 拍板)**:角色画像 / 可配置项 / 技能合同在 Git 侧(`.claude/agents/`、`.claude/skills/`),库里没有。看板只读库,故新增**智能体/技能登记表**(`example_agent_role` + `example_skill`,设计见落库设计稿),把角色、模型归属、可配置项、技能读写合同**登记入库**——智能体配置也纳入留痕,看板只读这张表。 -**分阶段点亮(认)**:首版实现为五空间(总览 / 作品 / 知识库 / 智能体 / 运行记录;智能体空间读 102 登记表 `example_agent_role`/`example_skill`,登记表由 `db/scripts/sync_agent_registry.py` 从 Git 侧同步,Git 仍是配置权威)。作品工作区**先上 [章节]**(正文已有),其余标签(大纲/细纲/设定/叙事状态、章工作区的细纲与出场卡)随对应表落库(规划表、候选表,落库设计稿第 3/2 步)逐格点亮;点亮前显示"待落库"占位,不假装已有。 +**分阶段点亮(认)**:首版实现为六空间(总览 / 作品 / 知识库 / 质量证据 / 智能体 / 运行记录,见 §9.3;智能体空间读 102 登记表 `example_agent_role`/`example_skill`,登记表由 `.claude/skills/access-database/scripts/sync_agent_registry.py` 从 Git 侧同步,Git 仍是配置权威)。作品工作区**先上 [章节]**(正文已有),其余标签(大纲/细纲/设定/叙事状态、章工作区的细纲与出场卡)随对应表落库(规划表、候选表,落库设计稿第 3/2 步)逐格点亮;点亮前显示"待落库"占位,不假装已有。 ### 10.2 UI 形态 - 纯标准库 HTTP 服务端渲染 HTML(§3),相对路径 + 注入 base(仿 open-wenmo hub),无 SPA 框架。 - 布局:左侧顶级导航(§10.1 五个领域空间,各项标注对应领域,顶级项下缩进列出二级子菜单)+ 主区。列表页 = 表格 + 分面过滤 + 分页;详情下钻页**不进菜单**、顶部显示面包屑(如 `作品 / 深空之影 / 第 5 章`),页内用二级标签分区(作品工作区 / 章工作区);正文 = 可读排版;raw = 等宽全文 + 访问控制提示。 -- 交互只有两类:只读浏览、沿来源下钻。**无任何写操作**(§2 硬约束);接受 / 丢弃仍走 `confirm` skill。 +- 交互只有两类:只读浏览、沿来源下钻。**无任何写操作**(§2 硬约束);接受 / 丢弃仍走 `decide-candidate` Skill。 - 过滤 / 搜索用 query 参数、服务端渲染;每次请求实时查库,不缓存(§7)。 ### 10.3 信息披露的重点(怎么组织、为什么) diff --git a/.claude/agents/detector.md b/.claude/agents/detector.md index b3e3d6f..da8b6df 100644 --- a/.claude/agents/detector.md +++ b/.claude/agents/detector.md @@ -5,7 +5,7 @@ tools: Read, Grep, Glob model: opus --- -你是检测员。功能合同以 `detect` skill 为准。你只返回当前调用要求的结构化检测草稿,不修改正文、规划、知识卡、运行状态或任何文件。 +你是检测员。功能合同以 `check-content-consistency` Skill 为准。你只返回当前调用要求的结构化检测草稿,不修改正文、规划、知识卡、运行状态或任何文件。 ## 模型口径 @@ -29,7 +29,7 @@ model: opus ## 细纲回放 -细纲回放只接收冻结到 `as_of` 的匿名候选与公共规划上下文,不读取目标章 proxy,不推断实验臂。报告类别使用 `detect` skill 登记的闭集;任一高严重度问题由编排器阻断,检测员不改候选、不裁决卡效用。 +细纲回放只接收冻结到 `as_of` 的匿名候选与公共规划上下文,不读取目标章 proxy,不推断实验臂。报告类别使用 `check-content-consistency` 登记的闭集;任一高严重度问题由编排器阻断,检测员不改候选、不裁决卡效用。 ## 禁区 diff --git a/.claude/agents/extractor.md b/.claude/agents/extractor.md index b48c1a9..180cf96 100644 --- a/.claude/agents/extractor.md +++ b/.claude/agents/extractor.md @@ -1,15 +1,15 @@ --- name: extractor -description: 抽取员——分析槽位默认绑定件,承接 full_parse(拆书)与 extraction(章后抽取)两功能;功能细节以 parse-book / extract-knowledge skill 为合同;产出全为草稿。 +description: 抽取员——分析槽位默认绑定件,承接 full_parse 与 extraction;分别加载 deconstruct-book 与 extract-chapter-knowledge,产出全为草稿。 tools: Read, Write, Grep, Glob model: opus --- -你是知识抽取员,分析槽位的默认绑定件。两用场各有功能合同:**拆书=`parse-book` skill,章后抽取=`extract-knowledge` skill**,一次只带本次功能的合同。产出全部是草稿(文件版不提交;PG 版 status=draft)。 +你是知识抽取员,分析槽位的默认绑定件。两用场各有功能合同:**拆书=`deconstruct-book`,章后抽取=`extract-chapter-knowledge`**,一次只带本次功能的合同。产出全部是草稿(文件版不提交;PG 版 status=draft)。 ## 模型口径(抽取有两条路径) -- **拆书 / 导入侧抽取**:经 `llm` / `parse-book` skill 统一入口调用,走 MiniMax-M3,**不走角色 model 派发**;本角色 frontmatter 的 `model: opus` 不约束这条路径。 +- **拆书 / 导入侧抽取**:经 `call-content-model` / `deconstruct-book` 统一入口调用,走 MiniMax-M3,**不走角色 model 派发**;本角色 frontmatter 的 `model: opus` 不约束这条路径。 - **创作期章后抽取**:作为角色派发执行,可用 opus 4.8;frontmatter 的 `model: opus` 对应的就是这条路径。 ## 元数据纪律(怎么用元数据) @@ -23,8 +23,8 @@ model: opus 1. 以正文为准,不脑补正文没写的; 2. 与既有知识冲突时**不覆盖**——「⚠ 冲突待裁决」双版本留档并升级用户; 3. 基础字段规范填:来源(抽取@第N章 / 拆书@书名)、状态(草稿); -4. **采纳正文≠确认知识**:确认另走 confirm,自动确认条件的判定不归你。 +4. **采纳正文≠确认知识**:确认另走 `decide-candidate`,自动确认条件的判定不归你。 ## 禁区 -不动正文、大纲、框架文件;不执行 git 写操作;PG 版不直接写库(产结构化清单,经主会话走 db skill 入库)。 +不动正文、大纲、框架文件;不执行 git 写操作;PG 版不直接写库(产结构化清单,经主会话走 `access-database` 入库)。 diff --git a/.claude/agents/judge.md b/.claude/agents/judge.md index 099ec9e..93828cf 100644 --- a/.claude/agents/judge.md +++ b/.claude/agents/judge.md @@ -5,7 +5,7 @@ tools: Read, Grep, Glob model: opus --- -你是质量评委。功能合同以 `quality-gate` skill 为准。每次调用只完成一个独立评审,不修改候选、规划、知识卡、运行状态或任何文件。 +你是质量评委。功能合同以 `score-content-quality` Skill 为准。每次调用只完成一个独立评审,不修改候选、规划、知识卡、运行状态或任何文件。 ## 正文回放边界 diff --git a/.claude/agents/planner.md b/.claude/agents/planner.md index 19c69a2..79016f4 100644 --- a/.claude/agents/planner.md +++ b/.claude/agents/planner.md @@ -1,20 +1,22 @@ --- name: planner -description: 规划师——规划槽位默认绑定件,承接 planning 与 fine_outline 功能;产出结构=schema 字段清单本身,功能细节以对应 skill 为合同;产出全为草稿。 +description: 规划师——规划槽位默认绑定件,承接 setting_init、planning 与 fine_outline;分别加载 design-story-foundation、plan-story 与 plan-chapter,产出全为草稿。 tools: Read, Write, Grep, Glob model: opus --- -你是这部书的总规划,规划槽位的默认绑定件。功能合同按本次任务二选一: +你是这部书的总规划,规划槽位的默认绑定件。每次只执行一个功能合同: -- `planning`:遵守 `planning` skill,负责立项与规划修订; -- `fine_outline`:遵守 `fine-outline` skill,只产结构细纲,不写正文。 +- `setting_init`:遵守 `design-story-foundation` Skill,独立完成一份用户挑选前的前期设定候选; +- `planning`:遵守 `plan-story` Skill,负责立项与规划修订; +- `fine_outline`:遵守 `plan-chapter` Skill,只产结构细纲,不写正文。 -产出全部不提交;未确认的规划不进生成上下文。回放任务中,`fine-outline` skill 的冻结边界优先于本身份段里面向正式创作的全局规划能力。 +产出全部不提交;未确认的规划不进生成上下文。回放任务中,`plan-chapter` 的冻结边界优先于本身份段里面向正式创作的全局规划能力。 ## 元数据纪律(怎么用元数据) -- **产出结构=schema 字段清单本身**:设定包/大纲/知识卡/状态的每一节每一卡,都按对应 schema 逐字段产出(落点表见 planning skill);**字段全覆盖**,写不出=设计问题,标「字段存疑:原因」——这是验证元数据设计的一等产出,不许静默跳过。 +- `setting_init` 的结构由 `design-story-foundation` 冻结的候选合同控制;下面的 schema 纪律只用于 `planning` 与 `fine_outline`。 +- **产出结构=schema 字段清单本身**:设定包/大纲/知识卡/状态的每一节每一卡,都按对应 schema 逐字段产出(落点表见 `plan-story`);**字段全覆盖**,写不出=设计问题,标「字段存疑:原因」——这是验证元数据设计的一等产出,不许静默跳过。 - **你是唯一看全底牌的生成型角色**(谜底与真相/结局方向/未来卷粗纲):底牌管理是规划职责——底牌写进对应 aiContext 受限字段,绝不散进人人可见的字段。 - schema 加字段,设定包立刻多一节,你一字不改。 @@ -27,4 +29,4 @@ model: opus ## 禁区 -不写正文;不动 `meta/` 与框架文件;不执行 git 写操作、不写数据库(规划落库由主会话经 `planning` skill 的 `persist_planning.py` 做)。 +不写正文;不动 `meta/` 与框架文件;不执行 git 写操作、不写数据库(规划落库由主会话经 `plan-story` 的 `persist_planning.py` 做)。 diff --git a/.claude/agents/writer.md b/.claude/agents/writer.md index d594b3a..f763457 100644 --- a/.claude/agents/writer.md +++ b/.claude/agents/writer.md @@ -1,10 +1,10 @@ --- name: writer -description: 网文写手——写作槽位默认绑定件,承接 continuation/rewrite/expansion/polish 四功能;功能细节以对应 skill 为合同;只产候选、不提交。 +description: 网文写手——写作槽位默认绑定件,承接 continuation/rewrite/expansion/polish;分别加载 write-next-chapter、rewrite-selection、expand-scene 与 polish-prose,只产候选。 model: opus --- -你是这部书的执笔写手,写作槽位的默认绑定件。功能行为以派发指令加载的单一 skill 为合同:`continuation`(续写)/`rewrite`(改写)/`expansion`(扩写)/`polish`(润色)。你只返回一章正文草稿,**永不读写工作区、永不 git 提交**——采纳权在用户。 +你是这部书的执笔写手,写作槽位的默认绑定件。派发指令按 scenario 只加载一个 Skill:`continuation`→`write-next-chapter`,`rewrite`→`rewrite-selection`,`expansion`→`expand-scene`,`polish`→`polish-prose`。你只返回当前合同要求的正文草稿,**永不读写工作区、永不 git 提交**——采纳权在用户。 ## 输入边界 @@ -12,6 +12,7 @@ model: opus - `fineOutline`、`narrativeState`、`factConstraints`、`proseExcerpts`、`patternReferences`、`lengthContract` 和 `styleConstraints` 都由可信上下文层投影;卡片只是索引,你不得自行顺着卡搜索。 - 大纲只给本章方向;细纲的硬事件、结果方向、伏笔动作、章末钩子和必须出场实体是不可删除或反转的硬骨架;可调整节拍才允许重排。 - `factConstraints` 只约束事实真伪;`proseExcerpts` 只用于人物声音、动作习惯和叙事质感,不得拿文风样本替代事实约束。 +- `patternReferences` 是可参考的写作范式:每条含名字(name)、一句话摘要(summary)和写法要点(writingPoints);只借鉴其写法节奏与技巧,不当作事实约束,不照抄。 ## 元数据纪律(怎么用元数据) diff --git a/.claude/skills/db/SKILL.md b/.claude/skills/access-database/SKILL.md similarity index 50% rename from .claude/skills/db/SKILL.md rename to .claude/skills/access-database/SKILL.md index fb00abd..f024db7 100644 --- a/.claude/skills/db/SKILL.md +++ b/.claude/skills/access-database/SKILL.md @@ -1,9 +1,9 @@ --- -name: db -description: muse-example 实验库的唯一数据库通道——查询/DML/DDL/SQL 文件应用全走 scripts/db.py(psycopg 直连),SELECT 结果卡片式打印即审查面。主会话与智能体要读写 PG 一律经此,不得裸连或散写一次性脚本。 +name: access-database +description: 通过唯一受控入口查询或修改 muse-example PostgreSQL,并应用可审计 DDL。主会话或 Skill 需要通用数据库访问时使用;专用导入、嵌入和检索仍走各自 Skill,禁止裸连和一次性脚本。 --- -# db —— muse-example 唯一数据库通道 +# 访问 muse-example 数据库 对应 muse API 面:数据访问层。连接事实与凭据见 [`db/连接信息.md`](../../../db/连接信息.md)(DSN 已锁死在脚本内,只连 `muse-example`)。 @@ -11,37 +11,37 @@ description: muse-example 实验库的唯一数据库通道——查询/DML/DDL/ ```bash # 查询:卡片式打印(默认最多 50 行、长值截 160 字) -.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT id,title FROM muse_content_work" -.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT ..." --json # JSON 数组输出(给脚本消费) -.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT ..." --full # 长值不截断 -.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT ..." --max 200 # 放宽行数 +.venv/bin/python .claude/skills/access-database/scripts/db.py query "SELECT id,title FROM muse_content_work" +.venv/bin/python .claude/skills/access-database/scripts/db.py query "SELECT ..." --json # JSON 数组输出(给脚本消费) +.venv/bin/python .claude/skills/access-database/scripts/db.py query "SELECT ..." --full # 长值不截断 +.venv/bin/python .claude/skills/access-database/scripts/db.py query "SELECT ..." --max 200 # 放宽行数 # 单条写操作(INSERT/UPDATE/DELETE/DDL):报影响行数 -.venv/bin/python .claude/skills/db/scripts/db.py exec "UPDATE ... WHERE ..." +.venv/bin/python .claude/skills/access-database/scripts/db.py exec "UPDATE ... WHERE ..." # 参数化写操作:SQL 用 %s 占位,参数走服务端绑定(防注入;大内容不拼命令行) -.venv/bin/python .claude/skills/db/scripts/db.py execparams "INSERT INTO example_raw_content(kind,content_sha256,content) VALUES (%s,%s,%s)" --param response --param --param "短文本" +.venv/bin/python .claude/skills/access-database/scripts/db.py execparams "INSERT INTO example_raw_content(kind,content_sha256,content) VALUES (%s,%s,%s)" --param response --param --param "短文本" # 大对象(raw 全文)经 stdin 传 JSON 数组(避开 shell 转义 / ARG_MAX): -.venv/bin/python .claude/skills/db/scripts/db.py execparams "INSERT ... VALUES (%s,%s)" --stdin < params.json # params.json = ["", "<完整全文>"] +.venv/bin/python .claude/skills/access-database/scripts/db.py execparams "INSERT ... VALUES (%s,%s)" --stdin < params.json # params.json = ["", "<完整全文>"] # execparams 参数装载逻辑离线自测(不连库) -.venv/bin/python .claude/skills/db/scripts/test_db_params.py +.venv/bin/python .claude/skills/access-database/scripts/test_db_params.py # SQL 文件应用:整文件一个事务,失败全回滚 -.venv/bin/python .claude/skills/db/scripts/db.py apply db/ddl/91-example实验私货.sql +.venv/bin/python .claude/skills/access-database/scripts/db.py apply db/ddl/91-example实验私货.sql # 表清单+活行数(deleted=FALSE 计数,无 deleted 列的表计全行) -.venv/bin/python .claude/skills/db/scripts/db.py tables +.venv/bin/python .claude/skills/access-database/scripts/db.py tables # A3 种子:23 型 YAML → meta 表行(幂等可重跑;字段改动=改 YAML 后重跑) -.venv/bin/python .claude/skills/db/scripts/seed_schemas.py +.venv/bin/python .claude/skills/access-database/scripts/seed_schemas.py ``` ## 红线 - **只连 `muse-example`**:DSN 硬编码锁库;严禁改造脚本去碰共享 PG 上的 muse_local / muse_slice_live / *_test。 - 软删约定照主仓:删除=UPDATE `deleted=TRUE`,不物理删(example_* 表同样遵守)。 -- 批量导入/嵌入等专用写路径由 import/embed skill 封装(内部同走 psycopg 直连),本 skill 承担通用查改与 DDL 应用。 +- 批量导入/嵌入等专用写路径由 `import-book`/`embed-knowledge` Skill 封装(内部同走 psycopg 直连),本 Skill 承担通用查改与 DDL 应用。 - 建表/改表先落 `db/ddl/` 文件再 `apply`,不敲一次性 DDL——文件即审计。 - 大对象写入(raw 全文等)走 `execparams` 参数化通道(大内容经 stdin JSON),不得把大内容拼进 `exec` 的 SQL 字符串(shell 转义 + ARG_MAX);参数化绑定同时防 SQL 注入。 diff --git a/.claude/skills/db/scripts/db.py b/.claude/skills/access-database/scripts/db.py similarity index 97% rename from .claude/skills/db/scripts/db.py rename to .claude/skills/access-database/scripts/db.py index 27f5824..79dc783 100644 --- a/.claude/skills/db/scripts/db.py +++ b/.claude/skills/access-database/scripts/db.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""muse-example 唯一数据库通道(db skill 脚本层)。 +"""muse-example 唯一数据库通道(access-database Skill 脚本层)。 - DSN 锁死 muse-example:严禁触碰共享 PG 上其他库(muse_local / muse_slice_live / *_test)。 - query 卡片式打印=审查面;exec 报影响行数;apply 整文件一个事务失败全回滚。 @@ -22,7 +22,7 @@ def connect(readonly: bool = False): readonly=True 时会话级锁死只读(写语句被 PG 直接拒)——query 命令与看板用。 其它 skill 的写路径需要参数化短连接时,`from db import connect` 复用同一 DSN, - 不要各自硬编码连接串(仿 llm skill 的 _bump_window)。 + 不要各自硬编码连接串(仿 call-content-model 的 _bump_window)。 """ if readonly: return psycopg.connect(DSN, options="-c default_transaction_read_only=on") diff --git a/.claude/skills/db/scripts/seed_schemas.py b/.claude/skills/access-database/scripts/seed_schemas.py similarity index 100% rename from .claude/skills/db/scripts/seed_schemas.py rename to .claude/skills/access-database/scripts/seed_schemas.py diff --git a/.claude/skills/db/scripts/sync_agent_registry.py b/.claude/skills/access-database/scripts/sync_agent_registry.py similarity index 58% rename from .claude/skills/db/scripts/sync_agent_registry.py rename to .claude/skills/access-database/scripts/sync_agent_registry.py index 77a50b9..29bb8d7 100644 --- a/.claude/skills/db/scripts/sync_agent_registry.py +++ b/.claude/skills/access-database/scripts/sync_agent_registry.py @@ -7,18 +7,23 @@ upsert 进 example_agent_role / example_skill; - 看板只读登记表、不读 Git。幂等可重跑(upsert),配置变更后重跑即同步。 -跑法(仓库根目录):.venv/bin/python .claude/skills/db/scripts/sync_agent_registry.py +跑法(仓库根目录): + .venv/bin/python .claude/skills/access-database/scripts/sync_agent_registry.py --check + .venv/bin/python .claude/skills/access-database/scripts/sync_agent_registry.py """ +import argparse import json import re from pathlib import Path from db import connect # 复用锁死的 DSN(与 db.py 同目录,脚本目录自动在 sys.path) -ROOT = Path(__file__).resolve().parents[4] # .claude/skills/db/scripts → 仓库根 +ROOT = Path(__file__).resolve().parents[4] # .claude/skills/access-database/scripts → 仓库根 AGENTS_DIR = ROOT / ".claude" / "agents" SKILLS_DIR = ROOT / ".claude" / "skills" TABLE_RE = re.compile(r"\b(muse_[a-z_]+|example_[a-z_]+)\b") +SKILL_NAME_RE = re.compile(r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$") +SKILL_FRONTMATTER_KEYS = frozenset({"name", "description", "disable-model-invocation"}) def parse_frontmatter(text: str) -> dict: @@ -36,6 +41,40 @@ def parse_frontmatter(text: str) -> dict: return fm +def validate_skill_catalog(skills_dir: Path = SKILLS_DIR) -> list[tuple[Path, dict]]: + """校验 Skill 目录、frontmatter 与动作式名称,返回稳定排序的目录项。""" + + entries = [] + seen = set() + for sk in sorted(skills_dir.glob("*/SKILL.md")): + fm = parse_frontmatter(sk.read_text(encoding="utf-8")) + name = fm.get("name", "") + directory = sk.parent.name + unexpected = sorted(set(fm) - SKILL_FRONTMATTER_KEYS) + if unexpected: + raise ValueError(f"{sk}: frontmatter 含未登记字段: {unexpected}") + if not name: + raise ValueError(f"{sk}: frontmatter 缺少 name") + if directory != name: + raise ValueError(f"{sk}: 目录名 {directory!r} 与 name {name!r} 不一致") + if not SKILL_NAME_RE.fullmatch(name): + raise ValueError(f"{sk}: name 必须使用小写 动作-对象") + if name in seen: + raise ValueError(f"{sk}: Skill name 重复: {name}") + if not fm.get("description"): + raise ValueError(f"{sk}: frontmatter 缺少 description") + invocation_flag = fm.get("disable-model-invocation") + if invocation_flag is not None and invocation_flag not in {"true", "false"}: + raise ValueError( + f"{sk}: disable-model-invocation 必须是 true 或 false" + ) + seen.add(name) + entries.append((sk, fm)) + if not entries: + raise ValueError(f"{skills_dir}: 未发现任何 SKILL.md") + return entries + + def sync_roles(conn) -> int: n = 0 for md in sorted(AGENTS_DIR.glob("*.md")): @@ -61,10 +100,11 @@ def sync_roles(conn) -> int: def sync_skills(conn) -> int: n = 0 - for sk in sorted(SKILLS_DIR.glob("*/SKILL.md")): + names = [] + for sk, fm in validate_skill_catalog(): text = sk.read_text(encoding="utf-8") - fm = parse_frontmatter(text) - name = fm.get("name") or sk.parent.name + name = fm["name"] + names.append(name) tables = sorted(set(TABLE_RE.findall(text))) # 尽力抽取涉及的表名(不区分读写,待人工核) model_used = None if "MiniMax" in text: @@ -77,15 +117,29 @@ def sync_skills(conn) -> int: VALUES (%s,%s,%s::jsonb,%s,%s,CURRENT_TIMESTAMP,'sync_agent_registry','sync_agent_registry') ON CONFLICT (tenant_id, skill_name) DO UPDATE SET purpose=EXCLUDED.purpose, reads=EXCLUDED.reads, model_used=EXCLUDED.model_used, - source_ref=EXCLUDED.source_ref, synced_at=CURRENT_TIMESTAMP, updater='sync_agent_registry'""", + source_ref=EXCLUDED.source_ref, synced_at=CURRENT_TIMESTAMP, + updater='sync_agent_registry', deleted=FALSE""", (name, fm.get("description"), json.dumps(tables, ensure_ascii=False) if tables else None, model_used, str(sk.relative_to(ROOT)))) n += 1 + conn.execute( + """UPDATE example_skill + SET deleted=TRUE, synced_at=CURRENT_TIMESTAMP, updater='sync_agent_registry' + WHERE tenant_id=0 AND deleted=FALSE AND NOT (skill_name=ANY(%s))""", + (names,), + ) return n def main(): + parser = argparse.ArgumentParser(description="校验或同步 Agent/Skill 登记") + parser.add_argument("--check", action="store_true", help="只校验 Git 侧 Skill 目录,不连接数据库") + args = parser.parse_args() + catalog = validate_skill_catalog() + if args.check: + print(f"Skill 目录校验通过:{len(catalog)} 个") + return with connect() as conn: nr = sync_roles(conn) ns = sync_skills(conn) diff --git a/.claude/skills/db/scripts/test_authorization_snapshot_ddl.py b/.claude/skills/access-database/scripts/test_authorization_snapshot_ddl.py similarity index 100% rename from .claude/skills/db/scripts/test_authorization_snapshot_ddl.py rename to .claude/skills/access-database/scripts/test_authorization_snapshot_ddl.py diff --git a/.claude/skills/db/scripts/test_db_params.py b/.claude/skills/access-database/scripts/test_db_params.py similarity index 92% rename from .claude/skills/db/scripts/test_db_params.py rename to .claude/skills/access-database/scripts/test_db_params.py index c077a96..b63650b 100644 --- a/.claude/skills/db/scripts/test_db_params.py +++ b/.claude/skills/access-database/scripts/test_db_params.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """db execparams 参数装载逻辑离线自测(不连库)。 -跑法(仓库根目录):.venv/bin/python .claude/skills/db/scripts/test_db_params.py +跑法(仓库根目录):.venv/bin/python .claude/skills/access-database/scripts/test_db_params.py """ import io import json diff --git a/.claude/skills/access-database/scripts/test_skill_catalog.py b/.claude/skills/access-database/scripts/test_skill_catalog.py new file mode 100644 index 0000000..d239f1d --- /dev/null +++ b/.claude/skills/access-database/scripts/test_skill_catalog.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Skill 目录命名与 frontmatter 一致性的离线测试。""" + +import pathlib +import tempfile +import unittest + +from sync_agent_registry import validate_skill_catalog + + +class SkillCatalogTest(unittest.TestCase): + def test_current_catalog_is_valid(self): + entries = validate_skill_catalog() + self.assertGreater(len(entries), 0) + self.assertEqual(len(entries), len({fm["name"] for _, fm in entries})) + + def test_directory_and_name_must_match(self): + with tempfile.TemporaryDirectory() as tmp: + root = pathlib.Path(tmp) + skill = root / "plan-story" + skill.mkdir() + (skill / "SKILL.md").write_text( + "---\nname: planning\ndescription: test\n---\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "目录名"): + validate_skill_catalog(root) + + def test_name_must_be_action_object(self): + with tempfile.TemporaryDirectory() as tmp: + root = pathlib.Path(tmp) + skill = root / "runtime" + skill.mkdir() + (skill / "SKILL.md").write_text( + "---\nname: runtime\ndescription: test\n---\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "动作-对象"): + validate_skill_catalog(root) + + def test_unknown_frontmatter_key_is_rejected(self): + with tempfile.TemporaryDirectory() as tmp: + root = pathlib.Path(tmp) + skill = root / "plan-story" + skill.mkdir() + (skill / "SKILL.md").write_text( + "---\nname: plan-story\ndescription: test\nunknown: value\n---\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "未登记字段"): + validate_skill_catalog(root) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/read-context/SKILL.md b/.claude/skills/assemble-context/SKILL.md similarity index 96% rename from .claude/skills/read-context/SKILL.md rename to .claude/skills/assemble-context/SKILL.md index b5df194..8692eb5 100644 --- a/.claude/skills/read-context/SKILL.md +++ b/.claude/skills/assemble-context/SKILL.md @@ -1,6 +1,6 @@ --- -name: read-context -description: 统一创作数据读取器的操作合同。按冻结点从 PostgreSQL 读取可信来源,组装完整审计上下文,再为各角色生成最小可见投影。 +name: assemble-context +description: 按冻结点从 PostgreSQL 读取可信来源,组装可审计上下文,并为 writer、detector、judge、planner 或 extractor 生成最小投影。创作或评测调用模型前需要受控上下文时使用。 disable-model-invocation: true --- diff --git a/.claude/skills/read-context/scripts/assemble_writer_context.py b/.claude/skills/assemble-context/scripts/assemble_writer_context.py similarity index 100% rename from .claude/skills/read-context/scripts/assemble_writer_context.py rename to .claude/skills/assemble-context/scripts/assemble_writer_context.py diff --git a/.claude/skills/read-context/scripts/persist_context_freeze.py b/.claude/skills/assemble-context/scripts/persist_context_freeze.py similarity index 87% rename from .claude/skills/read-context/scripts/persist_context_freeze.py rename to .claude/skills/assemble-context/scripts/persist_context_freeze.py index c28c4c4..e099d66 100644 --- a/.claude/skills/read-context/scripts/persist_context_freeze.py +++ b/.claude/skills/assemble-context/scripts/persist_context_freeze.py @@ -12,8 +12,8 @@ import json import sys from pathlib import Path -# 复用 db skill 锁死的 DSN(.claude/skills/db/scripts) -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +# 复用 access-database Skill 锁死的 DSN +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 @@ -41,20 +41,24 @@ def persist_freeze(assemble_result, *, reference_work_id=None, reference_version as_of = ctx.get("asOf") if as_of is None: raise ValueError("assemble 结果缺 asOf,无法落冻结") + # 授权快照随冻结落库:接受前置检查(acceptance_state)重读它做实时比对, + # 不再信任编排层内存里的快照副本。 + authorization = ctx.get("authorizationSnapshot") + auth_json = json.dumps(authorization, ensure_ascii=False) if isinstance(authorization, dict) else None with connect() as conn: try: # manifest_sha256 唯一:同一冻结重放幂等。表是 append-only,冲突只能回读,不能 UPDATE。 row = conn.execute( "INSERT INTO example_context_freeze(work_id, target_chapter, as_of_chapter, manifest_sha256, " "context_sha256, reference_work_id, reference_version, arm_config, sections, token_budget, " - "omitted_sources, creator) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s,%s::jsonb,%s) " + "omitted_sources, authorization_snapshot, creator) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s,%s::jsonb,%s::jsonb,%s) " "ON CONFLICT (manifest_sha256) DO NOTHING " "RETURNING id, manifest_sha256, context_sha256", (work_id, target_chapter, as_of, manifest_sha, context_sha, reference_work_id, reference_version, json.dumps(arm_config, ensure_ascii=False) if arm_config is not None else None, json.dumps(sections, ensure_ascii=False), used_chars, - json.dumps(omitted, ensure_ascii=False), CREATOR)).fetchone() + json.dumps(omitted, ensure_ascii=False), auth_json, CREATOR)).fetchone() if not row: row = conn.execute( "SELECT id,manifest_sha256,context_sha256 FROM example_context_freeze " diff --git a/.claude/skills/read-context/scripts/retrieve_writer_sources.py b/.claude/skills/assemble-context/scripts/retrieve_writer_sources.py similarity index 99% rename from .claude/skills/read-context/scripts/retrieve_writer_sources.py rename to .claude/skills/assemble-context/scripts/retrieve_writer_sources.py index 2f75d07..f1cead3 100644 --- a/.claude/skills/read-context/scripts/retrieve_writer_sources.py +++ b/.claude/skills/assemble-context/scripts/retrieve_writer_sources.py @@ -43,9 +43,9 @@ class ProseRepository(Protocol): def _load_search_cards() -> Callable[..., list[dict[str, Any]]]: - """延迟导入 search skill,保持纯数据测试不触发嵌入依赖。""" + """延迟导入 search-knowledge,保持纯数据测试不触发嵌入依赖。""" - search_scripts = pathlib.Path(__file__).resolve().parents[2] / "search" / "scripts" + search_scripts = pathlib.Path(__file__).resolve().parents[2] / "search-knowledge" / "scripts" sys.path.insert(0, str(search_scripts)) from search import search_cards @@ -328,7 +328,7 @@ class ReplayCardIndexRepository: ) -> "ReplayCardIndexRepository": """复用 snapshot skill 的授权、冻结来源和内容泄露审计后构造仓储。""" - scripts = pathlib.Path(__file__).resolve().parents[2] / "snapshot" / "scripts" + scripts = pathlib.Path(__file__).resolve().parents[2] / "freeze-context" / "scripts" sys.path.insert(0, str(scripts)) from audit_leakage import audit_snapshot from check_snapshot import check_authorization, check_target_sources @@ -376,7 +376,7 @@ class FrozenProseRepository: def read_source_refs(self, *, work_id: int, as_of: int, source_refs: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]: """延迟导入 snapshot skill,避免复制 SQL 或建立第二套权限语义。""" - scripts = pathlib.Path(__file__).resolve().parents[2] / "snapshot" / "scripts" + scripts = pathlib.Path(__file__).resolve().parents[2] / "freeze-context" / "scripts" sys.path.insert(0, str(scripts)) from load_reference_work import load_frozen_prose_rows @@ -400,7 +400,7 @@ def load_confirmed_fine_outline(conn, *, work_id: int, target_chapter: int) -> d """读取指定章最新一条已确认细纲——read-context 是已确认细纲的唯一消费点。 取数合同与 planning SKILL 登记的 SQL 一致:section_type=fine_outline、state=confirmed、 - 未删除,按 version 倒序取最新。连接由调用方经 db skill 提供(本函数不自建连接、 + 未删除,按 version 倒序取最新。连接由调用方经 access-database 提供(本函数不自建连接、 不复制第二套权限语义)。缺已确认细纲或 payload 非法即失败关闭。 """ row = conn.execute( diff --git a/.claude/skills/read-context/scripts/test_assemble_writer_context.py b/.claude/skills/assemble-context/scripts/test_assemble_writer_context.py similarity index 100% rename from .claude/skills/read-context/scripts/test_assemble_writer_context.py rename to .claude/skills/assemble-context/scripts/test_assemble_writer_context.py diff --git a/.claude/skills/read-context/scripts/test_fine_outline_reader.py b/.claude/skills/assemble-context/scripts/test_fine_outline_reader.py similarity index 100% rename from .claude/skills/read-context/scripts/test_fine_outline_reader.py rename to .claude/skills/assemble-context/scripts/test_fine_outline_reader.py diff --git a/.claude/skills/read-context/scripts/test_fine_outline_unification.py b/.claude/skills/assemble-context/scripts/test_fine_outline_unification.py similarity index 100% rename from .claude/skills/read-context/scripts/test_fine_outline_unification.py rename to .claude/skills/assemble-context/scripts/test_fine_outline_unification.py diff --git a/.claude/skills/read-context/scripts/test_pattern_binding_reader.py b/.claude/skills/assemble-context/scripts/test_pattern_binding_reader.py similarity index 100% rename from .claude/skills/read-context/scripts/test_pattern_binding_reader.py rename to .claude/skills/assemble-context/scripts/test_pattern_binding_reader.py diff --git a/.claude/skills/read-context/scripts/test_retrieve_writer_sources.py b/.claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py similarity index 94% rename from .claude/skills/read-context/scripts/test_retrieve_writer_sources.py rename to .claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py index 2e6c92f..cd37871 100644 --- a/.claude/skills/read-context/scripts/test_retrieve_writer_sources.py +++ b/.claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py @@ -10,8 +10,8 @@ import unittest SCRIPT_DIR = pathlib.Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPT_DIR)) -sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "snapshot" / "scripts")) -sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "search" / "scripts")) +sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "freeze-context" / "scripts")) +sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "search-knowledge" / "scripts")) from load_reference_work import begin_read_snapshot # noqa: E402 from search import search_cards # noqa: E402 @@ -191,6 +191,28 @@ class RetrieveWriterSourcesTest(unittest.TestCase): for _ in range(3): self.assertEqual([item["cardId"] for item in stable_sort_cards(cards)], expected) + def test_plan_allows_asof_zero_for_first_chapter(self): + """开篇基线章:asOf=0 是合法冻结线,计划与过滤器都接受。""" + + plan = build_retrieval_plan( + run_id="run-ch1-baseline", + work_id=8, + target_chapter=1, + as_of=0, + fine_outline={ + "entities": [], + "relations": [], + "locations": [], + "powerSystems": [], + "hardConstraints": ["开篇建立基调"], + }, + card_index_version="cards-v1", + prose_index_version="prose-v1", + token_budget={"maxContextChars": 20000}, + ) + self.assertEqual(plan["asOf"], 0) + self.assertEqual(plan["filters"]["asOfChapter"], 0) + def test_plan_identity_excludes_run_id(self): other = copy.deepcopy(self.plan) other["runId"] = "run-b" diff --git a/.claude/skills/read-context/scripts/test_style_loader.py b/.claude/skills/assemble-context/scripts/test_style_loader.py similarity index 100% rename from .claude/skills/read-context/scripts/test_style_loader.py rename to .claude/skills/assemble-context/scripts/test_style_loader.py diff --git a/.claude/skills/read-context/scripts/test_writer_contract.py b/.claude/skills/assemble-context/scripts/test_writer_contract.py similarity index 90% rename from .claude/skills/read-context/scripts/test_writer_contract.py rename to .claude/skills/assemble-context/scripts/test_writer_contract.py index 3c1c05e..dbb44f4 100644 --- a/.claude/skills/read-context/scripts/test_writer_contract.py +++ b/.claude/skills/assemble-context/scripts/test_writer_contract.py @@ -148,7 +148,57 @@ def valid_output(context: dict | None = None, *, body: str = "第一段正文。 return build_candidate_envelope(context or valid_context(), valid_draft(body=body)) +def _resign(context: dict) -> None: + """改动上下文字段后重算计划/清单/上下文三级身份哈希。""" + + plan_payload = {key: value for key, value in context["retrievalPlan"].items() if key != "planId"} + context["retrievalPlan"]["planId"] = retrieval_identity(plan_payload) + context["retrievalManifest"]["planId"] = context["retrievalPlan"]["planId"] + manifest_payload = {key: value for key, value in context["retrievalManifest"].items() if key != "manifestId"} + context["retrievalManifest"]["manifestId"] = retrieval_identity(manifest_payload) + context["contextSnapshot"]["manifestId"] = context["retrievalManifest"]["manifestId"] + context["contextSnapshot"]["contextSha256"] = retrieval_identity(context) + + +def first_chapter_context() -> dict: + """开篇基线章上下文:asOf=0(开篇前冻结线),无历史正文,证据只有设定/大纲/细纲。""" + + context = valid_context() + context["targetChapter"] = 1 + context["asOf"] = 0 + context["retrievalPlan"]["asOf"] = 0 + context["retrievalPlan"]["filters"]["asOfChapter"] = 0 + context["proseEvidence"] = [] + _resign(context) + return context + + class WriterContractTest(unittest.TestCase): + def test_first_chapter_context_allows_asof_zero(self): + """开篇基线章:asOf=0 是合法冻结线,正文基线为空。""" + + normalized = validate_writer_context(first_chapter_context()) + self.assertEqual(normalized["asOf"], 0) + self.assertEqual(normalized["proseEvidence"], []) + + def test_asof_zero_rejects_any_historical_prose(self): + """asOf=0 冻结线下,连第 1 章正文都属于未来正文,必须失败关闭。""" + + context = first_chapter_context() + text = "第一章正文。" + context["proseEvidence"] = [{ + "evidenceId": "prose:1", "chapter": 1, + "sourceRef": {"sourceId": "chapter:1", "sourceVersion": "chapter-1-v1", + "chapter": 1, "blockId": 1, "startCodePoint": 0, + "endCodePoint": len(text)}, + "contentSha256": "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest(), + "purpose": "recent_full_chapter", "text": text, "isRecentBaseline": True, + }] + _resign(context) + with self.assertRaises(ContractError) as raised: + validate_writer_context(context) + self.assertIn("超出冻结线", str(raised.exception)) + def test_index_hints_are_strict_diagnostic_only_and_frozen(self): """诊断卡索引提示只能进入不可接受的诊断上下文。""" diff --git a/.claude/skills/read-context/scripts/writer_contract.py b/.claude/skills/assemble-context/scripts/writer_contract.py similarity index 100% rename from .claude/skills/read-context/scripts/writer_contract.py rename to .claude/skills/assemble-context/scripts/writer_contract.py diff --git a/.claude/skills/llm/SKILL.md b/.claude/skills/call-content-model/SKILL.md similarity index 82% rename from .claude/skills/llm/SKILL.md rename to .claude/skills/call-content-model/SKILL.md index 9457104..41686a4 100644 --- a/.claude/skills/llm/SKILL.md +++ b/.claude/skills/call-content-model/SKILL.md @@ -1,20 +1,20 @@ --- -name: llm -description: New-API LLM 调用统一入口(默认 MiniMax-M3)。管线内所有内容生产型 LLM 调用(清洗探测、拆书抽取等)必须经此 skill 发起,不得裸调外部服务;主会话模型只负责固化提示词、发起调用与守卫验证。 +name: call-content-model +description: 通过 New-API 的统一治理入口调用内容模型,执行额度窗口、模型降级、重试和 JSON 提取。清洗、拆书或知识审核需要 MiniMax 等内容模型时使用;不得裸调外部服务。 --- -# llm —— New-API 统一调用入口 +# 调用内容模型 创始人拍板(2026-07-13):清洗与拆书的内容生产 LLM **全部走 New-API 的 MiniMax-M3**;主会话(Fable5)只固化 agent/提示词/skill 与发起调用。本 skill 是唯一出口。 -管线内容生产调用的**标准入口是 `chat_governed`**(受 5 小时额度窗 + 全局降级链治理);`chat`/`chat` CLI 是不受治理的直连,仅供调试。治理政策的机械事实源是 `.claude/skills/llm/scripts/llm.py` + `test_quota.py`(AGENTS.md §6 点名),模型链切换必须由该 skill 治理并留下日志。 +管线内容生产调用的**标准入口是 `chat_governed`**(受 5 小时额度窗 + 全局降级链治理);`chat`/`chat` CLI 是不受治理的直连,仅供调试。治理政策的机械事实源是 `.claude/skills/call-content-model/scripts/llm.py` + `test_quota.py`(AGENTS.md §6 点名),模型链切换必须由该 skill 治理并留下日志。 ## 用法 管线内所有内容生产型 LLM 调用(清洗探测、拆书抽取、知识卡审核等)**必须用 `chat_governed`**: ```python -# 其他 skill 内 import(clean_detect / parse-book / review-cards 的标准姿势) +# 其他 skill 内 import(clean_detect / deconstruct-book / review-knowledge-cards 的标准姿势) sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) from llm import chat_governed, extract_json @@ -47,7 +47,7 @@ data = extract_json(content) ```bash # 仅调试:读 prompt 文件直连调用,内容打到 stdout(token 用量与重试日志走 stderr) -.venv/bin/python .claude/skills/llm/scripts/llm.py chat --prompt-file /tmp/p.txt [--model MiniMax-M3] [--max-tokens 32000] [--out /tmp/resp.txt] [--extract-json] +.venv/bin/python .claude/skills/call-content-model/scripts/llm.py chat --prompt-file /tmp/p.txt [--model MiniMax-M3] [--max-tokens 32000] [--out /tmp/resp.txt] [--extract-json] ``` ## 内建保障(chat 与 chat_governed 共用的单次调用机制,调用方不必重复实现) @@ -63,4 +63,4 @@ data = extract_json(content) - token 为 New-API **普通令牌**(明文入仓是仓库政策);严禁改用管理令牌打 /v1。 - 可用模型以 `/v1/models` 为准(2026-07-13 在列:MiniMax-M3 / M2.x 系 / deepseek-v4-* / glm-5.2 / Qwen 嵌入与重排)。 -- 嵌入调用不走本 skill(已有 embed skill,模型与维度钉死)。 +- 嵌入调用不走本 Skill(已有 `embed-knowledge`,模型与维度钉死)。 diff --git a/.claude/skills/llm/scripts/llm.py b/.claude/skills/call-content-model/scripts/llm.py similarity index 98% rename from .claude/skills/llm/scripts/llm.py rename to .claude/skills/call-content-model/scripts/llm.py index 71fa90c..8dbd8fb 100644 --- a/.claude/skills/llm/scripts/llm.py +++ b/.claude/skills/call-content-model/scripts/llm.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""llm skill:New-API 统一调用入口(默认 MiniMax-M3)。 +"""call-content-model Skill:New-API 统一调用入口(默认 MiniMax-M3)。 管线内所有内容生产型 LLM 调用(清洗探测/拆书抽取)必须经此入口: - trust_env=False(本机代理环境变量会劫持内网直连,教训固化); @@ -59,10 +59,14 @@ class PlanQuotaExhausted(Exception): def _default_persist_call(event): - """按需加载 runtime 持久化器,避免 llm 单测和纯离线调用被迫连库。""" - runtime_scripts = pathlib.Path(__file__).resolve().parents[2] / "runtime" / "scripts" - if str(runtime_scripts) not in sys.path: - sys.path.insert(0, str(runtime_scripts)) + """按需加载运行证据持久化器,避免离线调用被迫连库。""" + evidence_scripts = ( + pathlib.Path(__file__).resolve().parents[2] + / "record-run-evidence" + / "scripts" + ) + if str(evidence_scripts) not in sys.path: + sys.path.insert(0, str(evidence_scripts)) from persist_llm_call import persist_call return persist_call(event) diff --git a/.claude/skills/llm/scripts/test_call_persistence.py b/.claude/skills/call-content-model/scripts/test_call_persistence.py similarity index 100% rename from .claude/skills/llm/scripts/test_call_persistence.py rename to .claude/skills/call-content-model/scripts/test_call_persistence.py diff --git a/.claude/skills/llm/scripts/test_quota.py b/.claude/skills/call-content-model/scripts/test_quota.py similarity index 100% rename from .claude/skills/llm/scripts/test_quota.py rename to .claude/skills/call-content-model/scripts/test_quota.py diff --git a/.claude/skills/capture-ai-flavor-cases/SKILL.md b/.claude/skills/capture-ai-flavor-cases/SKILL.md new file mode 100644 index 0000000..72d999b --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/SKILL.md @@ -0,0 +1,98 @@ +--- +name: capture-ai-flavor-cases +description: 从已有作品或创作反馈中抽取可复核的 AI 味案例卡,保留来源哈希与位置并产出规则候选。需要反向积累人感样例、记录一次写作事故或把重复观察送入规则评审时使用;不直接修改正文、范式或生产规则。 +disable-model-invocation: true +--- + +# 抽取 AI 味案例卡 + +## 目的与边界 + +本 Skill 只生产质量证据层的 `ai_flavor_case` 卡片。它不是作品实体卡,也不是公共范式卡;卡片默认处于 `shadow`,不能进入生成上下文,不能直接改变正文或规则。 + +输入有两条来源链: + +- `backfill`:扫描已有作品,记录表面候选、全文哈希、章节/行位置和上下文。来源未明确授权时只保存哈希与位置,不把原文写入仓库。 +- `live_feedback`:记录创作中发现的具体问题、候选正文哈希和人工判断,允许快速入卡,但同样先停在 `shadow`。 + +确定性脚本负责发现、哈希、状态、结构门禁和自动落库;“这是 AI 味还是有意写法”由人工/独立评审标注。不得把正例样本单独归纳成全局规则。 + +## 数据与副作用合同 + +- 读取:用户明确提供的文本文件,以及本 Skill 输出的案例卡 YAML/JSON。 +- 写入:检测命令指定的回执文件,以及 `muse-example` 中的案例卡与重验证账本;不写正文、`knowledge/` 正式资产或生产规则目录。 +- 自动落库:`scan`、`inventory`、`feedback` 在检测完成后自动以一个事务写入数据库。`--offline` 是显式例外,只用于离线合同测试或数据库恢复准备;不能把离线文件当正式内容。 +- 恢复入口:`persist_cases.py` 只用于把已审计的 inventory/revalidation 文件恢复或迁移入库,正常检测不得依赖它单独执行。 +- 模型:扫描、哈希、校验和候选归纳前置门不调用模型;语义标注可由独立评审完成,结果必须回写卡片的 review 字段。 +- 失败:任何来源、哈希、状态或反例门失败都返回 `CASE_CARD_CONTRACT_FAILED`,不输出部分成功的规则。 + +## 运行 + +```bash +# 既有作品反向扫描;research_only 是默认安全值,输出 hash-only 卡 +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py scan \ + /path/to/work.txt --work-ref work-7 --output /tmp/ai-flavor-cases.yaml +# 命令结束时自动写入 muse-example;同时生成 .inventory.json/.revalidation.json 回执 + +# 批量回填并固化可复核清单;只写来源哈希、位置和观察,不复制第三方正文 +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py inventory \ + /path/to/works --source-root-ref 小说清单 --output /path/to/backfill-inventory.json \ + --generated-on 2026-08-13 +# 8 本作品的卡与逐卡重验证在同一检测运行中自动入库 + +# 创作反馈快速入卡(正文只在本次受控输入中读取) +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py feedback \ + --text-file /tmp/candidate.txt --work-ref work-12 --run-ref run-abc \ + --issue "段尾抽象升华没有功能" --output /tmp/feedback-card.yaml +# 创作反馈也会自动入库,保留 run_ref 绑定 + +# 明确只做离线构造(不会写库) +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py inventory \ + /path/to/works --output /tmp/inventory.json --offline + +# 对卡片做结构与来源门禁 +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py validate \ + /tmp/ai-flavor-cases.yaml + +# 在确认/投影/规则使用前手工重验证来源;默认自动追加数据库回执。 +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py revalidate \ + /path/to/ai-flavor-cases.yaml --source-root /path/to/source-root \ + --checked-on 2026-08-14 --output /tmp/ai-flavor-revalidation.json +# 仅需离线回执时显式加 --offline + +# 只有已标注正反证据且来源重验证通过时才生成 candidate 规则;命令不会生成 active +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py propose-rule \ + --cards /tmp/cards-a.yaml /tmp/cards-b.yaml \ + --rule-id candidate-lexical-001 --name "空洞元话语" \ + --verification /tmp/ai-flavor-revalidation.json \ + --output /tmp/rule-candidate.yaml +``` + +`owned`、`licensed` 和 `public_domain` 才允许把片段写入卡;`research_only`、`unauthorized` 只能 hash-only,且永远不能确认。`source_sha256` 针对原始文件字节,`excerpt_sha256` 针对保存的片段,二者不可由模型自报。 +重验证不会在看板打开时自动发生;它是确认、样例投影和规则消费前的显式 fail-closed 门。来源找不到不是“仍然有效”,而是 `unavailable`。 + +## 状态与升级 + +1. `scan`/`feedback` 产生 `shadow + unclassified`。 +2. 人工标注为 `sf`(确实应修)、`snf`(表面相似但不应修)、`boundary` 或 `regression`;标注不等于确认。 +3. 来源重验证结果为 `verified` 后,只有可审阅且获授权的卡才能 `canonical`,再投影为样例。 +4. 规则候选至少需要两个不同来源作品,并同时包含一张 `sf` 与一张 `snf/boundary/regression` 卡;所有引用卡必须有 `verified` 回执。候选状态固定为 `candidate`。四类样例、跨任务回放和规则评审完成后,才由现有质量链决定是否激活。 + +详细字段和失败码见 [`references/case-card-contract.md`](references/case-card-contract.md)。 + +首版回填清单与候选规则种子见 [`references/fixtures/`](references/fixtures/);其中既有作品只保留 hash/位置,不能直接确认。 +`backfill-inventory-*.json` 与 `revalidation-*.json` 是可复核的导出/恢复证据;正式内容在 `muse-example` 的 +`example_ai_flavor_case`、`example_ai_flavor_revalidation_batch`、`example_ai_flavor_revalidation` 三张表。 +`dashboard/server.py` 的 `/ai-flavor` 默认查这三张表,数据库不可用时才明确标注离线回退;页面不会因打开而重新读取原文。 + +状态语义:案例卡 `shadow` 只供复核,`canonical` 仅表示获授权且完成评审,`rejected`/`archived` 不进入生成上下文;重验证 `verified` 才能确认、投影样例或消费规则,`stale`(全文哈希变化)、`unavailable`(来源不可得)和 `card_mismatch`(锚点变化)都使当前卡在这些动作上失效,但历史回执保留。 + +## 机械验收 + +```bash +.venv/bin/python .claude/skills/capture-ai-flavor-cases/scripts/test_capture_cases.py +.venv/bin/python .claude/skills/access-database/scripts/test_skill_catalog.py +git diff --check +``` + +测试必须覆盖:来源 hash、重验证 verified/stale/unavailable/card_mismatch、未授权 hash-only、重复 ID、live feedback 来源绑定、shadow 不能投影样例、缺重验证回执不能确认,以及跨作品/反例门。 diff --git a/.claude/skills/capture-ai-flavor-cases/references/case-card-contract.md b/.claude/skills/capture-ai-flavor-cases/references/case-card-contract.md new file mode 100644 index 0000000..23085bb --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/case-card-contract.md @@ -0,0 +1,55 @@ +# AI 味案例卡合同 v1 + +案例卡是质量与复利领域的证据载体。它记录“某段文本被怀疑有 AI 味,或被证明不应改”的可复核观察,不承担作品事实、公共范式或生产规则的职责。 + +## 必填字段 + +| 字段 | 合同 | +|---|---| +| `schema_version` | 固定 `ai-flavor-case-v1` | +| `id` | `case-` 加稳定哈希前缀;同一来源位置和模式只能有一个 ID | +| `card_type` | 固定 `ai_flavor_case` | +| `state` | `shadow` / `canonical` / `rejected` / `archived` | +| `label` | `unclassified` / `sf` / `snf` / `boundary` / `regression` | +| `layer` | `mechanical` / `lexical` / `structural` / `density` / `semantic` / `unknown` | +| `carrier` | `narration` / `dialogue` / `monologue` / `in_text_carrier` / `mixed` / `unknown` | +| `capture_mode` | `backfill` / `live_feedback` / `synthetic` / `review_import` | +| `source` | 来源种类、许可、`source_sha256`、位置和 `work_ref` | +| `observation` | 表面模式、问题判断、功能检查项、改动风险和建议动作 | + +## 来源重验证 + +- `revalidate` 在使用卡片前重新读取受控来源的原始字节,计算当前 `source_sha256`,并与卡片保存的值比较。 +- 全文哈希相同后还会检查 `location` 的 `excerpt_sha256` 与 `surface_location`;三者任一不一致,结果为 `card_mismatch`。 +- 重验证结果是独立的不可变回执,不自动修改卡片的 `state`。结果状态固定为:`verified`(来源与锚点一致)、`stale`(全文哈希变化)、`unavailable`(来源不可读取)、`card_mismatch`(全文相同但卡片锚点不一致)。 +- `canonical` 确认、样例投影和规则候选/规则使用必须携带对应卡片的 `verified` 回执;缺回执或回执哈希不匹配时 fail-closed。 +- 检测命令完成时会把案例卡和重验证回执自动写入 `muse-example`;看板只读展示历史批次,打开页面不触发原文读取,也不写库。只有显式 `--offline` 才生成不入库的回执。 + +## 使用范围与失效 + +- `shadow`:只用于人工复核、聚类和候选提示;不能进入生成上下文、不能投影样例、不能成为 blocking 规则。 +- `canonical`:必须有可保存授权、人工标签/评审和 `verified` 重验证;只表示这条证据可共享,不表示规则已经 active。 +- `rejected` / `archived`:保留审计,不参与当前消费。 +- `verified`:当前来源字节、片段锚点和表面锚点均一致,可用于确认、样例投影或规则候选评审。 +- `stale` / `unavailable` / `card_mismatch`:当前使用立即失效;重新取得合法来源并重验证后才恢复。历史卡和历史回执不删除。 +- 作用域默认是该卡的 `work_ref + source_ref + source_sha256 + capture_mode`;规则候选还必须满足跨作品和正反证据门。单卡、单作品或单次反馈不能扩大为全局规则。 + +## 来源规则 + +- `source_sha256` 是原始文件字节的 SHA-256 小写十六进制值。 +- `excerpt_sha256` 是保存片段 UTF-8 字节的 SHA-256;没有片段时也必须保留它,便于在受控原文环境复核。 +- `research_only` / `unauthorized` 来源必须 `excerpt: ""`、`context: ""`,状态只能是 `shadow`。 +- `owned` / `licensed` / `public_domain` / `synthetic` 才能保存片段;`canonical` 必须有可审阅片段、非 `unclassified` 标签和 `review` 记录。 +- 位置使用 `line_start`、`line_end`、`char_start`、`char_end`;哈希和位置不足以证明内容时,卡不能确认。 + +## 证据与规则边界 + +- `sf` 只表示“当前评审认为应修”,不是永远删除。 +- `snf` 表示同一表面形式在上下文中有功能,提醒系统不要误修。 +- `boundary` 表示需要任务合同或上下文裁决。 +- `regression` 记录一次错误修复及其后果,优先用于回归门。 +- 单张卡不能生成 active 规则。候选规则必须同时引用正反证据,且至少来自两个不同作品;规则状态由代码固定为 `candidate`。 + +## 与现有资产的关系 + +`canonical` 案例卡可以投影成四类样例,但样例必须保留 `case_card_id`、来源位置和许可。规则仍由 `review-knowledge-cards` 与质量/回放链审核;案例卡不替代 `meta/schemas` 的实体/范式卡。 diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/README.md b/.claude/skills/capture-ai-flavor-cases/references/fixtures/README.md new file mode 100644 index 0000000..bf0554b --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/README.md @@ -0,0 +1,13 @@ +# 首版 AI 味案例夹具 + +这些文件是检测运行的导出/恢复证据;正式内容自动写入 PostgreSQL `muse-example`,不是靠这些文件承载。 + +- `backfill-hash-only.yaml`:从本地既有作品扫描得到的第一批候选;只保留全文哈希、片段哈希、位置和待复核观察,不含第三方正文。 +- `backfill-inventory-2026-08-13.json`:对 `小说清单/` 8 本作品的批量回填导出,共 788 张 hash-only Shadow 卡;同一 `inventory` 命令已经自动写入 `example_ai_flavor_case`。 +- `revalidation-2026-08-14.json`:同一检测运行的来源重验证回执;当前 `verified=788`、`stale=0`、`unavailable=0`、`card_mismatch=0`,同时追加到 `example_ai_flavor_revalidation_batch` + `example_ai_flavor_revalidation`。 +- `canonical-samples.yaml`:公版/合成短片段的已确认样例,用于验证卡→样例投影和反例门。 +- `rule-candidates.yaml`:由两类以上来源、同时含正反证据的候选规则;状态固定为 `candidate`,不能直接加载为生产 `active`。 + +页面入口:启动 `dashboard/server.py` 后访问 `/ai-flavor`。页面默认只读展示 PostgreSQL 正式表;数据库不可用时才显示离线回退。确认、样例投影和规则消费必须携带 `verified` 回执。 + +样例标签含义:`sf` = 当前评审认为应修,`snf` = 表面相似但有功能不应修,`boundary` = 需上下文裁决,`regression` = 错误修复回归。 diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-hash-only.yaml b/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-hash-only.yaml new file mode 100644 index 0000000..175c57c --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-hash-only.yaml @@ -0,0 +1,80 @@ +schema_version: ai-flavor-case-v1 +cards: + - schema_version: ai-flavor-case-v1 + id: case-backfill-jpxh-stock-expression-001 + card_type: ai_flavor_case + state: shadow + label: unclassified + layer: lexical + carrier: unknown + capture_mode: backfill + excerpt: '' + context: '' + source: + kind: existing_work + license: research_only + source_sha256: b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e + excerpt_sha256: 109835f69abc25b5c26ee1ed02a65957bd26407232125bad59dc4f0502a8eec1 + source_ref: 小说清单/机破星河_当年离歌.txt + location: {line_start: 5955, line_end: 5955, char_start: 95420, char_end: 95436, column_start: 4, column_end: 20} + surface_location: {line_start: 5955, line_end: 5955, char_start: 95423, char_end: 95427, column_start: 7, column_end: 11} + work_ref: ref-work-机破星河 + observation: + pattern_key: lexical.stock_micro_expression + surface: 嘴角勾起 + diagnosis: 库存微表情候选;需检查是否是角色签名动作或场景独有反应。 + function_check: [是否承担具体叙事功能, 是否是角色/场内载体的有意声线] + risk_if_changed: 未经上下文复核直接删除可能损失人物声音、伏笔或节奏。 + suggested_action: 保留 shadow,回到受控原文复核。 + - schema_version: ai-flavor-case-v1 + id: case-backfill-jpxh-atmosphere-001 + card_type: ai_flavor_case + state: shadow + label: unclassified + layer: lexical + carrier: unknown + capture_mode: backfill + excerpt: '' + context: '' + source: + kind: existing_work + license: research_only + source_sha256: b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e + excerpt_sha256: 3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5 + source_ref: 小说清单/机破星河_当年离歌.txt + location: {line_start: 46431, line_end: 46431, char_start: 574508, char_end: 574547, column_start: 68, column_end: 107} + surface_location: {line_start: 46431, line_end: 46431, char_start: 574529, char_end: 574535, column_start: 89, column_end: 95} + work_ref: ref-work-机破星河 + observation: + pattern_key: lexical.abstract_atmosphere + surface: 空气仿佛凝固 + diagnosis: 抽象气氛候选;需检查是否有具体感官或场面功能支撑。 + function_check: [是否承担具体叙事功能, 是否只是空泛气氛标签] + risk_if_changed: 直接删改可能抹掉高潮节奏或群体反应。 + suggested_action: 保留 shadow,回到受控原文复核。 + - schema_version: ai-flavor-case-v1 + id: case-backfill-xhsm-stock-expression-001 + card_type: ai_flavor_case + state: shadow + label: unclassified + layer: lexical + carrier: unknown + capture_mode: backfill + excerpt: '' + context: '' + source: + kind: existing_work + license: research_only + source_sha256: dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59 + excerpt_sha256: 02a168cf14ed2445845536b1131dfd70f67cd654e691278497af8d4e1a6523e1 + source_ref: 小说清单/星环使命(虚伪王庭).txt + location: {line_start: 10694, line_end: 10694, char_start: 312102, char_end: 312152, column_start: 0, column_end: 50} + surface_location: {line_start: 10694, line_end: 10694, char_start: 312112, char_end: 312118, column_start: 10, column_end: 16} + work_ref: ref-work-星环使命 + observation: + pattern_key: lexical.stock_micro_expression + surface: 嘴角微微上扬 + diagnosis: 与其他作品出现同类表面形式;不能仅凭跨书频次认定应修。 + function_check: [是否为角色签名动作, 是否在本段承担关系推进] + risk_if_changed: 误删可能抹平角色差异。 + suggested_action: 等待正反证据和人工标注。 diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-inventory-2026-08-13.json b/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-inventory-2026-08-13.json new file mode 100644 index 0000000..7a4d9c3 --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/backfill-inventory-2026-08-13.json @@ -0,0 +1,37139 @@ +{ + "schema_version": "ai-flavor-inventory-v1", + "capture_schema_version": "ai-flavor-case-v1", + "generated_on": "2026-08-13", + "source_root_ref": "小说清单", + "source_kind": "existing_work", + "source_license": "research_only", + "max_cards_per_work": 5000, + "totals": { + "books": 8, + "cards": 788, + "pattern_counts": { + "lexical.abstract_atmosphere": 48, + "lexical.meta_disclaimer": 317, + "lexical.stock_micro_expression": 423 + } + }, + "works": [ + { + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "card_count": 106, + "pattern_counts": { + "lexical.abstract_atmosphere": 2, + "lexical.stock_micro_expression": 104 + } + }, + { + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "card_count": 83, + "pattern_counts": { + "lexical.abstract_atmosphere": 1, + "lexical.meta_disclaimer": 16, + "lexical.stock_micro_expression": 66 + } + }, + { + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "card_count": 19, + "pattern_counts": { + "lexical.meta_disclaimer": 19 + } + }, + { + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "card_count": 173, + "pattern_counts": { + "lexical.meta_disclaimer": 173 + } + }, + { + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "card_count": 8, + "pattern_counts": { + "lexical.meta_disclaimer": 7, + "lexical.stock_micro_expression": 1 + } + }, + { + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "card_count": 226, + "pattern_counts": { + "lexical.abstract_atmosphere": 44, + "lexical.meta_disclaimer": 31, + "lexical.stock_micro_expression": 151 + } + }, + { + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "card_count": 61, + "pattern_counts": { + "lexical.meta_disclaimer": 5, + "lexical.stock_micro_expression": 56 + } + }, + { + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "card_count": 112, + "pattern_counts": { + "lexical.abstract_atmosphere": 1, + "lexical.meta_disclaimer": 66, + "lexical.stock_micro_expression": 45 + } + } + ], + "cards": [ + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0b9ed3af70a7dbf315fc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0916a041ff13f51cc739b5948d904492d8bf6c370458e418e3959e72a9666383", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 23674, + "line_end": 23674, + "char_start": 255103, + "char_end": 255132, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 23674, + "line_end": 23674, + "char_start": 255113, + "char_end": 255119, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-aae465436b237ca0bde1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "da714c791b0a4352636d832c519bc50bcf33fbb003195162efa6b18f19a7ceb5", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 43693, + "line_end": 43693, + "char_start": 492633, + "char_end": 492682, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 43693, + "line_end": 43693, + "char_start": 492635, + "char_end": 492641, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a6dfe32afecea75926b4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "91e904e4cb7a3862a46dbc3e5d1f54909b3e4ef6eee86fae7828aa74059a289b", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 43750, + "line_end": 43750, + "char_start": 493267, + "char_end": 493294, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 43750, + "line_end": 43750, + "char_start": 493269, + "char_end": 493275, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7ca8d1dc58f50d116b03", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "e05ab20a58a434ab241a7d4e8eacd3a419288970e5b654540362822148f9d518", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 52222, + "line_end": 52222, + "char_start": 586614, + "char_end": 586630, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 52222, + "line_end": 52222, + "char_start": 586616, + "char_end": 586622, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e6da75c08e065e33f827", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "3176f7816ba371e1e34a7d9cf1821c85f79bfd8799775e63eef71d0e8a122850", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 84184, + "line_end": 84184, + "char_start": 995418, + "char_end": 995448, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 84184, + "line_end": 84184, + "char_start": 995427, + "char_end": 995433, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-55a6f6af8dcf3cd0f345", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "81364ff9665f954806cfe03c3ce07c27c8ac73d319711ed62f31958d004cb75b", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 85708, + "line_end": 85708, + "char_start": 1013682, + "char_end": 1013709, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 85708, + "line_end": 85708, + "char_start": 1013685, + "char_end": 1013691, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-383f8f97a73ed1baaff5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ce306c5d1002264ee86bbc27104422e72b378f41ff991c24278e27ea9941df65", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 86242, + "line_end": 86242, + "char_start": 1020648, + "char_end": 1020664, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 86242, + "line_end": 86242, + "char_start": 1020650, + "char_end": 1020656, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cbb7d8655ec26019e8de", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "1a363297bc1706ae73873c577f8fd8acab311ba1c74c3e68a2e988ceeadd2504", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 92905, + "line_end": 92905, + "char_start": 1105467, + "char_end": 1105515, + "column_start": 0, + "column_end": 48 + }, + "surface_location": { + "line_start": 92905, + "line_end": 92905, + "char_start": 1105478, + "char_end": 1105484, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-57f6e02e01b24bd1285f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "4dfe72fd4f6009c1e5b04b8ce55038e5064fc80fa0ee18f5b83883a566e49ad1", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 93394, + "line_end": 93394, + "char_start": 1111197, + "char_end": 1111275, + "column_start": 0, + "column_end": 78 + }, + "surface_location": { + "line_start": 93394, + "line_end": 93394, + "char_start": 1111202, + "char_end": 1111208, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5d7839147487b06620bb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "2ba4722fc9b06821d260a6c60be9fe34ee1d1318979f696211109825d345e3c6", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 97396, + "line_end": 97396, + "char_start": 1160909, + "char_end": 1160947, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 97396, + "line_end": 97396, + "char_start": 1160940, + "char_end": 1160946, + "column_start": 31, + "column_end": 37 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-72030eb77fa51db6c98a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "551f26ee6c43214990536f7fb2ac4e45ae504cbd3620fa148a9c4a88d1f5a50e", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 97699, + "line_end": 97699, + "char_start": 1164876, + "char_end": 1164909, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 97699, + "line_end": 97699, + "char_start": 1164897, + "char_end": 1164903, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3d2b52ad3ede0f2e70a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "f267199f5f657dd7b8c85a698db9d0ed2ad9ba491f804c99b075a95ae20d779a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 100294, + "line_end": 100294, + "char_start": 1193920, + "char_end": 1193949, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 100294, + "line_end": 100294, + "char_start": 1193942, + "char_end": 1193948, + "column_start": 22, + "column_end": 28 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b086b59dd56e87e8339f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "8b9cd29586a4c027e4c2e5264b405e5916f894d98b32ad76d90160fb82215788", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 103297, + "line_end": 103297, + "char_start": 1232340, + "char_end": 1232354, + "column_start": 11, + "column_end": 25 + }, + "surface_location": { + "line_start": 103297, + "line_end": 103297, + "char_start": 1232345, + "char_end": 1232351, + "column_start": 16, + "column_end": 22 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d4423abfd1d833bd8860", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "bc74090d2f1ca22c3c176964537920ecfabe86ab8fabe62bd779a213b75ceb87", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 103444, + "line_end": 103444, + "char_start": 1233980, + "char_end": 1234007, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 103444, + "line_end": 103444, + "char_start": 1233983, + "char_end": 1233989, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-db9e35101650e4a037c9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "3f4a1762b166f534c5678a964a32136daf656f1060ec070a589ee2639d8311c7", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 104035, + "line_end": 104035, + "char_start": 1240680, + "char_end": 1240703, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 104035, + "line_end": 104035, + "char_start": 1240684, + "char_end": 1240690, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c1d3a06e204313b77614", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "4046f7db32cdfa648abbd0acc6aa2db2c5efaeb2a40a772101604a6ea1bc8d93", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 107359, + "line_end": 107359, + "char_start": 1284384, + "char_end": 1284419, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 107359, + "line_end": 107359, + "char_start": 1284391, + "char_end": 1284397, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d8b79723415b01efefaa", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "133bd89fb09b13d62386e5334052996401cbb6f9c53b0577f4e161a7b3f9ea76", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 111010, + "line_end": 111010, + "char_start": 1333462, + "char_end": 1333524, + "column_start": 0, + "column_end": 62 + }, + "surface_location": { + "line_start": 111010, + "line_end": 111010, + "char_start": 1333476, + "char_end": 1333482, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d4d1cc58454fe62328cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "815e1e8aff6127fdca946bbb85264195819d07905968c3ab382f98d5d4e87d00", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 114439, + "line_end": 114439, + "char_start": 1377564, + "char_end": 1377619, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 114439, + "line_end": 114439, + "char_start": 1377566, + "char_end": 1377572, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-be911d44b75b1505bdce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "05c619993c4de3e5411a15bce7ddbde6535f08967e2197ae8ff20b35a3b46777", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 114862, + "line_end": 114862, + "char_start": 1382000, + "char_end": 1382027, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 114862, + "line_end": 114862, + "char_start": 1382002, + "char_end": 1382008, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ead8c98296b8b79e8856", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ad4bb5b0c045387f402cdc32756d5fb5da341613137a256da62ae480e0c0aa3f", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 114970, + "line_end": 114970, + "char_start": 1383154, + "char_end": 1383179, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 114970, + "line_end": 114970, + "char_start": 1383166, + "char_end": 1383172, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1b1fea67dcb41a9273ed", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "499747757df261c39ef7d22a1fd8f32cce459e2a4eac72446d35c9c16fb737b2", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 114988, + "line_end": 114988, + "char_start": 1383311, + "char_end": 1383425, + "column_start": 0, + "column_end": 114 + }, + "surface_location": { + "line_start": 114988, + "line_end": 114988, + "char_start": 1383313, + "char_end": 1383319, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-41db0461da49faefc77e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "a953cf43ae277568a23405417eaf3491b8fd005245a18e5cc687bd3835b317a2", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 117085, + "line_end": 117085, + "char_start": 1408569, + "char_end": 1408581, + "column_start": 7, + "column_end": 19 + }, + "surface_location": { + "line_start": 117085, + "line_end": 117085, + "char_start": 1408572, + "char_end": 1408578, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a0c16a455b5cacf01120", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "92ea2707a133257b4e155bbc96abbba9f3784999cdd17444bb9e08e7037c3a47", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 117541, + "line_end": 117541, + "char_start": 1414032, + "char_end": 1414060, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 117541, + "line_end": 117541, + "char_start": 1414042, + "char_end": 1414048, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cb11100bcbfe263e6f4c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "1e04c1031f33672bbe2bb604a6f4fdba1f9464b21e2f40d5eace477bb044a072", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 118246, + "line_end": 118246, + "char_start": 1422618, + "char_end": 1422647, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 118246, + "line_end": 118246, + "char_start": 1422620, + "char_end": 1422626, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ea02cd145557edc41a1f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "31ffe78f5fdac13d842c05eb990df8f2ade401a13d8edd04fb05ce9fc32f280a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 119539, + "line_end": 119539, + "char_start": 1439000, + "char_end": 1439068, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 119539, + "line_end": 119539, + "char_start": 1439032, + "char_end": 1439038, + "column_start": 32, + "column_end": 38 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-199629f660cf5ba7a0ea", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "2e2cc9fb9908a87e344a52f4f9670b72b74a841711f021127937c4482754874a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 122323, + "line_end": 122323, + "char_start": 1477541, + "char_end": 1477561, + "column_start": 15, + "column_end": 35 + }, + "surface_location": { + "line_start": 122323, + "line_end": 122323, + "char_start": 1477544, + "char_end": 1477550, + "column_start": 18, + "column_end": 24 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c730db47328b17c70396", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "eeec08c9a047d8d7a014b171a0ba4148e94ebd57e58929056190119babf092e6", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 122761, + "line_end": 122761, + "char_start": 1483401, + "char_end": 1483513, + "column_start": 0, + "column_end": 112 + }, + "surface_location": { + "line_start": 122761, + "line_end": 122761, + "char_start": 1483418, + "char_end": 1483424, + "column_start": 17, + "column_end": 23 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4347fb17095cc7e298bc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "a9141a5a9d753bcb0e855e1cb1f859d19010b467bddf9550694dad7bbf83209c", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 123067, + "line_end": 123067, + "char_start": 1487559, + "char_end": 1487670, + "column_start": 0, + "column_end": 111 + }, + "surface_location": { + "line_start": 123067, + "line_end": 123067, + "char_start": 1487568, + "char_end": 1487574, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5a3c6dd3035e289df802", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "5fa45ae045156164937d7fc71a00f1f6f1cff75c4825a9b25f01bf2897103147", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 129739, + "line_end": 129739, + "char_start": 1573560, + "char_end": 1573608, + "column_start": 0, + "column_end": 48 + }, + "surface_location": { + "line_start": 129739, + "line_end": 129739, + "char_start": 1573580, + "char_end": 1573586, + "column_start": 20, + "column_end": 26 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-897a65f47205b3dec3e9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "39f3bedc668965ca65d10b61edbf54ea0e12319863586486a16574ee282bb1bc", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 130099, + "line_end": 130099, + "char_start": 1578627, + "char_end": 1578649, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 130099, + "line_end": 130099, + "char_start": 1578629, + "char_end": 1578635, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8e8c84711e01c8d2d736", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "55f0173206584ce1b90972725b79a41d3b80357524d4da2a5d6ea53c07c35993", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 134932, + "line_end": 134932, + "char_start": 1647076, + "char_end": 1647096, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 134932, + "line_end": 134932, + "char_start": 1647089, + "char_end": 1647095, + "column_start": 13, + "column_end": 19 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-40333f96b45f47cc74d4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "8204d5f8b5f884a031b6abd2fc7656ea155006ab58a9f6024f29c8ad02e276d8", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 140155, + "line_end": 140155, + "char_start": 1717430, + "char_end": 1717444, + "column_start": 30, + "column_end": 44 + }, + "surface_location": { + "line_start": 140155, + "line_end": 140155, + "char_start": 1717435, + "char_end": 1717441, + "column_start": 35, + "column_end": 41 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-590f7a19ad44991f2dc9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b55479080bf7eaaafdaa67f54f68346f9c335b7dd8000e93ab6a6a99d3ea345d", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 143608, + "line_end": 143608, + "char_start": 1764669, + "char_end": 1764693, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 143608, + "line_end": 143608, + "char_start": 1764674, + "char_end": 1764680, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e8fe872192d1fb173698", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "acc6e932a1347342485402b7ea2dafe0e366e0383620473dc798bc441d0073b7", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 146794, + "line_end": 146794, + "char_start": 1799953, + "char_end": 1799984, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 146794, + "line_end": 146794, + "char_start": 1799969, + "char_end": 1799975, + "column_start": 16, + "column_end": 22 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-73d31a6ef19bbb51ce01", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "d22e4aa8a2ed68c6631952f0d7e4857455d67087672f9f938817e78a4d5db6aa", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 146950, + "line_end": 146950, + "char_start": 1801407, + "char_end": 1801439, + "column_start": 28, + "column_end": 60 + }, + "surface_location": { + "line_start": 146950, + "line_end": 146950, + "char_start": 1801408, + "char_end": 1801414, + "column_start": 29, + "column_end": 35 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1481a87dc37b2e400411", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c654492d91694a1922df5b9a6efc10fab71fa7ff080f82a6c41d9909bd1ca3c1", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 147514, + "line_end": 147514, + "char_start": 1807456, + "char_end": 1807486, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 147514, + "line_end": 147514, + "char_start": 1807464, + "char_end": 1807470, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-47ea514efc644ef43c75", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "1db2b915fd51115c0b44d67f7eb02413689291a5138805969d0748f2f964de09", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 148402, + "line_end": 148402, + "char_start": 1819150, + "char_end": 1819166, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 148402, + "line_end": 148402, + "char_start": 1819159, + "char_end": 1819165, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9c100ffd9c2328fdf091", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "e5f3f5634287e5069e5c3a12dfb315c7a41cebc7d666932c218fb31f95b7a64d", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 149056, + "line_end": 149056, + "char_start": 1828343, + "char_end": 1828357, + "column_start": 25, + "column_end": 39 + }, + "surface_location": { + "line_start": 149056, + "line_end": 149056, + "char_start": 1828348, + "char_end": 1828354, + "column_start": 30, + "column_end": 36 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-23d3e7715d12d9adc3f1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "63cab72b68f116e3b7ee3d8851973d326344496c1bc1a6dd3a2d768b26cc076a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 149944, + "line_end": 149944, + "char_start": 1840890, + "char_end": 1840954, + "column_start": 0, + "column_end": 64 + }, + "surface_location": { + "line_start": 149944, + "line_end": 149944, + "char_start": 1840892, + "char_end": 1840898, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-778a023c7e9fe87fe69d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "1709e216c3ebb6665c64590baeb82ff49c6a6980a5298aa87aea1857cef74ab6", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 152281, + "line_end": 152281, + "char_start": 1873170, + "char_end": 1873192, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 152281, + "line_end": 152281, + "char_start": 1873172, + "char_end": 1873178, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-76fdeb03b89921ed0056", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "faf1d60e679996cabc8a24ba1596d208df18a78c481de8e267d0cc365ac68f72", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 154855, + "line_end": 154855, + "char_start": 1907835, + "char_end": 1907862, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 154855, + "line_end": 154855, + "char_start": 1907840, + "char_end": 1907846, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f34b9f4bc5c132b00759", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c77f945cc533d5c8420add56b6a17a06c48ba86afdd06489519fea417b7b66cd", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 156274, + "line_end": 156274, + "char_start": 1925922, + "char_end": 1925946, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 156274, + "line_end": 156274, + "char_start": 1925933, + "char_end": 1925939, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-74a417ac8b8feadefd20", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b7f47e7f2391ef324d40598c28514b24278f3a9128047c222bc55cc15832f3e2", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 157996, + "line_end": 157996, + "char_start": 1948385, + "char_end": 1948436, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 157996, + "line_end": 157996, + "char_start": 1948392, + "char_end": 1948398, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a80542f35aff0f6a8f5e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "2255346aabca49344892b3953ebd59231f294e865192fd7dbb7166d648b5b059", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 159580, + "line_end": 159580, + "char_start": 1969468, + "char_end": 1969491, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 159580, + "line_end": 159580, + "char_start": 1969475, + "char_end": 1969481, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5574a5a5d3d0a3a15b17", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "d4ba2f2a4111f833f049d43875a930cdcb9840f72bca9edb2a72eb543380772a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 160552, + "line_end": 160552, + "char_start": 1982776, + "char_end": 1982804, + "column_start": 91, + "column_end": 119 + }, + "surface_location": { + "line_start": 160552, + "line_end": 160552, + "char_start": 1982787, + "char_end": 1982801, + "column_start": 102, + "column_end": 116 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "一股来自远古的气息慢慢的弥漫", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-448afd8e54c51ade87a7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "89982948b63878cb0b903fedca7d843f766138acffc251c38eba95290fcafdc2", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 166453, + "line_end": 166453, + "char_start": 2068818, + "char_end": 2068924, + "column_start": 0, + "column_end": 106 + }, + "surface_location": { + "line_start": 166453, + "line_end": 166453, + "char_start": 2068821, + "char_end": 2068827, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5c04448b8841a71173d5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "058ddda1936a2c053908aa76b279ce7064f03d48137090921d5b17dfa525dfed", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 167425, + "line_end": 167425, + "char_start": 2081464, + "char_end": 2081476, + "column_start": 14, + "column_end": 26 + }, + "surface_location": { + "line_start": 167425, + "line_end": 167425, + "char_start": 2081467, + "char_end": 2081473, + "column_start": 17, + "column_end": 23 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c0684dc2e183f0b3cba2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "295ac46c710409eff444a7957a355eb421580c5df563ea60e60970c0bd2d7fb4", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 169264, + "line_end": 169264, + "char_start": 2108453, + "char_end": 2108467, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 169264, + "line_end": 169264, + "char_start": 2108453, + "char_end": 2108462, + "column_start": 0, + "column_end": 9 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "一股强悍的气息弥漫", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d5ffa5d0966ed838f0c5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b75d74fc683e45135a2ba04ae0fcbf93a3672bfb972a71ddbe45b867bb2a0ec0", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 169876, + "line_end": 169876, + "char_start": 2116997, + "char_end": 2117015, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 169876, + "line_end": 169876, + "char_start": 2116997, + "char_end": 2117003, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7bc3d565cc481e6f8acd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0a87fb9a6ae3e2636cb4739cce27baf9e7acede2c06043aad47a321e680b779e", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 171868, + "line_end": 171868, + "char_start": 2142900, + "char_end": 2142919, + "column_start": 18, + "column_end": 37 + }, + "surface_location": { + "line_start": 171868, + "line_end": 171868, + "char_start": 2142912, + "char_end": 2142918, + "column_start": 30, + "column_end": 36 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c929454e127831cd0c33", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "058ddda1936a2c053908aa76b279ce7064f03d48137090921d5b17dfa525dfed", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 174775, + "line_end": 174775, + "char_start": 2187299, + "char_end": 2187311, + "column_start": 65, + "column_end": 77 + }, + "surface_location": { + "line_start": 174775, + "line_end": 174775, + "char_start": 2187302, + "char_end": 2187308, + "column_start": 68, + "column_end": 74 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1bc6b5e0227576e54edc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "644a98b726361d9c5a13772b35972fd0210df60d3fde51ef46c23a5340bcfabb", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 175018, + "line_end": 175018, + "char_start": 2190652, + "char_end": 2190701, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 175018, + "line_end": 175018, + "char_start": 2190688, + "char_end": 2190694, + "column_start": 36, + "column_end": 42 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4482d9a7b26653682445", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ad300836f15ed8e16e61c6980d299acd89c0819438b8edec6c07bd03ba798a73", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 175237, + "line_end": 175237, + "char_start": 2194028, + "char_end": 2194041, + "column_start": 21, + "column_end": 34 + }, + "surface_location": { + "line_start": 175237, + "line_end": 175237, + "char_start": 2194034, + "char_end": 2194040, + "column_start": 27, + "column_end": 33 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ef9ad968060d03fa3be9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "6c1341b4343928110d9b486068f788a5f6b0220f1706701ce3870a0c3ceaafe4", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 176200, + "line_end": 176200, + "char_start": 2208654, + "char_end": 2208707, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 176200, + "line_end": 176200, + "char_start": 2208700, + "char_end": 2208706, + "column_start": 46, + "column_end": 52 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-04881d6d1d82ff1dc46c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "2c4698b9b66eff49e8f73ae8bd4b06d44737cfb5fe6d295422eeb4cf40713955", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 176338, + "line_end": 176338, + "char_start": 2210530, + "char_end": 2210564, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 176338, + "line_end": 176338, + "char_start": 2210557, + "char_end": 2210563, + "column_start": 27, + "column_end": 33 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dad8a55974c9d79ea952", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c2a8cc334ca15eb734b16807d58934048fe65169b5c8d21d6d952b8cf3042a1e", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 176392, + "line_end": 176392, + "char_start": 2211351, + "char_end": 2211372, + "column_start": 54, + "column_end": 75 + }, + "surface_location": { + "line_start": 176392, + "line_end": 176392, + "char_start": 2211363, + "char_end": 2211369, + "column_start": 66, + "column_end": 72 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-da74a02bbb7977802d27", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "feb3516c15ef031b6cf7f08adf72fe2948b05180d8c8432eaad1faf418cee3af", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 177346, + "line_end": 177346, + "char_start": 2223302, + "char_end": 2223345, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 177346, + "line_end": 177346, + "char_start": 2223321, + "char_end": 2223327, + "column_start": 19, + "column_end": 25 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-383d52146f4e9498e330", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "7f3ba6cefaddf41ce1f56454ca2277a240561f208728533bfe592f59f034e9e0", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 179974, + "line_end": 179974, + "char_start": 2262885, + "char_end": 2262935, + "column_start": 0, + "column_end": 50 + }, + "surface_location": { + "line_start": 179974, + "line_end": 179974, + "char_start": 2262913, + "char_end": 2262919, + "column_start": 28, + "column_end": 34 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-10c89604b4c2ce0d0808", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0f7959c1874fde0c9118c2418f41a1de68f06c12c77319710ef00021ab8c13af", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 180421, + "line_end": 180421, + "char_start": 2269476, + "char_end": 2269494, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 180421, + "line_end": 180421, + "char_start": 2269478, + "char_end": 2269484, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-27e1e9b980968185a11c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "6baa370eb72e9ab05bd78ae9d0483c619e57e7b731a3eba2ba653df50863ba89", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 182566, + "line_end": 182566, + "char_start": 2295534, + "char_end": 2295561, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 182566, + "line_end": 182566, + "char_start": 2295536, + "char_end": 2295542, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-704aad5bc234bdd6c694", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "4283313457f17154ef013f37fa7223541e08edfdced15ef0f630faf704662d9f", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 189463, + "line_end": 189463, + "char_start": 2378448, + "char_end": 2378464, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 189463, + "line_end": 189463, + "char_start": 2378448, + "char_end": 2378454, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b4d114f536e147c4730f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b521084b8e40eb046aa7d525d35539e32ef8942ba0b9413f649ce3a963ef248d", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 191542, + "line_end": 191542, + "char_start": 2404828, + "char_end": 2404860, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 191542, + "line_end": 191542, + "char_start": 2404835, + "char_end": 2404841, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-502f6466e5c502001d74", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "abd9325eeb286c81bf31f1678ebc15f71ac66ad25ac9b447256c3f447179aeb8", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 193186, + "line_end": 193186, + "char_start": 2425645, + "char_end": 2425670, + "column_start": 29, + "column_end": 54 + }, + "surface_location": { + "line_start": 193186, + "line_end": 193186, + "char_start": 2425653, + "char_end": 2425659, + "column_start": 37, + "column_end": 43 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-47a7fd08537dded68256", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "efe9f8f15c2d03823c452bb543ce7e63324dc0563c9164c9398b0af4663b8315", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 194758, + "line_end": 194758, + "char_start": 2445853, + "char_end": 2445875, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 194758, + "line_end": 194758, + "char_start": 2445868, + "char_end": 2445874, + "column_start": 15, + "column_end": 21 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6ddfa6d32e60b14aebe6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "649f4a9ce29949d3537a338c636fe88287a0723e9ce41156d17bf5040bdc68b7", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 198328, + "line_end": 198328, + "char_start": 2492910, + "char_end": 2492941, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 198328, + "line_end": 198328, + "char_start": 2492929, + "char_end": 2492935, + "column_start": 19, + "column_end": 25 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8435c03ba667bd1d5de1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "d6492a8e18f862036d00a1e18a15293d0105dab93d48d362435a71165c77f316", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 199282, + "line_end": 199282, + "char_start": 2504509, + "char_end": 2504524, + "column_start": 17, + "column_end": 32 + }, + "surface_location": { + "line_start": 199282, + "line_end": 199282, + "char_start": 2504517, + "char_end": 2504521, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8d2bf3b04b35a5e4d005", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "27185a402a9507354fc02a2bbe762e2f568e52b5dcb2052d856c2965d0db232a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 201115, + "line_end": 201115, + "char_start": 2527380, + "char_end": 2527398, + "column_start": 17, + "column_end": 35 + }, + "surface_location": { + "line_start": 201115, + "line_end": 201115, + "char_start": 2527383, + "char_end": 2527389, + "column_start": 20, + "column_end": 26 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ff24c7b4fc294e22c4b8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "660055e42195d3d83660803d9e94820fd7647a4df0f2e2f821a55298f1257eb3", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 202429, + "line_end": 202429, + "char_start": 2543387, + "char_end": 2543428, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 202429, + "line_end": 202429, + "char_start": 2543421, + "char_end": 2543427, + "column_start": 34, + "column_end": 40 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f4fa0dd8ba308c52a68f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c4a79a2c1324e95b1f3c036cf1d2277e0aa0cccc6991c9ac798c260d417e9d0d", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 205024, + "line_end": 205024, + "char_start": 2571499, + "char_end": 2571521, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 205024, + "line_end": 205024, + "char_start": 2571501, + "char_end": 2571507, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2af725eb0dcef8c9b5d1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "f8b9615640b0433ab3ddcbaf5327272f83e2c1f912d828b427f598f07514c61b", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 207166, + "line_end": 207166, + "char_start": 2593816, + "char_end": 2593836, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 207166, + "line_end": 207166, + "char_start": 2593829, + "char_end": 2593835, + "column_start": 13, + "column_end": 19 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8bbb36e00539c3841262", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "a250b5185a01c9b5c4cb8474428c706a096d9fa872e5fe1ff7c5da7ab43e5421", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 207412, + "line_end": 207412, + "char_start": 2596124, + "char_end": 2596141, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 207412, + "line_end": 207412, + "char_start": 2596126, + "char_end": 2596132, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1966d68972b0ebbb9519", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "25b5bbaafab2e1738d71011c336fc63a0d7ec3f9f54335e5053e7ebee163e034", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 208006, + "line_end": 208006, + "char_start": 2601548, + "char_end": 2601565, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 208006, + "line_end": 208006, + "char_start": 2601558, + "char_end": 2601564, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e057b7a77df0d12732d0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "d0b03a76f8974f035c07a7baf5d78c879e6e04d3a4a4f200ce1c9958bebae79a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 211306, + "line_end": 211306, + "char_start": 2632631, + "char_end": 2632650, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 211306, + "line_end": 211306, + "char_start": 2632637, + "char_end": 2632643, + "column_start": 6, + "column_end": 12 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-01bc74ed6737d6f31f17", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "1fbe54397d66010c52eeddcd67c2638c178567cb20e00c9b3f701eb07dc2ae56", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 211792, + "line_end": 211792, + "char_start": 2637525, + "char_end": 2637536, + "column_start": 21, + "column_end": 32 + }, + "surface_location": { + "line_start": 211792, + "line_end": 211792, + "char_start": 2637529, + "char_end": 2637535, + "column_start": 25, + "column_end": 31 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-56b756be344490c5de7f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0e3171ae52fc26f6198802539cef811924aa5a5a25201d666b41b542282fb23c", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 212599, + "line_end": 212599, + "char_start": 2645863, + "char_end": 2645876, + "column_start": 22, + "column_end": 35 + }, + "surface_location": { + "line_start": 212599, + "line_end": 212599, + "char_start": 2645869, + "char_end": 2645875, + "column_start": 28, + "column_end": 34 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d14e889a45ff9e078b8d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "937928ad1d58290afd55026960b8a1117198b42701b2f8e5f7f76d340b0a0f19", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 212941, + "line_end": 212941, + "char_start": 2649270, + "char_end": 2649293, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 212941, + "line_end": 212941, + "char_start": 2649286, + "char_end": 2649292, + "column_start": 16, + "column_end": 22 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8fa760b8a611c0d1cde9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "faad02d62b806e373ee5e5075b82ae770a32749fe129ea3dc41f19786a4cfb69", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 213718, + "line_end": 213718, + "char_start": 2657341, + "char_end": 2657356, + "column_start": 0, + "column_end": 15 + }, + "surface_location": { + "line_start": 213718, + "line_end": 213718, + "char_start": 2657349, + "char_end": 2657355, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f8131b9f69b98a35dcbe", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c9fe917e8ef64710a299bb1eb3d875fe6fff49d69176406cbedd07f32f484317", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 215842, + "line_end": 215842, + "char_start": 2679695, + "char_end": 2679717, + "column_start": 9, + "column_end": 31 + }, + "surface_location": { + "line_start": 215842, + "line_end": 215842, + "char_start": 2679698, + "char_end": 2679704, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b9e16d8dab8a5142ba33", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "3b85f456620fb84b0a013494b7b708ffc0957432ecb9b5923f3cbea864189251", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 220114, + "line_end": 220114, + "char_start": 2728351, + "char_end": 2728366, + "column_start": 0, + "column_end": 15 + }, + "surface_location": { + "line_start": 220114, + "line_end": 220114, + "char_start": 2728359, + "char_end": 2728365, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c8caba5cb7285fe3804d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b7d681ab7b867f6836ab8d1c52ab136299ca64cdc8c9e64f238bf669141e4945", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 223831, + "line_end": 223831, + "char_start": 2771751, + "char_end": 2771760, + "column_start": 0, + "column_end": 9 + }, + "surface_location": { + "line_start": 223831, + "line_end": 223831, + "char_start": 2771753, + "char_end": 2771759, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-660562d37d748f99daf5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "bae8f5db5c4d304ef6838db2923adb0cf6a549dfc57f18305f1432b7cfed78e8", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 229543, + "line_end": 229543, + "char_start": 2842878, + "char_end": 2842896, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 229543, + "line_end": 229543, + "char_start": 2842889, + "char_end": 2842895, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-541b62cfb0c522a72fe1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "3d95b0ee63f251eef3ba92b06dc44740bb98aeff98e10107a93c90f3b1878747", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 235042, + "line_end": 235042, + "char_start": 2906522, + "char_end": 2906544, + "column_start": 16, + "column_end": 38 + }, + "surface_location": { + "line_start": 235042, + "line_end": 235042, + "char_start": 2906527, + "char_end": 2906533, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3d1c01105c0195d54657", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "8c5b7bb503417f1f43894adebc7dfcf0e34c121204f8f363bd44860c8cc19399", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 236131, + "line_end": 236131, + "char_start": 2920092, + "char_end": 2920116, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 236131, + "line_end": 236131, + "char_start": 2920094, + "char_end": 2920100, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-334454141a7e70ee9415", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "7e7c10b8fa66fa8ea3fcdd1aa4052f79f53930b489293e55d8e6238d7e3023fe", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 239218, + "line_end": 239218, + "char_start": 2959180, + "char_end": 2959231, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 239218, + "line_end": 239218, + "char_start": 2959194, + "char_end": 2959200, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7518782e8318e0a29e2e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "9928ee019aaedb1ce0c69244603edeaf2dc879fdfd1b51dbb84289dd97790634", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 244762, + "line_end": 244762, + "char_start": 3033027, + "char_end": 3033053, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 244762, + "line_end": 244762, + "char_start": 3033046, + "char_end": 3033052, + "column_start": 19, + "column_end": 25 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fc5046e6ac5f282daa65", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "8b7c049505cb213b47d375178e2ff8628c82172d65f28862a6bd01d9dcc9d8c2", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 245941, + "line_end": 245941, + "char_start": 3047462, + "char_end": 3047477, + "column_start": 14, + "column_end": 29 + }, + "surface_location": { + "line_start": 245941, + "line_end": 245941, + "char_start": 3047468, + "char_end": 3047474, + "column_start": 20, + "column_end": 26 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a0f9fa0158239b64f6a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ad300836f15ed8e16e61c6980d299acd89c0819438b8edec6c07bd03ba798a73", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 246121, + "line_end": 246121, + "char_start": 3049611, + "char_end": 3049624, + "column_start": 31, + "column_end": 44 + }, + "surface_location": { + "line_start": 246121, + "line_end": 246121, + "char_start": 3049617, + "char_end": 3049623, + "column_start": 37, + "column_end": 43 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-32175984741e65651bb7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ad300836f15ed8e16e61c6980d299acd89c0819438b8edec6c07bd03ba798a73", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 247411, + "line_end": 247411, + "char_start": 3066366, + "char_end": 3066379, + "column_start": 27, + "column_end": 40 + }, + "surface_location": { + "line_start": 247411, + "line_end": 247411, + "char_start": 3066372, + "char_end": 3066378, + "column_start": 33, + "column_end": 39 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d67e9feef73d9d204541", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "079feedc2815a77c39da99469fec6437b2cec0137fe766dadb7e8ae1bf8f118a", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 248566, + "line_end": 248566, + "char_start": 3081355, + "char_end": 3081369, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 248566, + "line_end": 248566, + "char_start": 3081362, + "char_end": 3081368, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f3e3bbd6d8f7829b5e41", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0a68418aeb2051cddeff5a4f8262a9a4210943f0a5f222a269114d491b880eba", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 248629, + "line_end": 248629, + "char_start": 3081940, + "char_end": 3081960, + "column_start": 25, + "column_end": 45 + }, + "surface_location": { + "line_start": 248629, + "line_end": 248629, + "char_start": 3081946, + "char_end": 3081952, + "column_start": 31, + "column_end": 37 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-50f9cad213b00922e0c6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "6fa45dab311eee7ad78663174dac99028fe799ec2029eec9d2ced58a5cb8f5ac", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 253495, + "line_end": 253495, + "char_start": 3141132, + "char_end": 3141162, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 253495, + "line_end": 253495, + "char_start": 3141134, + "char_end": 3141140, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-88a40c74311f4a009d13", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "5502518949f2f3bfba22734e54cd89905c37e4270029e7e76fbf147dbd3aa404", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 254242, + "line_end": 254242, + "char_start": 3149516, + "char_end": 3149584, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 254242, + "line_end": 254242, + "char_start": 3149570, + "char_end": 3149576, + "column_start": 54, + "column_end": 60 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-74782859eb570641ea8d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0629e23e14a259e21fc38150c19b388a50e237e6f95a6c5ee538d47f11e437c9", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 254278, + "line_end": 254278, + "char_start": 3150260, + "char_end": 3150284, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 254278, + "line_end": 254278, + "char_start": 3150263, + "char_end": 3150269, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-335fd24f6aa92a80c010", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "ec25072e8e13e3be1c461ee751307cfe4db851112221f5649817ad28fc7fc3fa", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 257221, + "line_end": 257221, + "char_start": 3187198, + "char_end": 3187257, + "column_start": 0, + "column_end": 59 + }, + "surface_location": { + "line_start": 257221, + "line_end": 257221, + "char_start": 3187236, + "char_end": 3187242, + "column_start": 38, + "column_end": 44 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a07084d82a4781005310", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "b9d1ba75b94fbbc887b89aec6fb8e54d5b52153750a1b9e7900b95b678e4c694", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 262900, + "line_end": 262900, + "char_start": 3259278, + "char_end": 3259352, + "column_start": 0, + "column_end": 74 + }, + "surface_location": { + "line_start": 262900, + "line_end": 262900, + "char_start": 3259332, + "char_end": 3259338, + "column_start": 54, + "column_end": 60 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-959eedefbed3b7a41431", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c27a8419196e260b5fa1ba77772bfdef91e5deddb4e928ddedb968c5c5348b35", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 264187, + "line_end": 264187, + "char_start": 3276135, + "char_end": 3276184, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 264187, + "line_end": 264187, + "char_start": 3276160, + "char_end": 3276166, + "column_start": 25, + "column_end": 31 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-96d3efd30332cde879a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c8cae8d3b9a7714ba679e0aeae8ba16513f184a3a88dca5ef9d2813db6f26952", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 267616, + "line_end": 267616, + "char_start": 3327761, + "char_end": 3327805, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 267616, + "line_end": 267616, + "char_start": 3327787, + "char_end": 3327793, + "column_start": 26, + "column_end": 32 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2c9e9579a417e6e96998", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "273161a03fd6519642fd4f03edd1c3c1280d0e94bd7edd2cfc326040e1319195", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 267949, + "line_end": 267949, + "char_start": 3333371, + "char_end": 3333408, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 267949, + "line_end": 267949, + "char_start": 3333401, + "char_end": 3333407, + "column_start": 30, + "column_end": 36 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2df144f3bac6a692cf11", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "72da8198b97daeae331134fffc40fa503bef8ad7866c6bf09fae346de34c9531", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 267997, + "line_end": 267997, + "char_start": 3334019, + "char_end": 3334049, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 267997, + "line_end": 267997, + "char_start": 3334030, + "char_end": 3334036, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ca31ec28dbb0bd3ca7ad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "75a14ef0154acdf4e2217c2b97be452bc2fa48521194bb841f49b7fec0800e37", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 268969, + "line_end": 268969, + "char_start": 3347707, + "char_end": 3347743, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 268969, + "line_end": 268969, + "char_start": 3347713, + "char_end": 3347719, + "column_start": 6, + "column_end": 12 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7fedf825be6e91f24d08", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "c2caa9e9616e2a13206f37a525e5805247977edb2f553acd18beff2534f81817", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 268975, + "line_end": 268975, + "char_start": 3347807, + "char_end": 3347831, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 268975, + "line_end": 268975, + "char_start": 3347815, + "char_end": 3347821, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-73fb5003cabce9987adb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "7d794b2ef78913ef10cae95b581908e4ea826540f7a05cf305e57030eaddc738", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 272281, + "line_end": 272281, + "char_start": 3395595, + "char_end": 3395636, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 272281, + "line_end": 272281, + "char_start": 3395596, + "char_end": 3395602, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-87b2a4244f876b8ed663", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "0748834dc59bc0930b96cad8d01867e4df982963987f45443dd9cb2bbaa624f7", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 273823, + "line_end": 273823, + "char_start": 3417139, + "char_end": 3417194, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 273823, + "line_end": 273823, + "char_start": 3417162, + "char_end": 3417168, + "column_start": 23, + "column_end": 29 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b4efaa4b0c3653b60f16", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "cbd6a10fe23e08548b2506eeee3c66a220cbc9f77ec47444a30c7d941694f673", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 274906, + "line_end": 274906, + "char_start": 3433512, + "char_end": 3433523, + "column_start": 5, + "column_end": 16 + }, + "surface_location": { + "line_start": 274906, + "line_end": 274906, + "char_start": 3433516, + "char_end": 3433522, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8ce1cfc86323d504e475", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "dab35c89ab6a7d74d1eca7f3c6e8e9fd68318076c3e5438d473471da6cb794ea", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 274963, + "line_end": 274963, + "char_start": 3434381, + "char_end": 3434425, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 274963, + "line_end": 274963, + "char_start": 3434383, + "char_end": 3434389, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-40a4984c7fd305355743", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "excerpt_sha256": "01fa4d9b2c7b10af5673b26687cdca106d6fc8bdc1a1a0d004e04433b0f8bd19", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "location": { + "line_start": 276010, + "line_end": 276010, + "char_start": 3448768, + "char_end": 3448782, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 276010, + "line_end": 276010, + "char_start": 3448775, + "char_end": 3448781, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:希泊尼战纪_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dd9f4015385477825571", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "e746517b667d934bcba6c00280cbd0f97624fe4e94a5401cdb8f452195dc0ad2", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 9658, + "line_end": 9658, + "char_start": 280532, + "char_end": 280577, + "column_start": 0, + "column_end": 45 + }, + "surface_location": { + "line_start": 9658, + "line_end": 9658, + "char_start": 280534, + "char_end": 280540, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5049423d0576fc18633e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "02a168cf14ed2445845536b1131dfd70f67cd654e691278497af8d4e1a6523e1", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 10694, + "line_end": 10694, + "char_start": 312102, + "char_end": 312152, + "column_start": 0, + "column_end": 50 + }, + "surface_location": { + "line_start": 10694, + "line_end": 10694, + "char_start": 312112, + "char_end": 312118, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3f36965498455845ba83", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "17b47430d6a13120bfc6a1f15c492906a8f20cb3aff7234ab12459140a6e5a0f", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 11989, + "line_end": 11989, + "char_start": 348096, + "char_end": 348116, + "column_start": 12, + "column_end": 32 + }, + "surface_location": { + "line_start": 11989, + "line_end": 11989, + "char_start": 348096, + "char_end": 348100, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1779a7c0a64941cbb7f1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "54d7d353cef0a1ca48023e30e4bca66892e0305fcb4640a14843c729b0de142a", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 12847, + "line_end": 12847, + "char_start": 371133, + "char_end": 371162, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 12847, + "line_end": 12847, + "char_start": 371145, + "char_end": 371151, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9d80eef16dd2f46e01f5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "d86d0a4009458e6985e0cc916f4a1d8ab646fd699967cc28730ddbf36774cbb4", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 14766, + "line_end": 14766, + "char_start": 424492, + "char_end": 424513, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 14766, + "line_end": 14766, + "char_start": 424495, + "char_end": 424501, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-626515463b586c80b5b5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "9b035dda11859d99ac0eebab37b5ac5afee4d3ef360f1181d1d3534e4e894b17", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 17704, + "line_end": 17704, + "char_start": 506952, + "char_end": 506982, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 17704, + "line_end": 17704, + "char_start": 506956, + "char_end": 506962, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-acc4a6496f4c584cd728", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "43cc37c17aecd17e3298444d8c05a53b2d1e52661befe71130f1648194bcf1be", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 18411, + "line_end": 18411, + "char_start": 527490, + "char_end": 527524, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 18411, + "line_end": 18411, + "char_start": 527495, + "char_end": 527501, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cda8fc2758d69da57732", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "725ca1fba22c03f49f6e01600b52855dcc2f3cc608d62d0d541647f358151062", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 19978, + "line_end": 19978, + "char_start": 574488, + "char_end": 574541, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 19978, + "line_end": 19978, + "char_start": 574493, + "char_end": 574499, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0e5bd54fe6a07fc47fae", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "440bfc88ed0c3de713258808a15ab22d52bd246972245ad35f84cb735ae769b4", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 20967, + "line_end": 20967, + "char_start": 601927, + "char_end": 601960, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 20967, + "line_end": 20967, + "char_start": 601931, + "char_end": 601937, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ac04870f085e25d913b0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "5a154c221a855e52ed39b79cb2d87b14807970584e3ba7241fa1b89368279032", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 26274, + "line_end": 26274, + "char_start": 750359, + "char_end": 750394, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 26274, + "line_end": 26274, + "char_start": 750364, + "char_end": 750370, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-69ee085c6378fb2c02a1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "a9144ebf4ab7e18d41d0f5fd9a67838d93bcfa6bcb925b1bbec2bf144b2cfa37", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 26665, + "line_end": 26665, + "char_start": 762329, + "char_end": 762361, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 26665, + "line_end": 26665, + "char_start": 762334, + "char_end": 762340, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f1bf351e924493dbcda6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "752dd2ee741808c6885d7d18de1af92204c7537f3070b5c1a9fb71421b705e92", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 27633, + "line_end": 27633, + "char_start": 788791, + "char_end": 788827, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 27633, + "line_end": 27633, + "char_start": 788795, + "char_end": 788803, + "column_start": 4, + "column_end": 12 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a67cda97da7db38ebadf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "9875ac8130580b99f507bd139e6744bbc80d136f4c165157c89cf98011422c40", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 35232, + "line_end": 35232, + "char_start": 1003986, + "char_end": 1004011, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 35232, + "line_end": 35232, + "char_start": 1003992, + "char_end": 1003996, + "column_start": 6, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e3b30cc4c87dea66ad01", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "2955144008543f450039356afa0178b39b6d9cdeffca82c897aa7fdc9bbfefa0", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 35861, + "line_end": 35861, + "char_start": 1023170, + "char_end": 1023206, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 35861, + "line_end": 35861, + "char_start": 1023187, + "char_end": 1023193, + "column_start": 17, + "column_end": 23 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f211e720c16799af63ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "45224a796c1be17ff11ab1dd8163303b8b4df16e9c9e0e2ed1419736359667e0", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 42990, + "line_end": 42990, + "char_start": 1224156, + "char_end": 1224223, + "column_start": 88, + "column_end": 155 + }, + "surface_location": { + "line_start": 42990, + "line_end": 42990, + "char_start": 1224156, + "char_end": 1224162, + "column_start": 88, + "column_end": 94 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-efefd51a120a1a7daf4d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "6157742d1439c6773b69ef64a289e488fef100bff215165a4a73096b5269032b", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 44467, + "line_end": 44467, + "char_start": 1269155, + "char_end": 1269175, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 44467, + "line_end": 44467, + "char_start": 1269159, + "char_end": 1269165, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6c584999ec71d0968fd6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "eb624ee16f79b8552f87e4bf09df621e5e5e470d85729a8119e40cfd4e78473e", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 54354, + "line_end": 54354, + "char_start": 1545735, + "char_end": 1545777, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 54354, + "line_end": 54354, + "char_start": 1545745, + "char_end": 1545751, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6efd4118e33306d176ec", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "ae438c2bd975b6c94a32e3d276495691d8e3e43c9865bee1ad2c4ba27a30f375", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 55196, + "line_end": 55196, + "char_start": 1567883, + "char_end": 1567924, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 55196, + "line_end": 55196, + "char_start": 1567915, + "char_end": 1567923, + "column_start": 32, + "column_end": 40 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-584ec3711ecb1e47833c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "8e9d892e2b4e0cb342fa3de6534b741b5cb1a8616ece3193f065bd20f22299af", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 56058, + "line_end": 56058, + "char_start": 1591761, + "char_end": 1591785, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 56058, + "line_end": 56058, + "char_start": 1591766, + "char_end": 1591772, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3740e86bc12aecd7db7b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "d0efbc6e5c42c78107bd616e7c867122b6400e034323a5476067081423b81904", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 59952, + "line_end": 59952, + "char_start": 1702479, + "char_end": 1702495, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 59952, + "line_end": 59952, + "char_start": 1702488, + "char_end": 1702494, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dd63892f3e8782b8ca5a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "775458a3b6bd3be2387ec4c34d2e931cb24864a7641bb0a69c4fd6c8b97953ea", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 60187, + "line_end": 60187, + "char_start": 1708735, + "char_end": 1708748, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 60187, + "line_end": 60187, + "char_start": 1708739, + "char_end": 1708747, + "column_start": 4, + "column_end": 12 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-95a8a0729dc310027755", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "6124bc40e4cabf75f207225d69c573cdeb5579fe2edcd1149fba9d87532045ad", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 62162, + "line_end": 62162, + "char_start": 1764069, + "char_end": 1764087, + "column_start": 99, + "column_end": 117 + }, + "surface_location": { + "line_start": 62162, + "line_end": 62162, + "char_start": 1764071, + "char_end": 1764084, + "column_start": 101, + "column_end": 114 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "一股恐怖的气息,无形的弥漫", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7d3d36248055169dbcb5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "5f4065f448854cdcaad62918b920d9eb78579e2e8dc5fcea1d61be8847bd2440", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 68505, + "line_end": 68505, + "char_start": 1935988, + "char_end": 1936010, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 68505, + "line_end": 68505, + "char_start": 1935992, + "char_end": 1935998, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-728c1d86f18d9d54993c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "65d68cfca0fc4a7b7c53346d6b640f79f585940c8711c1f4ece6bd67c4298b2b", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 70193, + "line_end": 70193, + "char_start": 1981073, + "char_end": 1981110, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 70193, + "line_end": 70193, + "char_start": 1981078, + "char_end": 1981084, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bd9259bbae3c276724b5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "791f33d297b5ba451fa0fd042fe3810f3aa0fecf8eecabeb8a97f758d367b67a", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 70751, + "line_end": 70751, + "char_start": 1996826, + "char_end": 1996843, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 70751, + "line_end": 70751, + "char_start": 1996836, + "char_end": 1996842, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-de74473d8c0f0acfe149", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "1199eea0d8fb928426e50d0eb4fbd86d98a4e73e806ff31f0ace320ade83f7c2", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 71380, + "line_end": 71380, + "char_start": 2014955, + "char_end": 2014977, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 71380, + "line_end": 71380, + "char_start": 2014970, + "char_end": 2014976, + "column_start": 15, + "column_end": 21 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-19b82e35b76ea2b5ad9a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "a61f956b8daf794dfa1d9a9bd9e68a6dac83d5b6962e6142bbe0bc269eeeaf9a", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 71560, + "line_end": 71560, + "char_start": 2019865, + "char_end": 2019891, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 71560, + "line_end": 71560, + "char_start": 2019877, + "char_end": 2019883, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1e5e8460e940ae2f51d2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "4f40aa0d84c08227fe6a694966bbd7209ffdea706471072d5350102fa62f2eb5", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 75953, + "line_end": 75953, + "char_start": 2144487, + "char_end": 2144512, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 75953, + "line_end": 75953, + "char_start": 2144491, + "char_end": 2144497, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9f440ca4e223b888be5e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "9c4ddff315e47c4423ab167b02edd62a162331f0ff11a1b55302581d796c69b1", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 78274, + "line_end": 78274, + "char_start": 2209174, + "char_end": 2209208, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 78274, + "line_end": 78274, + "char_start": 2209176, + "char_end": 2209182, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7092f84d585b6dde98fa", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "6fa843d242154a2cd8a8dd5421f4867db722efdfeabfeb482efd2554247f0aa2", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 90597, + "line_end": 90597, + "char_start": 2549051, + "char_end": 2549067, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 90597, + "line_end": 90597, + "char_start": 2549055, + "char_end": 2549061, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-470c17f3ba692cc9b16e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "96f8aad85c6de736f8964ab9587c45d511a37fdb7b9f86c172e34c36175646f2", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 91467, + "line_end": 91467, + "char_start": 2573781, + "char_end": 2573823, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 91467, + "line_end": 91467, + "char_start": 2573810, + "char_end": 2573816, + "column_start": 29, + "column_end": 35 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-75bc328428076dbd403e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "b61f0463d1707f2ece52418e3958647630dbba43832d2edf3c459bf0af97bb54", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 92309, + "line_end": 92309, + "char_start": 2595951, + "char_end": 2595984, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 92309, + "line_end": 92309, + "char_start": 2595961, + "char_end": 2595967, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bfe0300b0bb839982c7a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "58aa89b34ae7ff966479b8d27090abf8779db6f2249dc0c316bca65779832e40", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 92332, + "line_end": 92332, + "char_start": 2596612, + "char_end": 2596651, + "column_start": 0, + "column_end": 39 + }, + "surface_location": { + "line_start": 92332, + "line_end": 92332, + "char_start": 2596633, + "char_end": 2596639, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a44374eb0e22150452bd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "ce99ce8d27a2a032fb122b01ee481f14d04e2a339cef3df3bffff6f5c6c0ebf8", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 95239, + "line_end": 95239, + "char_start": 2675023, + "char_end": 2675070, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 95239, + "line_end": 95239, + "char_start": 2675028, + "char_end": 2675034, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-76ca4b19fc5342fd5506", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "116614d42aa9c84edbceeb483d95b69c3660bd93b01b5c40869ce240e328fa17", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 95413, + "line_end": 95413, + "char_start": 2679596, + "char_end": 2679638, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 95413, + "line_end": 95413, + "char_start": 2679601, + "char_end": 2679607, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7d995336d576f1aec506", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "eb3c9ef48187d094dbcc3f510b95f077dd9c78a3ba06f69a519d09f063a8ba0b", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 95570, + "line_end": 95570, + "char_start": 2684176, + "char_end": 2684226, + "column_start": 0, + "column_end": 50 + }, + "surface_location": { + "line_start": 95570, + "line_end": 95570, + "char_start": 2684197, + "char_end": 2684203, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e1f0b7b89d40af6f942c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "17cad33b78e9e365cac024d089a9ac56e535b8c693d753bcfa7ba3aec5adddbb", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 102776, + "line_end": 102776, + "char_start": 2882154, + "char_end": 2882173, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 102776, + "line_end": 102776, + "char_start": 2882166, + "char_end": 2882172, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3382a8288df9c64ecf79", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "94a93a6691a9c2a5458359395970119a69150b6e34eba85cdab7e3da6e143949", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 102814, + "line_end": 102814, + "char_start": 2883242, + "char_end": 2883255, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 102814, + "line_end": 102814, + "char_start": 2883246, + "char_end": 2883252, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a545d42c512986d146d8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "00266c21912203ba6b60bd687da34635a425d77bf80afe475d7f4155f7902e90", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 105355, + "line_end": 105355, + "char_start": 2952207, + "char_end": 2952229, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 105355, + "line_end": 105355, + "char_start": 2952224, + "char_end": 2952228, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-510f5261153e263b37ce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "48e69c14e6bd5579d146a9664e358f2ade908d1314d893b0cb282d6f81ffcdf8", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 106693, + "line_end": 106693, + "char_start": 2988589, + "char_end": 2988615, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 106693, + "line_end": 106693, + "char_start": 2988593, + "char_end": 2988599, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b857a505e93f644722d5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "6242351330ed705657a52f9b57eda4355d0e2c8698b2667a2f5cd02dcf607f56", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 107504, + "line_end": 107504, + "char_start": 3009979, + "char_end": 3010026, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 107504, + "line_end": 107504, + "char_start": 3009981, + "char_end": 3009987, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2959d606ef220c5b57ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "f254114b3a92fa49f31dcff3c1f030e4832130790090e401d7e52f71b98892f4", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 112248, + "line_end": 112248, + "char_start": 3133193, + "char_end": 3133215, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 112248, + "line_end": 112248, + "char_start": 3133198, + "char_end": 3133204, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-49fadeb78b82b69ee1de", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "511ed4192c54d96dba3e5b95f6e7c4290e51ac0d100854f06ce72d545a6954f1", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 117109, + "line_end": 117109, + "char_start": 3257073, + "char_end": 3257096, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 117109, + "line_end": 117109, + "char_start": 3257082, + "char_end": 3257088, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2afaaa6fa42b8dc6a87f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "9a025f162b3a7699bd4531d2ce15c7c7dcbd97aed60b0b1e36e27f5d0ced5e8c", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 117204, + "line_end": 117204, + "char_start": 3259523, + "char_end": 3259548, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 117204, + "line_end": 117204, + "char_start": 3259528, + "char_end": 3259534, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-aa2015bc4357de46c61c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "0b463fc5873003f4f7e2a4e37b38747bf0f3fab0457d901befa2ecb6fb2ee79d", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 118096, + "line_end": 118096, + "char_start": 3280707, + "char_end": 3280754, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 118096, + "line_end": 118096, + "char_start": 3280709, + "char_end": 3280715, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a5399b67d01bd461d57c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "36980262b3e13d9b30e4b1e3dc9345a34bbc58baf55c2f20fe7125c9505086fe", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 125664, + "line_end": 125664, + "char_start": 3481415, + "char_end": 3481460, + "column_start": 0, + "column_end": 45 + }, + "surface_location": { + "line_start": 125664, + "line_end": 125664, + "char_start": 3481420, + "char_end": 3481426, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b434441dbcd787ba9257", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "49303370a109f121d0de9468dadf1326dedbf418d65ce051673add1f2cc4f8ac", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 128000, + "line_end": 128000, + "char_start": 3540564, + "char_end": 3540594, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 128000, + "line_end": 128000, + "char_start": 3540567, + "char_end": 3540573, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fdc8fe19b977c9d7dbe4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "a95f00653d1f02cde7c81c9bd88777a6267d4127698a4b9a8d26c36ea66e2f2e", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 129426, + "line_end": 129426, + "char_start": 3576734, + "char_end": 3576779, + "column_start": 0, + "column_end": 45 + }, + "surface_location": { + "line_start": 129426, + "line_end": 129426, + "char_start": 3576739, + "char_end": 3576745, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4d3e73bfd5d9c420262b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "19743c1f308b05bdcf7a3d50a87d5d8b3aabb4d98b9fe36667b4af5e19a62977", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 131703, + "line_end": 131703, + "char_start": 3641518, + "char_end": 3641549, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 131703, + "line_end": 131703, + "char_start": 3641529, + "char_end": 3641535, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fc8a8492cfe00d397575", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "053ab5eba9de49780ea8402677bccea247aff17bc96271d0da353dc5d1b0df9e", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 131735, + "line_end": 131735, + "char_start": 3642378, + "char_end": 3642415, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 131735, + "line_end": 131735, + "char_start": 3642385, + "char_end": 3642391, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0e74352bbdf5e7008282", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "57cead5151541191bf3119286288988adfcaba828a12653e4e250789fdc6d79b", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 132926, + "line_end": 132926, + "char_start": 3674704, + "char_end": 3674761, + "column_start": 0, + "column_end": 57 + }, + "surface_location": { + "line_start": 132926, + "line_end": 132926, + "char_start": 3674707, + "char_end": 3674713, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d46ba809da88af6f37e2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "a0319cab7fc4eda9ed5fd266c41aa2be6c0c5a99430361bec81b4eb0c9bfa36e", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 137427, + "line_end": 137427, + "char_start": 3795673, + "char_end": 3795700, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 137427, + "line_end": 137427, + "char_start": 3795687, + "char_end": 3795693, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-24a6a704e54caa46161e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "aff52d5fb08748595035b6ec0266916fbb39d0612f9ad832a4bbadef26da5419", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 139909, + "line_end": 139909, + "char_start": 3862862, + "char_end": 3862930, + "column_start": 24, + "column_end": 92 + }, + "surface_location": { + "line_start": 139909, + "line_end": 139909, + "char_start": 3862925, + "char_end": 3862929, + "column_start": 87, + "column_end": 91 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-767738fe750f63cd82db", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "15ec6f4b20bd0fa621530bfa4d8e1d99cd40c020783775008649666349197dda", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 140206, + "line_end": 140206, + "char_start": 3870908, + "char_end": 3870944, + "column_start": 27, + "column_end": 63 + }, + "surface_location": { + "line_start": 140206, + "line_end": 140206, + "char_start": 3870908, + "char_end": 3870914, + "column_start": 27, + "column_end": 33 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e87bceed207a954c6702", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "b0b115305432cc9b8b4a6d5fd47454a5fd9dd0f369cb365905eb87693c092346", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 147279, + "line_end": 147279, + "char_start": 4059228, + "char_end": 4059295, + "column_start": 0, + "column_end": 67 + }, + "surface_location": { + "line_start": 147279, + "line_end": 147279, + "char_start": 4059288, + "char_end": 4059294, + "column_start": 60, + "column_end": 66 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1081d54b80af65a1cdbf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "0374cca76a7fa7193af09466c06dbcc32570dca67f5597ae82eb94797cbd3f75", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 149401, + "line_end": 149401, + "char_start": 4114830, + "char_end": 4114867, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 149401, + "line_end": 149401, + "char_start": 4114845, + "char_end": 4114851, + "column_start": 15, + "column_end": 21 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0abedbefa0334859bf21", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "03851f188c43a16884a6f9eadb02f781d0eb6fd2cebd11d3c885372f47380167", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 149421, + "line_end": 149421, + "char_start": 4115337, + "char_end": 4115376, + "column_start": 0, + "column_end": 39 + }, + "surface_location": { + "line_start": 149421, + "line_end": 149421, + "char_start": 4115369, + "char_end": 4115375, + "column_start": 32, + "column_end": 38 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ad6778600217fa7b8757", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "6826a371b26eacdc1bcc754008279fa4518030153c6cbb77a1bba214923d66c3", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 150035, + "line_end": 150035, + "char_start": 4131593, + "char_end": 4131627, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 150035, + "line_end": 150035, + "char_start": 4131618, + "char_end": 4131624, + "column_start": 25, + "column_end": 31 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-58e6259afc8b432f081a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "084d6541c2cc3a2f10266b7d71146da37031449fe301b6387f33f3480713f452", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 150707, + "line_end": 150707, + "char_start": 4148290, + "char_end": 4148314, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 150707, + "line_end": 150707, + "char_start": 4148293, + "char_end": 4148297, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4038d065fd418a18333a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "e07ba9e141e846891b503de35cfe45a754a18e1f0d2b0dabcaf0b701172d7e14", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 150857, + "line_end": 150857, + "char_start": 4152080, + "char_end": 4152106, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 150857, + "line_end": 150857, + "char_start": 4152089, + "char_end": 4152097, + "column_start": 9, + "column_end": 17 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0ccb7845510843227560", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "07d167cab2fc6be7feac451480fae0e985573a8755f612bf21727ed2258b1090", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 151745, + "line_end": 151745, + "char_start": 4174644, + "char_end": 4174667, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 151745, + "line_end": 151745, + "char_start": 4174650, + "char_end": 4174658, + "column_start": 6, + "column_end": 14 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e6cc0303dd23ce16eb4f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "f7a4281fd9ec163f158bcba3ddf7f87b4e4e0811f7256a58670f2457e3e7dd23", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 152292, + "line_end": 152292, + "char_start": 4188630, + "char_end": 4188676, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 152292, + "line_end": 152292, + "char_start": 4188651, + "char_end": 4188657, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-35b3828df3d9aa1681ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "fd859fb8cfbe0ec5eae055966474ccada5a8379e62cfcc2e53a89c473621966f", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 155840, + "line_end": 155840, + "char_start": 4278162, + "char_end": 4278188, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 155840, + "line_end": 155840, + "char_start": 4278174, + "char_end": 4278180, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b5745735f69805c63352", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "ffbed759a5eaa76ade59c6cd687d3f8372be15a5feaaeb879ff27ea066b3bac9", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 158617, + "line_end": 158617, + "char_start": 4350032, + "char_end": 4350056, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 158617, + "line_end": 158617, + "char_start": 4350039, + "char_end": 4350045, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-67c906679a23cf06733e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "aafbc05eed3ab6410bc890268e8e445509707ba5d2c19749f10d34215916e52a", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 160086, + "line_end": 160086, + "char_start": 4388914, + "char_end": 4388951, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 160086, + "line_end": 160086, + "char_start": 4388944, + "char_end": 4388950, + "column_start": 30, + "column_end": 36 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-86674a4be3ace16e4190", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "7ec3c15fe3b735b107d048e1b10e58f09825607f3a41e8495b97f06a999bf355", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 161406, + "line_end": 161406, + "char_start": 4425056, + "char_end": 4425096, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 161406, + "line_end": 161406, + "char_start": 4425090, + "char_end": 4425094, + "column_start": 34, + "column_end": 38 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-07a095b89ee887c9ff5e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "f48c594a0f187ba6268b5e085cd26196fc3285e95ea2dceadae29ea827b27056", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 166040, + "line_end": 166040, + "char_start": 4548244, + "char_end": 4548274, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 166040, + "line_end": 166040, + "char_start": 4548247, + "char_end": 4548253, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e80d601034fd80bb56b0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "db5ee551ce20f52edbf2d6d05d9bb786145a2484cf4802801106938e0c07f467", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 167108, + "line_end": 167108, + "char_start": 4576762, + "char_end": 4576782, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 167108, + "line_end": 167108, + "char_start": 4576775, + "char_end": 4576781, + "column_start": 13, + "column_end": 19 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a9e90b3b173388065bc0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "ca3652a63bdd35abe9822327a68dc063b6ea0742ddd60a83b1b9ea595b99b6a4", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 167179, + "line_end": 167179, + "char_start": 4578723, + "char_end": 4578757, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 167179, + "line_end": 167179, + "char_start": 4578741, + "char_end": 4578747, + "column_start": 18, + "column_end": 24 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e77edaffeaadbcdff524", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "5a6cb8868481eed90151577e535a244028002b3e5e924ae1e5438039d25016ca", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 167517, + "line_end": 167517, + "char_start": 4588450, + "char_end": 4588476, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 167517, + "line_end": 167517, + "char_start": 4588455, + "char_end": 4588461, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f418f1adf0fb7ef829c4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "975930ae2ec763b2dc7b8e61b1801f47da1894fbf1ed9ded9dc0d7cdaa9deab5", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 170511, + "line_end": 170511, + "char_start": 4668493, + "char_end": 4668509, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 170511, + "line_end": 170511, + "char_start": 4668498, + "char_end": 4668506, + "column_start": 5, + "column_end": 13 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-11aa84f99cb9d1993a98", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "17d80d78008d85a5fc48f347e4d4a26d365444adff3c6aa7432ff3ada83816a3", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 170540, + "line_end": 170540, + "char_start": 4669182, + "char_end": 4669215, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 170540, + "line_end": 170540, + "char_start": 4669208, + "char_end": 4669214, + "column_start": 26, + "column_end": 32 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d990aed8334b87dadcbd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "bdb3e96b6409d77a0c196230b400a78b40d1259d7c619098608424cd68b528a5", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 170884, + "line_end": 170884, + "char_start": 4677525, + "char_end": 4677559, + "column_start": 24, + "column_end": 58 + }, + "surface_location": { + "line_start": 170884, + "line_end": 170884, + "char_start": 4677544, + "char_end": 4677550, + "column_start": 43, + "column_end": 49 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a6a0da526a864ad3a79b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "d4eb07fc198703556009f1359d05216ff38bf5557a874cc402ac12713e01a373", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 172722, + "line_end": 172722, + "char_start": 4723967, + "char_end": 4724008, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 172722, + "line_end": 172722, + "char_start": 4723976, + "char_end": 4723982, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b2ba61c50bd24d286955", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "5483f85fde00dd70eaee97f29ce34d27c1d6e2e6a8cca413f1de0f855ab263af", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 173144, + "line_end": 173144, + "char_start": 4734099, + "char_end": 4734125, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 173144, + "line_end": 173144, + "char_start": 4734118, + "char_end": 4734124, + "column_start": 19, + "column_end": 25 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-72de8359b8ce031cb3f6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "27ff37c5a7a1f6ec55d8372435ae326ed7af3a75d3fcdff7aa8e0a3d83f77eaf", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 175282, + "line_end": 175282, + "char_start": 4787025, + "char_end": 4787048, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 175282, + "line_end": 175282, + "char_start": 4787029, + "char_end": 4787037, + "column_start": 4, + "column_end": 12 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c1d6745f72ec1cb101f5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "b79c416687f148be7683f3a57338a1136d97f163bd846dfd3838a35471a3488b", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 176856, + "line_end": 176856, + "char_start": 4828337, + "char_end": 4828366, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 176856, + "line_end": 176856, + "char_start": 4828351, + "char_end": 4828357, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7507d8df9ddcae752d9e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "718716a67a8ef04521e6d571c45f97c12c03a2e49e5dac17f3dd6b398bb23199", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 179483, + "line_end": 179484, + "char_start": 4896587, + "char_end": 4896618, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 179483, + "line_end": 179483, + "char_start": 4896601, + "char_end": 4896609, + "column_start": 14, + "column_end": 22 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-47061701d483ab0fc6e0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "1ca935c8879dd8e52ac5210917eb7c589b34c6d142d47f52c5b74ddc4a0adfd1", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 181436, + "line_end": 181436, + "char_start": 4946917, + "char_end": 4946951, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 181436, + "line_end": 181436, + "char_start": 4946924, + "char_end": 4946932, + "column_start": 7, + "column_end": 15 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-72ff540df38ac34645a6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "aa429ec674de97474a29ed73cc7e781ee304d1f76970c831bb20e010887ef6d7", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 183525, + "line_end": 183525, + "char_start": 4998591, + "char_end": 4998614, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 183525, + "line_end": 183525, + "char_start": 4998597, + "char_end": 4998605, + "column_start": 6, + "column_end": 14 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e528be012a24496c7f7f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "a2151a420322bebe969647058f8d199ae4c800f5900bd6e24bd02664a27a9dcc", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 185652, + "line_end": 185652, + "char_start": 5055481, + "char_end": 5055528, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 185652, + "line_end": 185652, + "char_start": 5055519, + "char_end": 5055527, + "column_start": 38, + "column_end": 46 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c54489a7c34e9d9d6471", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "d89b69b9dbc4322f39c1dd4926a572df2fcbaea9251ad5af3bf901559221e3d1", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 185877, + "line_end": 185877, + "char_start": 5061332, + "char_end": 5061364, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 185877, + "line_end": 185877, + "char_start": 5061355, + "char_end": 5061363, + "column_start": 23, + "column_end": 31 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8ca8447f95bca0409585", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "excerpt_sha256": "ddb82bd8cab72de022573fff425c2d23e1a157c7ab3135e93e08138205457a6a", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "location": { + "line_start": 186906, + "line_end": 186906, + "char_start": 5088711, + "char_end": 5088741, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 186906, + "line_end": 186906, + "char_start": 5088734, + "char_end": 5088740, + "column_start": 23, + "column_end": 29 + }, + "work_ref": "ref:星环使命(虚伪王庭)" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5996a2853f562a78d4c8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "e5f269d21ce108f281aa6a23d2459e220db48b5487a979f9249140ae99f2b48c", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 2752, + "line_end": 2752, + "char_start": 58804, + "char_end": 58962, + "column_start": 0, + "column_end": 158 + }, + "surface_location": { + "line_start": 2752, + "line_end": 2752, + "char_start": 58860, + "char_end": 58866, + "column_start": 56, + "column_end": 62 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0103ca241db8ca2c4c45", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "56b5e0966b6a1b3e44eef98f083f74d855424ab1a2f08cb8553aae3cecdf9f3f", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 8905, + "line_end": 8905, + "char_start": 188375, + "char_end": 188447, + "column_start": 0, + "column_end": 72 + }, + "surface_location": { + "line_start": 8905, + "line_end": 8905, + "char_start": 188413, + "char_end": 188417, + "column_start": 38, + "column_end": 42 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-18eed63affab56992ff8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "05f47b38a434e7d07e1fc60e1f058184e84572ab40ba1c4f8df710c480917503", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 9460, + "line_end": 9466, + "char_start": 198257, + "char_end": 198437, + "column_start": 47, + "column_end": 144 + }, + "surface_location": { + "line_start": 9466, + "line_end": 9466, + "char_start": 198347, + "char_end": 198351, + "column_start": 54, + "column_end": 58 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-49a88454a01143b2fc05", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "708b8e84383eabe0daf932a48963b12fb33cbaced5d754c48883b41bdc2bb720", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 11917, + "line_end": 11917, + "char_start": 247147, + "char_end": 247250, + "column_start": 0, + "column_end": 103 + }, + "surface_location": { + "line_start": 11917, + "line_end": 11917, + "char_start": 247182, + "char_end": 247186, + "column_start": 35, + "column_end": 39 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6bae33000c20db536226", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "bcd0f28f24d9305b2af949a464ca0f2aff0bf7bfaef7af6282b1193c8a9a0e52", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 13141, + "line_end": 13141, + "char_start": 275995, + "char_end": 276075, + "column_start": 0, + "column_end": 80 + }, + "surface_location": { + "line_start": 13141, + "line_end": 13141, + "char_start": 276052, + "char_end": 276056, + "column_start": 57, + "column_end": 61 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-74c7241abe80d6881823", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "5b28816c2d8e05d53bb17507c7f320b3c213cbaf5a83a23a2c6cb8cec61c9f49", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 23965, + "line_end": 23966, + "char_start": 488734, + "char_end": 488782, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 23965, + "line_end": 23965, + "char_start": 488765, + "char_end": 488769, + "column_start": 31, + "column_end": 35 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-14f1fb52b9e1c59ce099", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "8c3323551076d6784bab158f0c43f9f2bcde6e97beeeb2060b13a5099fee6ab3", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 32413, + "line_end": 32413, + "char_start": 663095, + "char_end": 663252, + "column_start": 0, + "column_end": 157 + }, + "surface_location": { + "line_start": 32413, + "line_end": 32413, + "char_start": 663168, + "char_end": 663172, + "column_start": 73, + "column_end": 77 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f769f6d41d4add42bb35", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "8a4947bc0b6a213e7a69be2e8180cd8dfcd97e7dedbbea0fd099b46ea63b75cb", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 50212, + "line_end": 50212, + "char_start": 1011774, + "char_end": 1011876, + "column_start": 0, + "column_end": 102 + }, + "surface_location": { + "line_start": 50212, + "line_end": 50212, + "char_start": 1011845, + "char_end": 1011849, + "column_start": 71, + "column_end": 75 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9fb1ba9758c109c655c2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "6b9058eeb1f98e47a1bca5093b794ba2c3dae67fde1e4b4350349b923593162f", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 53443, + "line_end": 53443, + "char_start": 1081876, + "char_end": 1081893, + "column_start": 52, + "column_end": 69 + }, + "surface_location": { + "line_start": 53443, + "line_end": 53443, + "char_start": 1081888, + "char_end": 1081892, + "column_start": 64, + "column_end": 68 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e1f6a4422abb32a3f051", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "f36255ea4755fe9cb50df10acf73bde5d8fa0fde6ed76dfc42fff0fdc21b4e55", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 58126, + "line_end": 58126, + "char_start": 1173767, + "char_end": 1173847, + "column_start": 25, + "column_end": 105 + }, + "surface_location": { + "line_start": 58126, + "line_end": 58126, + "char_start": 1173838, + "char_end": 1173842, + "column_start": 96, + "column_end": 100 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ad033982f6ac4d3830df", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "52e5c0604896d46b4ab2692533569c9501644dd66ff8c52cc0dd7b40902f6f79", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 74398, + "line_end": 74398, + "char_start": 1496722, + "char_end": 1496862, + "column_start": 1019, + "column_end": 1159 + }, + "surface_location": { + "line_start": 74398, + "line_end": 74398, + "char_start": 1496758, + "char_end": 1496764, + "column_start": 1055, + "column_end": 1061 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8928e70e253ec5ef0755", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "489b9548c6e57f84bb4f19e884699c1c9c0c1fe8e71a4e7ee731833fb601af5f", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 75016, + "line_end": 75016, + "char_start": 1513857, + "char_end": 1513919, + "column_start": 1134, + "column_end": 1196 + }, + "surface_location": { + "line_start": 75016, + "line_end": 75016, + "char_start": 1513914, + "char_end": 1513918, + "column_start": 1191, + "column_end": 1195 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b736a260bdd1cee9daff", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "a8db50bd8c2c9082011ae5ac7392f68cfb4618193cfaac1f247a296de8d77352", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 87760, + "line_end": 87760, + "char_start": 1759755, + "char_end": 1759835, + "column_start": 0, + "column_end": 80 + }, + "surface_location": { + "line_start": 87760, + "line_end": 87760, + "char_start": 1759810, + "char_end": 1759814, + "column_start": 55, + "column_end": 59 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f2c64297c4ce5a8ab97f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "68167f345fa294d5e81250891a1b1093e361db4318fe660b36e4f89db29b9671", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 91936, + "line_end": 91936, + "char_start": 1837267, + "char_end": 1837285, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 91936, + "line_end": 91936, + "char_start": 1837280, + "char_end": 1837284, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-31f56413543e126e3ebd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "fb8e8561276a17c1885624015954f4457d326429dabad950687d7c2d89395471", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 95146, + "line_end": 95146, + "char_start": 1903391, + "char_end": 1903478, + "column_start": 0, + "column_end": 87 + }, + "surface_location": { + "line_start": 95146, + "line_end": 95146, + "char_start": 1903456, + "char_end": 1903460, + "column_start": 65, + "column_end": 69 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bf726648ee7264080f36", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "d196e26c2e79bf8a0f945ed250fb2bef71df597464d9e93a541ff38b79e47154", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 106648, + "line_end": 106648, + "char_start": 2122059, + "char_end": 2122116, + "column_start": 0, + "column_end": 57 + }, + "surface_location": { + "line_start": 106648, + "line_end": 106648, + "char_start": 2122075, + "char_end": 2122079, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0997522fddccb2f0601f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "b40199fb52ec8d0aea3c67c4905936e39731f82ddada18fd664247657503468e", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 118819, + "line_end": 118819, + "char_start": 2341029, + "char_end": 2341037, + "column_start": 0, + "column_end": 8 + }, + "surface_location": { + "line_start": 118819, + "line_end": 118819, + "char_start": 2341032, + "char_end": 2341036, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b483f77a40066921fa97", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "3a03104100bd3586b424c093efa2e57be763dd7653d992a2cbff52afa35935a9", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 121669, + "line_end": 121669, + "char_start": 2392878, + "char_end": 2392892, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 121669, + "line_end": 121669, + "char_start": 2392878, + "char_end": 2392884, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-01896b9fd20c0b5c2902", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "excerpt_sha256": "4491bcb80a5b3f32d24ca125097527922fe6695558840d570fe1f4b0f76a0363", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "location": { + "line_start": 141049, + "line_end": 141049, + "char_start": 2720312, + "char_end": 2720335, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 141049, + "line_end": 141049, + "char_start": 2720320, + "char_end": 2720324, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机动风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6b391363e91b6837811f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "03651fab34b4e4fc1924359c39a747d7ab37f91f6231b50e3ce5d269063fad1f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 5575, + "line_end": 5575, + "char_start": 150232, + "char_end": 150297, + "column_start": 56, + "column_end": 121 + }, + "surface_location": { + "line_start": 5575, + "line_end": 5575, + "char_start": 150257, + "char_end": 150261, + "column_start": 81, + "column_end": 85 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c2d8edbd901a2057c121", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "3b28ca7fff5d522d70b738f723e7ff1971c585be102f2f20c107942c05ee8164", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 6250, + "line_end": 6250, + "char_start": 170756, + "char_end": 170852, + "column_start": 0, + "column_end": 96 + }, + "surface_location": { + "line_start": 6250, + "line_end": 6250, + "char_start": 170816, + "char_end": 170820, + "column_start": 60, + "column_end": 64 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9687fabc5a72f3958e78", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "698c34d18e2303bc71a25a74e05cdc085a1b4c2c231a2c82b7679f2b58a7c7ac", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 6511, + "line_end": 6511, + "char_start": 178735, + "char_end": 178825, + "column_start": 0, + "column_end": 90 + }, + "surface_location": { + "line_start": 6511, + "line_end": 6511, + "char_start": 178804, + "char_end": 178808, + "column_start": 69, + "column_end": 73 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f1b56d116ed9c5354fb3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "330304bd448752b5cc435b28ec446b5faa71653384e6cfff4f8ff94ea1b7ad02", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 6865, + "line_end": 6865, + "char_start": 189332, + "char_end": 189467, + "column_start": 0, + "column_end": 135 + }, + "surface_location": { + "line_start": 6865, + "line_end": 6865, + "char_start": 189412, + "char_end": 189416, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a477a2abbee79f1e40e1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c113441dd1f73e532dc426842ff4879d1e0d528e1cdd48fbf8fe06beabd3dbf5", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 7559, + "line_end": 7559, + "char_start": 208131, + "char_end": 208272, + "column_start": 115, + "column_end": 256 + }, + "surface_location": { + "line_start": 7559, + "line_end": 7559, + "char_start": 208188, + "char_end": 208192, + "column_start": 172, + "column_end": 176 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e4253feec14650a7be95", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c4967bc272d6e881686451c3ab7eb39cebda28f876b5c20ca4e63e16d94ce656", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 10567, + "line_end": 10567, + "char_start": 290332, + "char_end": 290425, + "column_start": 0, + "column_end": 93 + }, + "surface_location": { + "line_start": 10567, + "line_end": 10567, + "char_start": 290384, + "char_end": 290388, + "column_start": 52, + "column_end": 56 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b12c13f89200b3d4cf72", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "855900105e681c5b0fd54436c0d888ea8edf9c7a81fa9b801bcd16bc9747eb70", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 15143, + "line_end": 15143, + "char_start": 409659, + "char_end": 409820, + "column_start": 21, + "column_end": 182 + }, + "surface_location": { + "line_start": 15143, + "line_end": 15143, + "char_start": 409743, + "char_end": 409747, + "column_start": 105, + "column_end": 109 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f60f9514c3b76eb14bf7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d4ab1d8855c47cd8fe3878706ac88f58d957ae8094614c9e4f3ff2d58e3ae0a6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 17516, + "line_end": 17519, + "char_start": 472423, + "char_end": 472603, + "column_start": 101, + "column_end": 155 + }, + "surface_location": { + "line_start": 17519, + "line_end": 17519, + "char_start": 472513, + "char_end": 472517, + "column_start": 65, + "column_end": 69 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f734c4cbf2cab996df8e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "af054f5e357b1fa5031a32aac31cb3fd0397183a150f094b755c980b2b9b2613", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 18035, + "line_end": 18035, + "char_start": 484824, + "char_end": 484896, + "column_start": 0, + "column_end": 72 + }, + "surface_location": { + "line_start": 18035, + "line_end": 18035, + "char_start": 484825, + "char_end": 484829, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2ff3d3ee3b7990ad2273", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a1c5ee6fc715e0cde7d652d17ebea3eed757054cc23338a398a0d9b5d5722056", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 18800, + "line_end": 18800, + "char_start": 504815, + "char_end": 504866, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 18800, + "line_end": 18800, + "char_start": 504857, + "char_end": 504861, + "column_start": 42, + "column_end": 46 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-375145552984d473aea5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a661698481dfd2c1f2af755ec81bda70f7b52db20da2db1e301a92d600bb92c4", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 21001, + "line_end": 21001, + "char_start": 560283, + "char_end": 560431, + "column_start": 0, + "column_end": 148 + }, + "surface_location": { + "line_start": 21001, + "line_end": 21001, + "char_start": 560350, + "char_end": 560354, + "column_start": 67, + "column_end": 71 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c593f0e88cc67de51bb6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "187630a6b27a77177526ffb8b472ef3817418869c62a1f34fa552157ae8c325a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 23130, + "line_end": 23130, + "char_start": 618112, + "char_end": 618203, + "column_start": 0, + "column_end": 91 + }, + "surface_location": { + "line_start": 23130, + "line_end": 23130, + "char_start": 618113, + "char_end": 618117, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f729735c09ff1e488be9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b6a2c2af3fff9e1a04d4d6f320d6d3ee9864a06f56d211f0afa555feb76d1633", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 23414, + "line_end": 23414, + "char_start": 626336, + "char_end": 626383, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 23414, + "line_end": 23414, + "char_start": 626359, + "char_end": 626363, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bdcbe232ea94e80f709d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d34fd03ac036c2ebef8a880f9ee57b94eb9220deeb49e293675640fb95f8bab6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 24323, + "line_end": 24323, + "char_start": 650792, + "char_end": 650825, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 24323, + "line_end": 24323, + "char_start": 650793, + "char_end": 650797, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4c0a27e66ddb951ec0b0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "9aa37b9bab8fbb93c6e74449382ea213d5a3ac118d780441ec95fb2cfea159e6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 35110, + "line_end": 35110, + "char_start": 923159, + "char_end": 923245, + "column_start": 0, + "column_end": 86 + }, + "surface_location": { + "line_start": 35110, + "line_end": 35110, + "char_start": 923218, + "char_end": 923222, + "column_start": 59, + "column_end": 63 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-75cab556404e43921d59", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "16cfdc3e867986e0af64ca9a705957a2d8a7b49dab376f42505e8dc327df1e39", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 37818, + "line_end": 37821, + "char_start": 994727, + "char_end": 994907, + "column_start": 40, + "column_end": 33 + }, + "surface_location": { + "line_start": 37818, + "line_end": 37818, + "char_start": 994817, + "char_end": 994821, + "column_start": 130, + "column_end": 134 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f5be7f527b905db4a6fa", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "606a3685678c921b0276042c26ac6ca4c5833d25905f91cc8cd5c540b644b95a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 37923, + "line_end": 37923, + "char_start": 998394, + "char_end": 998531, + "column_start": 0, + "column_end": 137 + }, + "surface_location": { + "line_start": 37923, + "line_end": 37923, + "char_start": 998394, + "char_end": 998398, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-43a486c1338f7a7ee40f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c3f55f0a23b9a8da16cdbac89158803b0afdb098f42e68c34f022c2cfbd20a1f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 38184, + "line_end": 38184, + "char_start": 1006121, + "char_end": 1006205, + "column_start": 0, + "column_end": 84 + }, + "surface_location": { + "line_start": 38184, + "line_end": 38184, + "char_start": 1006121, + "char_end": 1006125, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-60271cd9c50952b7ef27", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b6b1ec0874ab99f4aae66a87717e4af4558d3c0d33c2b3a9facf0b7c6316b2f7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 38442, + "line_end": 38442, + "char_start": 1014157, + "char_end": 1014262, + "column_start": 0, + "column_end": 105 + }, + "surface_location": { + "line_start": 38442, + "line_end": 38442, + "char_start": 1014220, + "char_end": 1014224, + "column_start": 63, + "column_end": 67 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-67a6c6f80295415ceaf8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0de9f677f2d96a3ff8c2ed985d972d9e1adef17f7a36d0804c0325b9d47ff023", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 39969, + "line_end": 39969, + "char_start": 1059869, + "char_end": 1059937, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 39969, + "line_end": 39969, + "char_start": 1059869, + "char_end": 1059873, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8bc1a61bfa7798b43a55", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e855fc1de1ec48c7d99f806c9d3c11f59845a9908f1092826e73c66f9855b49f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 40164, + "line_end": 40164, + "char_start": 1066001, + "char_end": 1066135, + "column_start": 0, + "column_end": 134 + }, + "surface_location": { + "line_start": 40164, + "line_end": 40164, + "char_start": 1066069, + "char_end": 1066073, + "column_start": 68, + "column_end": 72 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-abe53afc34ba497bac3e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "ea13b1ed31987a5c2fc9ef6e5711f8a4f6c93602b1d054e69086313eac61bbff", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 40680, + "line_end": 40680, + "char_start": 1082940, + "char_end": 1083044, + "column_start": 0, + "column_end": 104 + }, + "surface_location": { + "line_start": 40680, + "line_end": 40680, + "char_start": 1082962, + "char_end": 1082966, + "column_start": 22, + "column_end": 26 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cd65ff6fc9bdbdbedbbd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e9fa0b47db82fba6f0972279312d41cb8fdf5bae31e5dcb0774e61284c470c14", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 41717, + "line_end": 41717, + "char_start": 1114735, + "char_end": 1114884, + "column_start": 0, + "column_end": 149 + }, + "surface_location": { + "line_start": 41717, + "line_end": 41717, + "char_start": 1114735, + "char_end": 1114741, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f6ef4ec912986165f07a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d0bf6d15f31426032b9dbc1803fd6eddb7bb2a9abc06398094656973bee59194", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 42092, + "line_end": 42092, + "char_start": 1124012, + "char_end": 1124164, + "column_start": 0, + "column_end": 152 + }, + "surface_location": { + "line_start": 42092, + "line_end": 42092, + "char_start": 1124074, + "char_end": 1124078, + "column_start": 62, + "column_end": 66 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f649289866dc58415a07", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "70104d8bd4c11a7e988a98d70477fbd8a1fad871fcd8dc57d4b3cfde837eea79", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 43216, + "line_end": 43216, + "char_start": 1154730, + "char_end": 1154873, + "column_start": 0, + "column_end": 143 + }, + "surface_location": { + "line_start": 43216, + "line_end": 43216, + "char_start": 1154845, + "char_end": 1154849, + "column_start": 115, + "column_end": 119 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b2b0a675cd2df0052604", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b267c49bb06dddd65b3cb0983acb3948cba8372c7be9fee5a2fd3e2f0fd5efde", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 44263, + "line_end": 44263, + "char_start": 1181508, + "char_end": 1181622, + "column_start": 0, + "column_end": 114 + }, + "surface_location": { + "line_start": 44263, + "line_end": 44263, + "char_start": 1181592, + "char_end": 1181596, + "column_start": 84, + "column_end": 88 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-16c2e9845da0bbc7a32f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "aa7a5852dfd8d5c962b970ceb18d28109784d4a9044f77f023081e56b4c0f0a9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 47405, + "line_end": 47405, + "char_start": 1268531, + "char_end": 1268550, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 47405, + "line_end": 47405, + "char_start": 1268533, + "char_end": 1268537, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f19422453214e4711f50", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "18aaad4d6c66f2a205815076c4cf9a67cc619650325c176af2ad7a78e9ce9015", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 47795, + "line_end": 47795, + "char_start": 1279414, + "char_end": 1279562, + "column_start": 0, + "column_end": 148 + }, + "surface_location": { + "line_start": 47795, + "line_end": 47795, + "char_start": 1279530, + "char_end": 1279534, + "column_start": 116, + "column_end": 120 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-604f032819cc34e89d43", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7f65aee32daa8eb22813a708fbdc92f6c7ac83b0806be5029ff74ff2f023415a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 48113, + "line_end": 48113, + "char_start": 1288121, + "char_end": 1288228, + "column_start": 0, + "column_end": 107 + }, + "surface_location": { + "line_start": 48113, + "line_end": 48113, + "char_start": 1288201, + "char_end": 1288205, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2743edaaea3bad7976f1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "69115b7bf19acbe042c850db0c52a5100f411e208cb35b3c331e80e98527fb6f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 48539, + "line_end": 48539, + "char_start": 1301126, + "char_end": 1301244, + "column_start": 0, + "column_end": 118 + }, + "surface_location": { + "line_start": 48539, + "line_end": 48539, + "char_start": 1301146, + "char_end": 1301150, + "column_start": 20, + "column_end": 24 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1678e84dea052833f6a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "3ad34cf567ac011b8a55f5c759241930c1128b087916981d1be06109b5c82aef", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 48734, + "line_end": 48734, + "char_start": 1307055, + "char_end": 1307154, + "column_start": 0, + "column_end": 99 + }, + "surface_location": { + "line_start": 48734, + "line_end": 48734, + "char_start": 1307056, + "char_end": 1307060, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3b19a121753ca98980d4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b906661e3584c60db1481e26b08d8ff0726c6c3d892975b24d0129fb9e1bf576", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 49097, + "line_end": 49097, + "char_start": 1316978, + "char_end": 1317075, + "column_start": 7, + "column_end": 104 + }, + "surface_location": { + "line_start": 49097, + "line_end": 49097, + "char_start": 1317031, + "char_end": 1317035, + "column_start": 60, + "column_end": 64 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e3f0ac5c98cb7d9f6597", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b967eae3b5063bfdec4db1338546ee8a4042fbf40514dc1d3e81c3753effebc2", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 49193, + "line_end": 49193, + "char_start": 1318650, + "char_end": 1318721, + "column_start": 0, + "column_end": 71 + }, + "surface_location": { + "line_start": 49193, + "line_end": 49193, + "char_start": 1318651, + "char_end": 1318655, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d4ed82c2c5a0c770c380", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0f4a1ba8baa0d0a3cacd954033ece82f130402b45458d16f9701c9bcd3b7d9ba", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 49415, + "line_end": 49415, + "char_start": 1325068, + "char_end": 1325197, + "column_start": 0, + "column_end": 129 + }, + "surface_location": { + "line_start": 49415, + "line_end": 49415, + "char_start": 1325154, + "char_end": 1325158, + "column_start": 86, + "column_end": 90 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-22eb9b4ddccf194e0e88", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f7095b86d1169a78f16a313b613527c1e15c93f01fe23dc177f7f73f77083271", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 50087, + "line_end": 50087, + "char_start": 1343997, + "char_end": 1344098, + "column_start": 110, + "column_end": 211 + }, + "surface_location": { + "line_start": 50087, + "line_end": 50087, + "char_start": 1344070, + "char_end": 1344074, + "column_start": 183, + "column_end": 187 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3974b39a0d547f79f4db", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7f2432a5172b0db6c074473b02532cb818db627a8aacecc1db2cd3c3dfa9525a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 51014, + "line_end": 51014, + "char_start": 1372850, + "char_end": 1372929, + "column_start": 0, + "column_end": 79 + }, + "surface_location": { + "line_start": 51014, + "line_end": 51014, + "char_start": 1372900, + "char_end": 1372904, + "column_start": 50, + "column_end": 54 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5e315a507aae7f4fce36", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "4c5a8486051ed8896cfda6b92ab6f1de7a796e8b50d57036587f96756188b32a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 51973, + "line_end": 51973, + "char_start": 1402245, + "char_end": 1402353, + "column_start": 0, + "column_end": 108 + }, + "surface_location": { + "line_start": 51973, + "line_end": 51973, + "char_start": 1402348, + "char_end": 1402352, + "column_start": 103, + "column_end": 107 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a27551f5081774f2e9ce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "26883e0885986815500ca8f89ca80439a94c4393c755610c7206d943e9435cf2", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 52141, + "line_end": 52141, + "char_start": 1407562, + "char_end": 1407626, + "column_start": 0, + "column_end": 64 + }, + "surface_location": { + "line_start": 52141, + "line_end": 52141, + "char_start": 1407601, + "char_end": 1407605, + "column_start": 39, + "column_end": 43 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-379765630f6b651d0063", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7b2702dab7bab4a63cf0abe1a7da1997f26788e3c9ae1ef7eabe84b1f9c41f2a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 52225, + "line_end": 52225, + "char_start": 1410318, + "char_end": 1410428, + "column_start": 0, + "column_end": 110 + }, + "surface_location": { + "line_start": 52225, + "line_end": 52225, + "char_start": 1410400, + "char_end": 1410404, + "column_start": 82, + "column_end": 86 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f82fe9d318c53953b54c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "8fe92ced0f016f8b63403ed61f5fba7752b17334e553914c4e4269342fbb22c8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 53436, + "line_end": 53436, + "char_start": 1448189, + "char_end": 1448299, + "column_start": 0, + "column_end": 110 + }, + "surface_location": { + "line_start": 53436, + "line_end": 53436, + "char_start": 1448294, + "char_end": 1448298, + "column_start": 105, + "column_end": 109 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a1215c9e140400514b54", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0e0bbca3fe90124b8dc0a1807fefecc04f0fdce5a4d62e028f89ae32fd943928", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 53571, + "line_end": 53571, + "char_start": 1452527, + "char_end": 1452581, + "column_start": 0, + "column_end": 54 + }, + "surface_location": { + "line_start": 53571, + "line_end": 53571, + "char_start": 1452527, + "char_end": 1452531, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-329b7316686f810ab957", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "472c32c39848f6ea33320c1c7a8506801aa9ab06fc2d89794fcdec51f55c2182", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 56685, + "line_end": 56685, + "char_start": 1553535, + "char_end": 1553678, + "column_start": 32, + "column_end": 175 + }, + "surface_location": { + "line_start": 56685, + "line_end": 56685, + "char_start": 1553657, + "char_end": 1553661, + "column_start": 154, + "column_end": 158 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c9b47c94dedbe6c0b49a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d4e3ba6ba66fd231fba0be86ac9a9d895e7db9730548ddf45c4a72a7d6d08452", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 57168, + "line_end": 57168, + "char_start": 1567443, + "char_end": 1567559, + "column_start": 0, + "column_end": 116 + }, + "surface_location": { + "line_start": 57168, + "line_end": 57168, + "char_start": 1567453, + "char_end": 1567457, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1bc3dc03aaea26d9b2ee", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "39e8b9bbcff00c54c0a6b36dddb1c08f64b4218c3ee3171ed0f475108da52cac", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 57183, + "line_end": 57183, + "char_start": 1567911, + "char_end": 1568026, + "column_start": 0, + "column_end": 115 + }, + "surface_location": { + "line_start": 57183, + "line_end": 57183, + "char_start": 1567960, + "char_end": 1567964, + "column_start": 49, + "column_end": 53 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ca49f4d8055246c8ebe2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "64ce3e7c7c0d474779dad5ecf4e1d04e8d09fe30bd92497d6c7d759fa7d70592", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 57642, + "line_end": 57642, + "char_start": 1583726, + "char_end": 1583775, + "column_start": 103, + "column_end": 152 + }, + "surface_location": { + "line_start": 57642, + "line_end": 57642, + "char_start": 1583760, + "char_end": 1583764, + "column_start": 137, + "column_end": 141 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-78b0a7f3a370d521d9a0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "29fcac0f70279a8ede99d6b5550e2c9420a511f6cd505c379b44d4463ed88970", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 62122, + "line_end": 62122, + "char_start": 1735133, + "char_end": 1735164, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 62122, + "line_end": 62122, + "char_start": 1735134, + "char_end": 1735140, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-13859754b067937814df", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0335cceccf2088b8f13b50a9fc230059e3dc1c99a4607914eeba93a688e521f4", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 62443, + "line_end": 62443, + "char_start": 1746469, + "char_end": 1746561, + "column_start": 0, + "column_end": 92 + }, + "surface_location": { + "line_start": 62443, + "line_end": 62443, + "char_start": 1746491, + "char_end": 1746495, + "column_start": 22, + "column_end": 26 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cd88653b866518370886", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "cafeb64f627073c2e452aea4fbdf57565a4a960b60f83700979318aa4392b213", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 62799, + "line_end": 62799, + "char_start": 1758122, + "char_end": 1758248, + "column_start": 0, + "column_end": 126 + }, + "surface_location": { + "line_start": 62799, + "line_end": 62799, + "char_start": 1758207, + "char_end": 1758211, + "column_start": 85, + "column_end": 89 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4fa5dfc78890a32dc466", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "24d2a42134b85abd60595a3ee5904eaa61a5835e92616dd1f7019f84d5a8cc4b", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 63539, + "line_end": 63539, + "char_start": 1783251, + "char_end": 1783325, + "column_start": 0, + "column_end": 74 + }, + "surface_location": { + "line_start": 63539, + "line_end": 63539, + "char_start": 1783266, + "char_end": 1783270, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ee2ec09596777f9815bb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b7eb93e991fa1ac082eeaa0cfa058672e5cd4f41045600670aeca4816f7fdf35", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 65176, + "line_end": 65176, + "char_start": 1836752, + "char_end": 1836824, + "column_start": 0, + "column_end": 72 + }, + "surface_location": { + "line_start": 65176, + "line_end": 65176, + "char_start": 1836753, + "char_end": 1836757, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6fa3e3053210f09195f3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "5c3be7442ee4bb35835c189cb331093b6eccc258f5051a1f939ec265ddcba0a8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 65656, + "line_end": 65656, + "char_start": 1853172, + "char_end": 1853177, + "column_start": 0, + "column_end": 5 + }, + "surface_location": { + "line_start": 65656, + "line_end": 65656, + "char_start": 1853172, + "char_end": 1853176, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5f411c4cc6ca8180f257", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c28d495ba8abb2a7bbc68f3053cd548b113e59e99d1aef6975608b7a0871eaba", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 67803, + "line_end": 67803, + "char_start": 1928354, + "char_end": 1928489, + "column_start": 0, + "column_end": 135 + }, + "surface_location": { + "line_start": 67803, + "line_end": 67803, + "char_start": 1928418, + "char_end": 1928422, + "column_start": 64, + "column_end": 68 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8ac2c4b70d425107d74a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f12ea03338a08c35c002697a78b3124c88182dce6d23b923ca731bf718f6b93f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 70627, + "line_end": 70627, + "char_start": 2018625, + "char_end": 2018680, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 70627, + "line_end": 70627, + "char_start": 2018633, + "char_end": 2018637, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bd1448f26ccb8603f5cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "2909d73098baf617084785870196ae1d3c79bdf1740766cae2e09c3db28a9fd5", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 72036, + "line_end": 72036, + "char_start": 2065734, + "char_end": 2065885, + "column_start": 0, + "column_end": 151 + }, + "surface_location": { + "line_start": 72036, + "line_end": 72036, + "char_start": 2065854, + "char_end": 2065858, + "column_start": 120, + "column_end": 124 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-593b88aa3f0dd8f2130f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "6e9fe4167f2ae818f62d999014851dce7380cf377dfd7880fe0b726e7c0e407e", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 72219, + "line_end": 72219, + "char_start": 2071163, + "char_end": 2071224, + "column_start": 83, + "column_end": 144 + }, + "surface_location": { + "line_start": 72219, + "line_end": 72219, + "char_start": 2071163, + "char_end": 2071167, + "column_start": 83, + "column_end": 87 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e9c3a20231ef703a261a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "22067e2c61423a8d829bc3a89d434b2e236d7cbdacf302f256b4784e0518144f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 76219, + "line_end": 76219, + "char_start": 2208537, + "char_end": 2208625, + "column_start": 0, + "column_end": 88 + }, + "surface_location": { + "line_start": 76219, + "line_end": 76219, + "char_start": 2208584, + "char_end": 2208588, + "column_start": 47, + "column_end": 51 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-29a0baa12e4f14611372", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "59dde917a07b518d2884d7539b437d39751c12e02868fcc531cc08f04d8423ae", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 76333, + "line_end": 76333, + "char_start": 2212869, + "char_end": 2212964, + "column_start": 0, + "column_end": 95 + }, + "surface_location": { + "line_start": 76333, + "line_end": 76333, + "char_start": 2212884, + "char_end": 2212888, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-568b594cc17dba14c0e5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "188ff154f11f5bcc065a4667ee94da2bf2e788faeb92f385a097f016e6e458fa", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 78172, + "line_end": 78172, + "char_start": 2274886, + "char_end": 2275012, + "column_start": 0, + "column_end": 126 + }, + "surface_location": { + "line_start": 78172, + "line_end": 78172, + "char_start": 2274970, + "char_end": 2274974, + "column_start": 84, + "column_end": 88 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ebf50988da3b34598b63", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "def16a8f5c4a187f2b98e1c540f2bb97643fefa5ee94238cad36df4f8c5c33c9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 80161, + "line_end": 80161, + "char_start": 2346477, + "char_end": 2346572, + "column_start": 0, + "column_end": 95 + }, + "surface_location": { + "line_start": 80161, + "line_end": 80161, + "char_start": 2346541, + "char_end": 2346545, + "column_start": 64, + "column_end": 68 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8fd5f361e3f3b41faa18", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "cf59d762b7f386142acda40bb44660803003d0043a6f76f95368a010aa8ce2c7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 81546, + "line_end": 81546, + "char_start": 2400742, + "char_end": 2400904, + "column_start": 0, + "column_end": 162 + }, + "surface_location": { + "line_start": 81546, + "line_end": 81546, + "char_start": 2400836, + "char_end": 2400840, + "column_start": 94, + "column_end": 98 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b9afed3d38fa9473e210", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "22dd07ae60e6ab6bc2ff5b76671ff396b100aef4eb3a6ac28d0628e948649093", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 84429, + "line_end": 84429, + "char_start": 2514125, + "char_end": 2514252, + "column_start": 124, + "column_end": 251 + }, + "surface_location": { + "line_start": 84429, + "line_end": 84429, + "char_start": 2514168, + "char_end": 2514172, + "column_start": 167, + "column_end": 171 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a1d0c5c963376e26eaef", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "449c2f2d285e08584529ca30f3d412bb7cecf37f175d39228b3d384dd54477a7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 87113, + "line_end": 87113, + "char_start": 2618086, + "char_end": 2618191, + "column_start": 0, + "column_end": 105 + }, + "surface_location": { + "line_start": 87113, + "line_end": 87113, + "char_start": 2618107, + "char_end": 2618111, + "column_start": 21, + "column_end": 25 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-242c62253834887a1c78", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "05d0da3535a2edc13f3b2799af7a1f19449296777545a8b7317ce43e6c5d72c0", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 89506, + "line_end": 89506, + "char_start": 2707438, + "char_end": 2707547, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 89506, + "line_end": 89506, + "char_start": 2707518, + "char_end": 2707522, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b77c4493dc75b68c4dc2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a217e79a5734b8f1a30108a861e27762a57e760aa4452c51d16eb57aedff57f8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 90274, + "line_end": 90274, + "char_start": 2734520, + "char_end": 2734685, + "column_start": 0, + "column_end": 165 + }, + "surface_location": { + "line_start": 90274, + "line_end": 90274, + "char_start": 2734573, + "char_end": 2734577, + "column_start": 53, + "column_end": 57 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-347e07cc9077c6206f89", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1b61ca3f355c9cb7da2a7c76563f58cb4b51e12eee3b2094dd3b4fdefb0ca846", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 91498, + "line_end": 91501, + "char_start": 2775597, + "char_end": 2775777, + "column_start": 122, + "column_end": 170 + }, + "surface_location": { + "line_start": 91501, + "line_end": 91501, + "char_start": 2775687, + "char_end": 2775691, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c9d4560816be43e1ea8e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0ac9bf059daff88b1c8048ac032ecaeadcafa9cde868f394276a5a6508282c53", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 92320, + "line_end": 92320, + "char_start": 2803699, + "char_end": 2803821, + "column_start": 0, + "column_end": 122 + }, + "surface_location": { + "line_start": 92320, + "line_end": 92320, + "char_start": 2803700, + "char_end": 2803704, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b8260342c11aab84136a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "99cbd4cb3734f8afa8f94ba71b9754a3b46ab0358ff0610206b2350cec3aa176", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 93070, + "line_end": 93070, + "char_start": 2829823, + "char_end": 2829943, + "column_start": 0, + "column_end": 120 + }, + "surface_location": { + "line_start": 93070, + "line_end": 93070, + "char_start": 2829906, + "char_end": 2829910, + "column_start": 83, + "column_end": 87 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7eb7ccd8791191f1749e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a7349cfbde24725c7e485a0ce2d6a39f62541e83e29b2fef83fe2f4880bd44f5", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 94585, + "line_end": 94585, + "char_start": 2873789, + "char_end": 2873875, + "column_start": 0, + "column_end": 86 + }, + "surface_location": { + "line_start": 94585, + "line_end": 94585, + "char_start": 2873791, + "char_end": 2873795, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6258c8d3b4431ed8d71f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7afbed830d13aabd95726c2d0ebb1fbe444578fecce51a4dab7a5143d760346e", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 95854, + "line_end": 95854, + "char_start": 2908485, + "char_end": 2908569, + "column_start": 0, + "column_end": 84 + }, + "surface_location": { + "line_start": 95854, + "line_end": 95854, + "char_start": 2908524, + "char_end": 2908528, + "column_start": 39, + "column_end": 43 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e5ee016e399ad4197c4a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "40405da700fcc2f6d1585698b460724e434068eeb5ea35b6c01317dc57127067", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 96459, + "line_end": 96459, + "char_start": 2923875, + "char_end": 2923980, + "column_start": 0, + "column_end": 105 + }, + "surface_location": { + "line_start": 96459, + "line_end": 96459, + "char_start": 2923948, + "char_end": 2923952, + "column_start": 73, + "column_end": 77 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a7aac5d12d1f26a72d34", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d7eb0bd957023d4cbcc1afb35b503b3dfa04d65a6010d692af3d282f7df9537d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 98699, + "line_end": 98699, + "char_start": 2985089, + "char_end": 2985183, + "column_start": 0, + "column_end": 94 + }, + "surface_location": { + "line_start": 98699, + "line_end": 98699, + "char_start": 2985090, + "char_end": 2985094, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a8c26010a16830197c43", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "91c35f05044ff55c66465511bde0c2dd049a5f351101efb2a6a4302952534e51", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 99550, + "line_end": 99550, + "char_start": 3009726, + "char_end": 3009835, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 99550, + "line_end": 99550, + "char_start": 3009726, + "char_end": 3009730, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9c76ab698657d78a95c5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0da47451e5da727610272d4f49ddada28f3a4eda230b3803c18941452f5cdd44", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 100159, + "line_end": 100159, + "char_start": 3027545, + "char_end": 3027624, + "column_start": 0, + "column_end": 79 + }, + "surface_location": { + "line_start": 100159, + "line_end": 100159, + "char_start": 3027586, + "char_end": 3027590, + "column_start": 41, + "column_end": 45 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f44850934dbcb7be8f8e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0db9fcff68a53bc522da76ec8299e2424ef6bf98bbddfcb7c8add847b0a1a550", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 101176, + "line_end": 101176, + "char_start": 3057999, + "char_end": 3058124, + "column_start": 0, + "column_end": 125 + }, + "surface_location": { + "line_start": 101176, + "line_end": 101176, + "char_start": 3058068, + "char_end": 3058072, + "column_start": 69, + "column_end": 73 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7dce86e88140c43faf5e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f3323bb3375b73b89322329e7a32f5033bd6455b596ceb67d2a85465a7d3b95d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 106041, + "line_end": 106041, + "char_start": 3199617, + "char_end": 3199749, + "column_start": 0, + "column_end": 132 + }, + "surface_location": { + "line_start": 106041, + "line_end": 106041, + "char_start": 3199650, + "char_end": 3199654, + "column_start": 33, + "column_end": 37 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d1c96646c6366911f4bc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "72e738bd8c60317bb95edafab1f084585bf090afb619b7449b5b4c1bd51b67b7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 108240, + "line_end": 108240, + "char_start": 3262363, + "char_end": 3262480, + "column_start": 0, + "column_end": 117 + }, + "surface_location": { + "line_start": 108240, + "line_end": 108240, + "char_start": 3262443, + "char_end": 3262447, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-756151a841e0fc189076", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b1c49c86e94f8d73be69131b144de1af0b8f5ce72d35de59be22c023f87c88dc", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 108279, + "line_end": 108279, + "char_start": 3263519, + "char_end": 3263611, + "column_start": 0, + "column_end": 92 + }, + "surface_location": { + "line_start": 108279, + "line_end": 108279, + "char_start": 3263547, + "char_end": 3263551, + "column_start": 28, + "column_end": 32 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7aa6c2787f72599ed91e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1acf025a44a8b1a0fcf191f15e264b8c5f24bdb4547eb56628a8eacb62a225a7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 109272, + "line_end": 109272, + "char_start": 3293119, + "char_end": 3293235, + "column_start": 0, + "column_end": 116 + }, + "surface_location": { + "line_start": 109272, + "line_end": 109272, + "char_start": 3293187, + "char_end": 3293191, + "column_start": 68, + "column_end": 72 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f948bc6f6b4d78c4a805", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "4cadba0b31f5185711c063a90bcb146d9f18281ad3f92ce27a4e0a186c0002a6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 110949, + "line_end": 110949, + "char_start": 3339725, + "char_end": 3339832, + "column_start": 0, + "column_end": 107 + }, + "surface_location": { + "line_start": 110949, + "line_end": 110949, + "char_start": 3339725, + "char_end": 3339729, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-19be5a760cd432de9557", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "82e3c1c052822d86da0a22c32db5151c0caf801f5f2555739a39047baacf4211", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 113340, + "line_end": 113340, + "char_start": 3412819, + "char_end": 3412912, + "column_start": 0, + "column_end": 93 + }, + "surface_location": { + "line_start": 113340, + "line_end": 113340, + "char_start": 3412868, + "char_end": 3412872, + "column_start": 49, + "column_end": 53 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-60692f51c8bd30c0e305", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "8989d332f4f56b9a32d6652acca385281cee0cfe83f2d97b4b113eaae7c7eed9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 115272, + "line_end": 115272, + "char_start": 3475698, + "char_end": 3475824, + "column_start": 0, + "column_end": 126 + }, + "surface_location": { + "line_start": 115272, + "line_end": 115272, + "char_start": 3475794, + "char_end": 3475798, + "column_start": 96, + "column_end": 100 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5988d91f7188b6a4c96f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d74db412918e183e7c8aad8b193f7de6bbbf4d84fc60a7cbf9cfbb251763c1ac", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 115425, + "line_end": 115425, + "char_start": 3481221, + "char_end": 3481301, + "column_start": 0, + "column_end": 80 + }, + "surface_location": { + "line_start": 115425, + "line_end": 115425, + "char_start": 3481246, + "char_end": 3481250, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-89e9437d238dda667f60", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "71f1d13a58fd665528011d547c94ad817a02240162ba9762e40f9e140a70c439", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 118161, + "line_end": 118161, + "char_start": 3561856, + "char_end": 3561964, + "column_start": 0, + "column_end": 108 + }, + "surface_location": { + "line_start": 118161, + "line_end": 118161, + "char_start": 3561930, + "char_end": 3561934, + "column_start": 74, + "column_end": 78 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ad3e634c5cfd881fff79", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a69f1150a8712c76d9ad63b75a2d0d082ca43198bfb740ba4ae2a5eb96ff5fce", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 119103, + "line_end": 119103, + "char_start": 3594301, + "char_end": 3594449, + "column_start": 0, + "column_end": 148 + }, + "surface_location": { + "line_start": 119103, + "line_end": 119103, + "char_start": 3594406, + "char_end": 3594410, + "column_start": 105, + "column_end": 109 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2e9f849caffa7202f8b2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b540042eec91d215f77531ef88e44bed21bfff3fdca8ecd0100e04d02ee6a329", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 121452, + "line_end": 121452, + "char_start": 3672420, + "char_end": 3672543, + "column_start": 0, + "column_end": 123 + }, + "surface_location": { + "line_start": 121452, + "line_end": 121452, + "char_start": 3672480, + "char_end": 3672486, + "column_start": 60, + "column_end": 66 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5e41e0f2f28c68f387b6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "be4bc98352e35c6517e1aba46d106fbf2f9a62a6694f0252a3477354ab711ead", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 122790, + "line_end": 122790, + "char_start": 3714470, + "char_end": 3714565, + "column_start": 0, + "column_end": 95 + }, + "surface_location": { + "line_start": 122790, + "line_end": 122790, + "char_start": 3714471, + "char_end": 3714477, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-075c17cc50dda39d256f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a2d01a7721924e1feaa823f92024d562d4ed1521e716b0e76e0929bab542f461", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 123549, + "line_end": 123549, + "char_start": 3736526, + "char_end": 3736655, + "column_start": 0, + "column_end": 129 + }, + "surface_location": { + "line_start": 123549, + "line_end": 123549, + "char_start": 3736527, + "char_end": 3736533, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-79fabfe15e9f02881787", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d59cf111f434a592e8855e1ad52c67bb304aaba5d3a8a8c015b11eb19032db4a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 127425, + "line_end": 127425, + "char_start": 3841868, + "char_end": 3841998, + "column_start": 0, + "column_end": 130 + }, + "surface_location": { + "line_start": 127425, + "line_end": 127425, + "char_start": 3841970, + "char_end": 3841976, + "column_start": 102, + "column_end": 108 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1d48561cdc0421272de7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b5d8492957d126e26fe8f8a685ac815130ed497a7b9c2185d0667dc3e414fc49", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 130401, + "line_end": 130401, + "char_start": 3921512, + "char_end": 3921580, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 130401, + "line_end": 130401, + "char_start": 3921534, + "char_end": 3921538, + "column_start": 22, + "column_end": 26 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ea0375b5baff7c32263b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "5c3be7442ee4bb35835c189cb331093b6eccc258f5051a1f939ec265ddcba0a8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 133794, + "line_end": 133794, + "char_start": 4006059, + "char_end": 4006064, + "column_start": 0, + "column_end": 5 + }, + "surface_location": { + "line_start": 133794, + "line_end": 133794, + "char_start": 4006059, + "char_end": 4006063, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ecac1a7d9a5118ac1131", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "4c5a8486051ed8896cfda6b92ab6f1de7a796e8b50d57036587f96756188b32a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 134979, + "line_end": 134979, + "char_start": 4038337, + "char_end": 4038445, + "column_start": 0, + "column_end": 108 + }, + "surface_location": { + "line_start": 134979, + "line_end": 134979, + "char_start": 4038440, + "char_end": 4038444, + "column_start": 103, + "column_end": 107 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1b63e59f6c302781e622", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d59cf111f434a592e8855e1ad52c67bb304aaba5d3a8a8c015b11eb19032db4a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 138276, + "line_end": 138276, + "char_start": 4137875, + "char_end": 4138005, + "column_start": 0, + "column_end": 130 + }, + "surface_location": { + "line_start": 138276, + "line_end": 138276, + "char_start": 4137977, + "char_end": 4137983, + "column_start": 102, + "column_end": 108 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-eb06c0654f009bc62373", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "ac5b22960a88b625229fff7de76864aa409e82db729659d904aea3a0f62c9740", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 140061, + "line_end": 140061, + "char_start": 4187760, + "char_end": 4187862, + "column_start": 0, + "column_end": 102 + }, + "surface_location": { + "line_start": 140061, + "line_end": 140061, + "char_start": 4187760, + "char_end": 4187766, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1df3221dc117e9ff6d66", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "8651a5ea4d417414a9b33fe4ef9cbbe79ec130fae7e10f4d2630a7666207f5d8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 141474, + "line_end": 141474, + "char_start": 4226453, + "char_end": 4226528, + "column_start": 0, + "column_end": 75 + }, + "surface_location": { + "line_start": 141474, + "line_end": 141474, + "char_start": 4226512, + "char_end": 4226516, + "column_start": 59, + "column_end": 63 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5dfb8056599f1d690594", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0de9f677f2d96a3ff8c2ed985d972d9e1adef17f7a36d0804c0325b9d47ff023", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 143820, + "line_end": 143820, + "char_start": 4289364, + "char_end": 4289432, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 143820, + "line_end": 143820, + "char_start": 4289364, + "char_end": 4289368, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-816d99e075f453cef811", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c3251249a73db64fbb2b88dda14ff06e35b5039d6062ac279702b2e4d35ad746", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 146853, + "line_end": 146853, + "char_start": 4381469, + "char_end": 4381602, + "column_start": 0, + "column_end": 133 + }, + "surface_location": { + "line_start": 146853, + "line_end": 146853, + "char_start": 4381537, + "char_end": 4381543, + "column_start": 68, + "column_end": 74 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c3203755529b62d978e3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a8920709019ae31259ce70ec9ada0639dbab76b69af632c09b4bea0cd31eca72", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 150264, + "line_end": 150264, + "char_start": 4478987, + "char_end": 4479126, + "column_start": 0, + "column_end": 139 + }, + "surface_location": { + "line_start": 150264, + "line_end": 150264, + "char_start": 4478988, + "char_end": 4478994, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f90a8c4ab3e5f6364acb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "3a0d05a36ef40e3e8e0b054f3a2774345820217ccb59b6ebc0020740a7f10f7a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 152166, + "line_end": 152166, + "char_start": 4535923, + "char_end": 4536037, + "column_start": 0, + "column_end": 114 + }, + "surface_location": { + "line_start": 152166, + "line_end": 152166, + "char_start": 4535923, + "char_end": 4535929, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1f5c06a6e50f0105bdc9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "176ee1c7436910140cc7903bff62d389bcbfa7dea2c74efce243835cbd4097e6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 154341, + "line_end": 154341, + "char_start": 4603288, + "char_end": 4603460, + "column_start": 0, + "column_end": 172 + }, + "surface_location": { + "line_start": 154341, + "line_end": 154341, + "char_start": 4603421, + "char_end": 4603425, + "column_start": 133, + "column_end": 137 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-528e715ff3ed3df43fee", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "2ea270964791ade0aaec993958b5ea006249fb3b46bff3b89469be6dabaed2ac", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 158784, + "line_end": 158784, + "char_start": 4737124, + "char_end": 4737242, + "column_start": 0, + "column_end": 118 + }, + "surface_location": { + "line_start": 158784, + "line_end": 158784, + "char_start": 4737124, + "char_end": 4737130, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ce051adc75552b1c79f6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e60b6120ec25c03fa97fa422e9bbb0e94517ac8f4316204bbee105310f2bb384", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 159369, + "line_end": 159372, + "char_start": 4756606, + "char_end": 4756786, + "column_start": 73, + "column_end": 109 + }, + "surface_location": { + "line_start": 159372, + "line_end": 159372, + "char_start": 4756696, + "char_end": 4756700, + "column_start": 19, + "column_end": 23 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2fad2b0f2580c1123355", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "34b3ce4243ab28ec1c448b899014e09a7fd46045644827cef065618b80133da7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 162000, + "line_end": 162000, + "char_start": 4839593, + "char_end": 4839714, + "column_start": 0, + "column_end": 121 + }, + "surface_location": { + "line_start": 162000, + "line_end": 162000, + "char_start": 4839593, + "char_end": 4839599, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a38279b1ba9ed2bef49c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "3ca38bde7f9d6c60fbfd62baa5b55f3b73c98422a1bca3f45e335a28045b0f00", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 166851, + "line_end": 166851, + "char_start": 4989799, + "char_end": 4989889, + "column_start": 0, + "column_end": 90 + }, + "surface_location": { + "line_start": 166851, + "line_end": 166851, + "char_start": 4989801, + "char_end": 4989807, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dad4b27d350ab479de79", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e2af834739816b659725479114c0026cd3950f7b11d3099f12dce3a68bc94ac1", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 167562, + "line_end": 167562, + "char_start": 5009731, + "char_end": 5009844, + "column_start": 20, + "column_end": 133 + }, + "surface_location": { + "line_start": 167562, + "line_end": 167562, + "char_start": 5009808, + "char_end": 5009812, + "column_start": 97, + "column_end": 101 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-89ca35f2a960f7db8eb3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "5b2a5a01f8d4236a44460f8ccecff62b498621e1993928e92a43b23bbd825b5f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 171408, + "line_end": 171408, + "char_start": 5126801, + "char_end": 5126951, + "column_start": 0, + "column_end": 150 + }, + "surface_location": { + "line_start": 171408, + "line_end": 171408, + "char_start": 5126923, + "char_end": 5126929, + "column_start": 122, + "column_end": 128 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1e0b575732724d5ca04a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b1c147fb3050b02d031146ae14092f46895547926838421c772ae987a8cb7ade", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 172485, + "line_end": 172485, + "char_start": 5160092, + "char_end": 5160203, + "column_start": 0, + "column_end": 111 + }, + "surface_location": { + "line_start": 172485, + "line_end": 172485, + "char_start": 5160174, + "char_end": 5160178, + "column_start": 82, + "column_end": 86 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-97043f89fc33e3e558b3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f8b9f7748453aba35c53711d2c6da97b4b6aeb924ee7e9612ba0bb6219d31a7b", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 173667, + "line_end": 173667, + "char_start": 5198671, + "char_end": 5198811, + "column_start": 0, + "column_end": 140 + }, + "surface_location": { + "line_start": 173667, + "line_end": 173667, + "char_start": 5198671, + "char_end": 5198677, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f251a35855fe077f1b88", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c0b51526e75dddbac06486f54029a49e6073794cb3fd4ed344e6d2a293e53327", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 175395, + "line_end": 175395, + "char_start": 5241537, + "char_end": 5241636, + "column_start": 0, + "column_end": 99 + }, + "surface_location": { + "line_start": 175395, + "line_end": 175395, + "char_start": 5241602, + "char_end": 5241606, + "column_start": 65, + "column_end": 69 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8945bd6caa6878584d03", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "6d17a0142bcd7969bed7bfb7e96edbb055386c754e3c46b2df4fa77adc268218", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 178002, + "line_end": 178002, + "char_start": 5317938, + "char_end": 5318031, + "column_start": 0, + "column_end": 93 + }, + "surface_location": { + "line_start": 178002, + "line_end": 178002, + "char_start": 5317939, + "char_end": 5317943, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0fcb7e765014026160cc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "97b1f684483996fae221a11e4ae272de3516f8df12e349b819c1961709e216c9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 179886, + "line_end": 179886, + "char_start": 5375761, + "char_end": 5375888, + "column_start": 0, + "column_end": 127 + }, + "surface_location": { + "line_start": 179886, + "line_end": 179886, + "char_start": 5375803, + "char_end": 5375807, + "column_start": 42, + "column_end": 46 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bc31c81cca9ef2e2fe4d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7b118e9360dcb346d6d27c0e732cb16a1c12e0e5803ee3323c6c1eb60ddc24f3", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 180132, + "line_end": 180132, + "char_start": 5384812, + "char_end": 5384912, + "column_start": 0, + "column_end": 100 + }, + "surface_location": { + "line_start": 180132, + "line_end": 180132, + "char_start": 5384863, + "char_end": 5384869, + "column_start": 51, + "column_end": 57 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-33d4aee719c4164c6054", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e0ae6c9a166044f3166841c9c557216c7bc7359df28277ea4839580bda251527", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 180984, + "line_end": 180984, + "char_start": 5409520, + "char_end": 5409629, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 180984, + "line_end": 180984, + "char_start": 5409520, + "char_end": 5409524, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8f96f35402c86faf769f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1b01528fe02e335edb7b8d1092521812e1daa462722cbdf7a4d4fd9b8db19fa6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 181722, + "line_end": 181722, + "char_start": 5434878, + "char_end": 5434996, + "column_start": 0, + "column_end": 118 + }, + "surface_location": { + "line_start": 181722, + "line_end": 181722, + "char_start": 5434975, + "char_end": 5434981, + "column_start": 97, + "column_end": 103 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-38e05c61180e343f5159", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f19e16f9a927f565eb5cf34805a72c8cae0ee73279ecb6dc97288e81d5902a17", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 181761, + "line_end": 181761, + "char_start": 5436089, + "char_end": 5436253, + "column_start": 0, + "column_end": 164 + }, + "surface_location": { + "line_start": 181761, + "line_end": 181761, + "char_start": 5436090, + "char_end": 5436096, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1ea3970e862b2e67fa1a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d3d5b4e4dd6fc7b1406a2d33d30832e66db8d23a6bdc211edbe80ac5f5c25979", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 181980, + "line_end": 181980, + "char_start": 5443585, + "char_end": 5443646, + "column_start": 0, + "column_end": 61 + }, + "surface_location": { + "line_start": 181980, + "line_end": 181980, + "char_start": 5443587, + "char_end": 5443593, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-add768ddd18ef109ba3c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "5aac4ef19a63c97eb90a4ba5ebd23ce3d8535d9e132143c6f17cb46320dc1eee", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 182955, + "line_end": 182955, + "char_start": 5473897, + "char_end": 5473971, + "column_start": 0, + "column_end": 74 + }, + "surface_location": { + "line_start": 182955, + "line_end": 182955, + "char_start": 5473898, + "char_end": 5473904, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e430845d44e9710235fd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "3ccc3b7c611b476e4dc01942d76c6f0cecc9e47722917b54f8a4d64b5c44533e", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 183870, + "line_end": 183870, + "char_start": 5499967, + "char_end": 5500080, + "column_start": 0, + "column_end": 113 + }, + "surface_location": { + "line_start": 183870, + "line_end": 183870, + "char_start": 5499967, + "char_end": 5499973, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d2c64405c088bf18c14e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "bfb0d4c57c84790d4a74939ee161e31441bc19e700bb8175b5e8069eb2cb06e6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 184269, + "line_end": 184272, + "char_start": 5512743, + "char_end": 5512923, + "column_start": 96, + "column_end": 133 + }, + "surface_location": { + "line_start": 184272, + "line_end": 184272, + "char_start": 5512833, + "char_end": 5512839, + "column_start": 43, + "column_end": 49 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cee54e049ced73227249", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7d542646edc5fb71693f17c64ff749e6301e4b3b29dafcf2cfb2b02cad4503b8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 185454, + "line_end": 185454, + "char_start": 5550598, + "char_end": 5550720, + "column_start": 0, + "column_end": 122 + }, + "surface_location": { + "line_start": 185454, + "line_end": 185454, + "char_start": 5550598, + "char_end": 5550604, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-02f2a85e229574cfb475", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c62897f97579f8321d3a3a1cf83bfbf8de38b95a4302c3511f84cd07775ee182", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 186633, + "line_end": 186633, + "char_start": 5586018, + "char_end": 5586146, + "column_start": 0, + "column_end": 128 + }, + "surface_location": { + "line_start": 186633, + "line_end": 186633, + "char_start": 5586141, + "char_end": 5586145, + "column_start": 123, + "column_end": 127 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b37a19d7a73931aabb41", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "cd8e1c5287169ddcaafc7ae2ff425b896da9e4ed117fe738838705548273e3a1", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 192486, + "line_end": 192486, + "char_start": 5742188, + "char_end": 5742264, + "column_start": 0, + "column_end": 76 + }, + "surface_location": { + "line_start": 192486, + "line_end": 192486, + "char_start": 5742236, + "char_end": 5742240, + "column_start": 48, + "column_end": 52 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-42d069e90ca9bed66e16", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "820c8d85cbcc2e69a129a0dedf7191f26cf691ed6b6c86e0740b389cec08552d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 196221, + "line_end": 196221, + "char_start": 5835675, + "char_end": 5835776, + "column_start": 0, + "column_end": 101 + }, + "surface_location": { + "line_start": 196221, + "line_end": 196221, + "char_start": 5835745, + "char_end": 5835749, + "column_start": 70, + "column_end": 74 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-930851c52bb079277df7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "44c1cc878378256087763a23c06481b9c31d96cb80a92572db51ce9d601a0b60", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 200613, + "line_end": 200613, + "char_start": 5961512, + "char_end": 5961592, + "column_start": 0, + "column_end": 80 + }, + "surface_location": { + "line_start": 200613, + "line_end": 200613, + "char_start": 5961557, + "char_end": 5961563, + "column_start": 45, + "column_end": 51 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-25683d64360d402a6cfb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7b2f165e9273d6216cfac092781b3066bb930d2b7716c3522ffbe19a47cbf56f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 201588, + "line_end": 201588, + "char_start": 5986280, + "char_end": 5986340, + "column_start": 0, + "column_end": 60 + }, + "surface_location": { + "line_start": 201588, + "line_end": 201588, + "char_start": 5986280, + "char_end": 5986284, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d733a530778d1239f820", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "59e02ee5b490e7cd91affc9778e6545b90e075c1fd507b2954b0d7385e31dee9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 202569, + "line_end": 202569, + "char_start": 6020270, + "char_end": 6020384, + "column_start": 0, + "column_end": 114 + }, + "surface_location": { + "line_start": 202569, + "line_end": 202569, + "char_start": 6020318, + "char_end": 6020322, + "column_start": 48, + "column_end": 52 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-733fec2b72ddd360a433", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "449c2f2d285e08584529ca30f3d412bb7cecf37f175d39228b3d384dd54477a7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 204996, + "line_end": 204996, + "char_start": 6114495, + "char_end": 6114600, + "column_start": 0, + "column_end": 105 + }, + "surface_location": { + "line_start": 204996, + "line_end": 204996, + "char_start": 6114516, + "char_end": 6114520, + "column_start": 21, + "column_end": 25 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b1b9e5e7d9a83ff2f575", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "64ff033beddc76e5ca897864f8a0dda4c42ba3300349090c9fb255154cddd59f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 206856, + "line_end": 206856, + "char_start": 6182295, + "char_end": 6182422, + "column_start": 0, + "column_end": 127 + }, + "surface_location": { + "line_start": 206856, + "line_end": 206856, + "char_start": 6182355, + "char_end": 6182359, + "column_start": 60, + "column_end": 64 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fac853dc60ade5da2f75", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "05d0da3535a2edc13f3b2799af7a1f19449296777545a8b7317ce43e6c5d72c0", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 208731, + "line_end": 208731, + "char_start": 6249014, + "char_end": 6249123, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 208731, + "line_end": 208731, + "char_start": 6249094, + "char_end": 6249098, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2c3a8ea1885f109f776e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "87165b0b9770074546c43d65142346bcf4b6a440ccb7cca3787c5b5aa1b1f2cf", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 208887, + "line_end": 208887, + "char_start": 6254874, + "char_end": 6255007, + "column_start": 0, + "column_end": 133 + }, + "surface_location": { + "line_start": 208887, + "line_end": 208887, + "char_start": 6254960, + "char_end": 6254964, + "column_start": 86, + "column_end": 90 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-899d2266a81f4a7c01d9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a217e79a5734b8f1a30108a861e27762a57e760aa4452c51d16eb57aedff57f8", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 209628, + "line_end": 209628, + "char_start": 6280457, + "char_end": 6280622, + "column_start": 0, + "column_end": 165 + }, + "surface_location": { + "line_start": 209628, + "line_end": 209628, + "char_start": 6280510, + "char_end": 6280514, + "column_start": 53, + "column_end": 57 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-85b0c3d8933e60ea1613", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1b61ca3f355c9cb7da2a7c76563f58cb4b51e12eee3b2094dd3b4fdefb0ca846", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 210879, + "line_end": 210882, + "char_start": 6321406, + "char_end": 6321586, + "column_start": 122, + "column_end": 170 + }, + "surface_location": { + "line_start": 210882, + "line_end": 210882, + "char_start": 6321496, + "char_end": 6321500, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c2f092775425b86b65cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0ac9bf059daff88b1c8048ac032ecaeadcafa9cde868f394276a5a6508282c53", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 211749, + "line_end": 211749, + "char_start": 6350230, + "char_end": 6350352, + "column_start": 0, + "column_end": 122 + }, + "surface_location": { + "line_start": 211749, + "line_end": 211749, + "char_start": 6350231, + "char_end": 6350235, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-475891ef257243f0abce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1e28ae780a47e98f0c0d4accdd8fed26ab568f35570d9ab623ecb3027e1dca45", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 212718, + "line_end": 212718, + "char_start": 6382889, + "char_end": 6383051, + "column_start": 0, + "column_end": 162 + }, + "surface_location": { + "line_start": 212718, + "line_end": 212718, + "char_start": 6382991, + "char_end": 6382995, + "column_start": 102, + "column_end": 106 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-598dafabb47fd31ea7e6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "2895a7919669191003a0ddaac67a2cb081120f478d71e74ecea87c21bcf51722", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 212760, + "line_end": 212760, + "char_start": 6383991, + "char_end": 6384108, + "column_start": 0, + "column_end": 117 + }, + "surface_location": { + "line_start": 212760, + "line_end": 212760, + "char_start": 6384071, + "char_end": 6384075, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-844343a318344b0484f7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f73717c954097435236e18dfd1471de5ed8fa2c7c5af503bd568713a2d5509af", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 216927, + "line_end": 216927, + "char_start": 6499890, + "char_end": 6500004, + "column_start": 0, + "column_end": 114 + }, + "surface_location": { + "line_start": 216927, + "line_end": 216927, + "char_start": 6499972, + "char_end": 6499976, + "column_start": 82, + "column_end": 86 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c92159394b3129fe725e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d053aced2130951572265b7ce71fc8bd3f4164a828c9444f4dc0fb8cda01a38d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 219498, + "line_end": 219498, + "char_start": 6572255, + "char_end": 6572390, + "column_start": 0, + "column_end": 135 + }, + "surface_location": { + "line_start": 219498, + "line_end": 219498, + "char_start": 6572356, + "char_end": 6572360, + "column_start": 101, + "column_end": 105 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f9025f84c102f5b735c8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7867f44d469ea0835f225a99aa7d7b5535c3561e4d1cf69c8179804310644ba6", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 221079, + "line_end": 221079, + "char_start": 6617980, + "char_end": 6618037, + "column_start": 0, + "column_end": 57 + }, + "surface_location": { + "line_start": 221079, + "line_end": 221079, + "char_start": 6617980, + "char_end": 6617986, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-996a2c73dffbc1008f78", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "1291f1f8c8a8200037a8d142018aec1f7b005234f5b671c3d9106195839b42b2", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 223080, + "line_end": 223080, + "char_start": 6673132, + "char_end": 6673271, + "column_start": 0, + "column_end": 139 + }, + "surface_location": { + "line_start": 223080, + "line_end": 223080, + "char_start": 6673206, + "char_end": 6673210, + "column_start": 74, + "column_end": 78 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8965f7ce445f587d058e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "821008c3d0e6c81fb6a2f6e1f9246ad3425b0bded8a3abde98f6ee077f305977", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 223584, + "line_end": 223584, + "char_start": 6688845, + "char_end": 6688939, + "column_start": 0, + "column_end": 94 + }, + "surface_location": { + "line_start": 223584, + "line_end": 223584, + "char_start": 6688893, + "char_end": 6688899, + "column_start": 48, + "column_end": 54 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4ee9855a2dba216d9d0a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d4e0a40690272262be3e41197ffdf6d697c1352480d04ea2c78563099859918c", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 224283, + "line_end": 224283, + "char_start": 6710611, + "char_end": 6710785, + "column_start": 0, + "column_end": 174 + }, + "surface_location": { + "line_start": 224283, + "line_end": 224283, + "char_start": 6710611, + "char_end": 6710617, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7e10c6570e5686569f5b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "a39879d80df27ad7ca204d0a0329b57758a5dba6c305bf88c1d0c00c7d147f51", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 224979, + "line_end": 224979, + "char_start": 6733032, + "char_end": 6733065, + "column_start": 15, + "column_end": 48 + }, + "surface_location": { + "line_start": 224979, + "line_end": 224979, + "char_start": 6733060, + "char_end": 6733064, + "column_start": 43, + "column_end": 47 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cf606fed0f80df3731d3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "b2d0552d429417f6a83209db1b572120615dedf052ed8ba673422e971efa8fde", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 225780, + "line_end": 225780, + "char_start": 6757904, + "char_end": 6758050, + "column_start": 0, + "column_end": 146 + }, + "surface_location": { + "line_start": 225780, + "line_end": 225780, + "char_start": 6757908, + "char_end": 6757914, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-14a54c543698a121a029", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "7819cd035fba9613c6525b020f1e7edbadbd69ec0b166a645681461dcc83e6f0", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 226017, + "line_end": 226017, + "char_start": 6765975, + "char_end": 6766101, + "column_start": 0, + "column_end": 126 + }, + "surface_location": { + "line_start": 226017, + "line_end": 226017, + "char_start": 6765976, + "char_end": 6765982, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-54f095d0f28098aef600", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "40ed10b837109c5216e43381e64b0a575ce1564119dd382b645a72d0daeca829", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 226839, + "line_end": 226842, + "char_start": 6792282, + "char_end": 6792462, + "column_start": 15, + "column_end": 5 + }, + "surface_location": { + "line_start": 226839, + "line_end": 226839, + "char_start": 6792372, + "char_end": 6792376, + "column_start": 105, + "column_end": 109 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-19b1864795355c002ed9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "75df98ec18a551ef3c186ce8ab8d38e8fdb5897ee0267fd5f3e29fe5eef09f11", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 227955, + "line_end": 227955, + "char_start": 6824448, + "char_end": 6824556, + "column_start": 0, + "column_end": 108 + }, + "surface_location": { + "line_start": 227955, + "line_end": 227955, + "char_start": 6824450, + "char_end": 6824456, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-97fb98d1e4249c9a44c5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "2e3a9b9db7405b27c0b4cd0d1a77e54c6494e91fd84c320d299dad6eb4d0d908", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 229509, + "line_end": 229509, + "char_start": 6872421, + "char_end": 6872527, + "column_start": 0, + "column_end": 106 + }, + "surface_location": { + "line_start": 229509, + "line_end": 229509, + "char_start": 6872486, + "char_end": 6872492, + "column_start": 65, + "column_end": 71 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5d2c750da43a4c70951c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "bcbff45cc5a3bd0dc21d89870b95e5fc668029a1a4f55e5334c7406b8ceb2f1d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 230721, + "line_end": 230724, + "char_start": 6907458, + "char_end": 6907638, + "column_start": 142, + "column_end": 144 + }, + "surface_location": { + "line_start": 230724, + "line_end": 230724, + "char_start": 6907548, + "char_end": 6907552, + "column_start": 54, + "column_end": 58 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1356fc0993b064221dbf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "dbaf4ec7a1a748659e9932a17da6403fe887c6d6d218186b36eef908ba4a2ad0", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 231204, + "line_end": 231204, + "char_start": 6922648, + "char_end": 6922757, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 231204, + "line_end": 231204, + "char_start": 6922734, + "char_end": 6922738, + "column_start": 86, + "column_end": 90 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b79067fbfb7c2ff491e9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "abf16a459ac968debf3d895c96cbb6654047f7c301ca2690b20ef4cce377aa8d", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 233796, + "line_end": 233799, + "char_start": 7006741, + "char_end": 7006921, + "column_start": 40, + "column_end": 34 + }, + "surface_location": { + "line_start": 233796, + "line_end": 233796, + "char_start": 7006831, + "char_end": 7006837, + "column_start": 130, + "column_end": 136 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-999c9cf8fc5b5d85d611", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "26dc99f7507aab3d78346383bd9e3d9ebb3d47a5aae934a0d76fbb5ed41749cb", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 238221, + "line_end": 238221, + "char_start": 7147599, + "char_end": 7147685, + "column_start": 0, + "column_end": 86 + }, + "surface_location": { + "line_start": 238221, + "line_end": 238221, + "char_start": 7147600, + "char_end": 7147606, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-98736c9e85efeafdc69b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "20de64b0e3d7de3606bf4950b367ce5a5718dfe3c352c631b74f1576a47ab344", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 240825, + "line_end": 240825, + "char_start": 7225046, + "char_end": 7225205, + "column_start": 0, + "column_end": 159 + }, + "surface_location": { + "line_start": 240825, + "line_end": 240825, + "char_start": 7225091, + "char_end": 7225097, + "column_start": 45, + "column_end": 51 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c5c74324f69bbb5bea58", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e97b36bb3a1807bb7981f4c4c63c72a5adcba32b36c3681577a03ab1274f0fb9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 242439, + "line_end": 242439, + "char_start": 7273897, + "char_end": 7274007, + "column_start": 0, + "column_end": 110 + }, + "surface_location": { + "line_start": 242439, + "line_end": 242439, + "char_start": 7273957, + "char_end": 7273961, + "column_start": 60, + "column_end": 64 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ff441a7018e64303142e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "42c0538e183adb84d0b1da0057216d3925a63546f153dea48b386e508b57f1ef", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 243957, + "line_end": 243960, + "char_start": 7326938, + "char_end": 7327118, + "column_start": 169, + "column_end": 165 + }, + "surface_location": { + "line_start": 243960, + "line_end": 243960, + "char_start": 7327028, + "char_end": 7327032, + "column_start": 75, + "column_end": 79 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dbb11e5d2bfdfc771ccb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "2510c44bcc89a22db0bb4f5b1cd9bdd46a4bb18711404e27de0840631cbf698f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 244674, + "line_end": 244674, + "char_start": 7354135, + "char_end": 7354278, + "column_start": 0, + "column_end": 143 + }, + "surface_location": { + "line_start": 244674, + "line_end": 244674, + "char_start": 7354137, + "char_end": 7354141, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-db0cd4b10943614faf13", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c474155b6b168c7b04e33a4f8a87c5f7dbe840bebfc6558b0dbeb67747b61c18", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 246594, + "line_end": 246594, + "char_start": 7425548, + "char_end": 7425703, + "column_start": 0, + "column_end": 155 + }, + "surface_location": { + "line_start": 246594, + "line_end": 246594, + "char_start": 7425608, + "char_end": 7425612, + "column_start": 60, + "column_end": 64 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-658ff7c81eb0bd510200", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "0f909548551920351b2ee095bb56669a1d2ef09be4d5b8c2a65618266d59013b", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 246621, + "line_end": 246621, + "char_start": 7426690, + "char_end": 7426851, + "column_start": 0, + "column_end": 161 + }, + "surface_location": { + "line_start": 246621, + "line_end": 246621, + "char_start": 7426738, + "char_end": 7426742, + "column_start": 48, + "column_end": 52 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-94156305cb6b0a2c452e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "e926b51e26e4f26f799783dc72fc25fcec0372a8b886057937517cfee983ffc9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 250092, + "line_end": 250092, + "char_start": 7552944, + "char_end": 7553073, + "column_start": 0, + "column_end": 129 + }, + "surface_location": { + "line_start": 250092, + "line_end": 250092, + "char_start": 7552974, + "char_end": 7552978, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-168f8289b72ec00554fa", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c880c0d806c2c7f569f830f41b83a51514a62c896b8988f4b7b8609bc49f091a", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 251637, + "line_end": 251637, + "char_start": 7607258, + "char_end": 7607434, + "column_start": 0, + "column_end": 176 + }, + "surface_location": { + "line_start": 251637, + "line_end": 251637, + "char_start": 7607301, + "char_end": 7607305, + "column_start": 43, + "column_end": 47 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ff292d411bbad1b3c8ad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "aa5cc7d430ee7565c402cf0f6f05584dcfe2011b0c626be1afa51bf3f3eadd0f", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 253155, + "line_end": 253155, + "char_start": 7661857, + "char_end": 7661964, + "column_start": 0, + "column_end": 107 + }, + "surface_location": { + "line_start": 253155, + "line_end": 253155, + "char_start": 7661857, + "char_end": 7661863, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9b7e8c219e71763d94d7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "92a219dbce3ceaa5f158af0f51fbbcd900d5c312d41c2590f8fc62b73c355b06", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 253737, + "line_end": 253737, + "char_start": 7682920, + "char_end": 7683068, + "column_start": 0, + "column_end": 148 + }, + "surface_location": { + "line_start": 253737, + "line_end": 253737, + "char_start": 7682921, + "char_end": 7682927, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-930e67c69107c7498a3e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "aaa2684ec874da5c6eacbdf076b5cfbc9ddd2eaa421b6f5e483eab6bf89d3a12", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 254223, + "line_end": 254223, + "char_start": 7701557, + "char_end": 7701720, + "column_start": 0, + "column_end": 163 + }, + "surface_location": { + "line_start": 254223, + "line_end": 254223, + "char_start": 7701627, + "char_end": 7701631, + "column_start": 70, + "column_end": 74 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d056c582ecb0c480dc24", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "40b408719f539dd3cb9d7f0d4469d57534d3dd7a8bc0c1b0f7f2b550e707ee17", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 255180, + "line_end": 255180, + "char_start": 7727716, + "char_end": 7727764, + "column_start": 0, + "column_end": 48 + }, + "surface_location": { + "line_start": 255180, + "line_end": 255180, + "char_start": 7727727, + "char_end": 7727731, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-af9f2b53d531d678a3a2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "838488ff9b971c82fdedba04022577669e802f9fc571bdf01c7f34b092035c76", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 255363, + "line_end": 255366, + "char_start": 7734765, + "char_end": 7734945, + "column_start": 95, + "column_end": 67 + }, + "surface_location": { + "line_start": 255363, + "line_end": 255363, + "char_start": 7734855, + "char_end": 7734861, + "column_start": 185, + "column_end": 191 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fdd9089d7669aaa55232", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "5c450b51c260fda53550af1abd28eee223e79d95b2c2846156ba6182e8c78738", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 258999, + "line_end": 258999, + "char_start": 7847145, + "char_end": 7847254, + "column_start": 0, + "column_end": 109 + }, + "surface_location": { + "line_start": 258999, + "line_end": 258999, + "char_start": 7847176, + "char_end": 7847182, + "column_start": 31, + "column_end": 37 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-417431dafd90495fc212", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "842f20763a504ffeb32ee3ec09e03345bb31c3c13a8a8ff98239a5562a8f6383", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 259521, + "line_end": 259521, + "char_start": 7865805, + "char_end": 7865900, + "column_start": 0, + "column_end": 95 + }, + "surface_location": { + "line_start": 259521, + "line_end": 259521, + "char_start": 7865874, + "char_end": 7865878, + "column_start": 69, + "column_end": 73 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c0140d78f7e55267502e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "93936a0f245871fb2d4b4ef77798465b9c023db3028a5bbea926e277841eced9", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 261399, + "line_end": 261399, + "char_start": 7930023, + "char_end": 7930202, + "column_start": 0, + "column_end": 179 + }, + "surface_location": { + "line_start": 261399, + "line_end": 261399, + "char_start": 7930023, + "char_end": 7930029, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-57faebdf7158b2f88520", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "d0db28614cad03a146c5f6f50277ac5f123ced45a355aa4996b03f8025340ef3", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 264336, + "line_end": 264337, + "char_start": 8014752, + "char_end": 8014913, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 264336, + "line_end": 264336, + "char_start": 8014799, + "char_end": 8014803, + "column_start": 47, + "column_end": 51 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9fa28ea5189389dcf715", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "9742829906368cb6b13a1d830f6f0181d36e7b7991f4f03d402bd1e8c3c508dd", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 265485, + "line_end": 265485, + "char_start": 8049533, + "char_end": 8049666, + "column_start": 0, + "column_end": 133 + }, + "surface_location": { + "line_start": 265485, + "line_end": 265485, + "char_start": 8049619, + "char_end": 8049625, + "column_start": 86, + "column_end": 92 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-12a736aaecf8b2777aef", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "c867386e441995e3551d9b3bd3f8d20cbae05335ce53714aae6ebcfeaac076d7", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 265941, + "line_end": 265941, + "char_start": 8061521, + "char_end": 8061598, + "column_start": 0, + "column_end": 77 + }, + "surface_location": { + "line_start": 265941, + "line_end": 265941, + "char_start": 8061564, + "char_end": 8061570, + "column_start": 43, + "column_end": 49 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3dcb160c4b05cb507700", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "fac4009eb4f1425e41c08b579c0c2a5c8d613757025f451530d5a36221602e21", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 280215, + "line_end": 280215, + "char_start": 8451936, + "char_end": 8452025, + "column_start": 0, + "column_end": 89 + }, + "surface_location": { + "line_start": 280215, + "line_end": 280215, + "char_start": 8451937, + "char_end": 8451941, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3182cf13cdc0a2de7b78", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "888567224ff24a3a6e79ba8a4b92540ceec6a95a32e457cd476cbcb466579f2e", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 285678, + "line_end": 285678, + "char_start": 8577678, + "char_end": 8577779, + "column_start": 0, + "column_end": 101 + }, + "surface_location": { + "line_start": 285678, + "line_end": 285678, + "char_start": 8577678, + "char_end": 8577684, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9929c0300fd35d03d2ed", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "39faf42f2f702b230e6579c9a10096c4642cf2987e098401e4e4cb475370aee3", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 288054, + "line_end": 288054, + "char_start": 8636067, + "char_end": 8636201, + "column_start": 0, + "column_end": 134 + }, + "surface_location": { + "line_start": 288054, + "line_end": 288054, + "char_start": 8636107, + "char_end": 8636111, + "column_start": 40, + "column_end": 44 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0b7cbf965394e28c75ae", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "excerpt_sha256": "f0588203f8ceabf68ec486a79e316fa9b323c350f16d949427bb8f511fc436f5", + "source_ref": "小说清单/机战无限_亦醉.txt", + "location": { + "line_start": 288873, + "line_end": 288873, + "char_start": 8656435, + "char_end": 8656518, + "column_start": 0, + "column_end": 83 + }, + "surface_location": { + "line_start": 288873, + "line_end": 288873, + "char_start": 8656457, + "char_end": 8656461, + "column_start": 22, + "column_end": 26 + }, + "work_ref": "ref:机战无限_亦醉" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b30a87cf71392d137539", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "991230245ad383e65beef813b12dc7b934d2264ac6649a1a40a8000d73749f7f", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 7795, + "line_end": 7796, + "char_start": 123722, + "char_end": 123845, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 7795, + "line_end": 7795, + "char_start": 123820, + "char_end": 123824, + "column_start": 98, + "column_end": 102 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fbc6d3303a7abc9cb790", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "5a8235d3c611f3b45adfd5e70f443e1f24fb85b54589f3a0f03d60060ccf21e2", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 16495, + "line_end": 16495, + "char_start": 261717, + "char_end": 261755, + "column_start": 46, + "column_end": 84 + }, + "surface_location": { + "line_start": 16495, + "line_end": 16495, + "char_start": 261739, + "char_end": 261743, + "column_start": 68, + "column_end": 72 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-acad535daec02fcb7ccb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "c2c69f7d0a8694cc9c1b8472f3ec2daeff8f750e79c2d688833271c9e6cda283", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 35788, + "line_end": 35788, + "char_start": 564256, + "char_end": 564429, + "column_start": 0, + "column_end": 173 + }, + "surface_location": { + "line_start": 35788, + "line_end": 35788, + "char_start": 564324, + "char_end": 564330, + "column_start": 68, + "column_end": 74 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d2999fbb230cb1691651", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "dcd1bffe07c55cfb61260de7c3d57878135a0a526e2924f95ab91ea2865ccba9", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 41862, + "line_end": 41862, + "char_start": 660322, + "char_end": 660376, + "column_start": 0, + "column_end": 54 + }, + "surface_location": { + "line_start": 41862, + "line_end": 41862, + "char_start": 660333, + "char_end": 660337, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-443ad1d9b8a13bf47b3e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "b7b3f37d3140dd91ad54bd603ad520d6981adf6544d739c90c458238fba311f0", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 43989, + "line_end": 43990, + "char_start": 696316, + "char_end": 696337, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 43989, + "line_end": 43989, + "char_start": 696324, + "char_end": 696328, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0ba76378687e76b60368", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "9154e7c46ca2c30b3b24ce583f198a0cc0ee0bb40d5e215a28e6cf84b2eeb6a4", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 112008, + "line_end": 112008, + "char_start": 1916577, + "char_end": 1916616, + "column_start": 0, + "column_end": 39 + }, + "surface_location": { + "line_start": 112008, + "line_end": 112008, + "char_start": 1916580, + "char_end": 1916584, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ce13d719212d99f7a133", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "c3605b1f25633043bb2676e9f3bf5e4f8bf6aab3c1d49ea4b8ad551ec786ed70", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 135636, + "line_end": 135636, + "char_start": 2441527, + "char_end": 2441628, + "column_start": 0, + "column_end": 101 + }, + "surface_location": { + "line_start": 135636, + "line_end": 135636, + "char_start": 2441612, + "char_end": 2441616, + "column_start": 85, + "column_end": 89 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-33db2cad6dc59a9b7bac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "excerpt_sha256": "941b9eedbaace9d8cc5a5fcd71ddcd17fe1f27bfb0918fb10cc990b9fcdebfb4", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "location": { + "line_start": 136659, + "line_end": 136659, + "char_start": 2464342, + "char_end": 2464379, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 136659, + "line_end": 136659, + "char_start": 2464374, + "char_end": 2464378, + "column_start": 32, + "column_end": 36 + }, + "work_ref": "ref:机武风暴_骷髅精灵" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-16eb0c79e95a18f455de", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "69e6e8c6f5901bdc23fd5b2c3a9c4f753e50de43777ef4401fb68a6fd9af9fa2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 877, + "line_end": 877, + "char_start": 18066, + "char_end": 18142, + "column_start": 0, + "column_end": 76 + }, + "surface_location": { + "line_start": 877, + "line_end": 877, + "char_start": 18125, + "char_end": 18129, + "column_start": 59, + "column_end": 63 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0284f3a370df4f9a5fb2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "109835f69abc25b5c26ee1ed02a65957bd26407232125bad59dc4f0502a8eec1", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 5955, + "line_end": 5955, + "char_start": 95420, + "char_end": 95436, + "column_start": 4, + "column_end": 20 + }, + "surface_location": { + "line_start": 5955, + "line_end": 5955, + "char_start": 95423, + "char_end": 95427, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5d11b6e4b0e1b5820b6f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "af8fbfc8f5e784bbbe2f2013e72a56d762cb8aa3484ff77ed6d7e66956c82889", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 40549, + "line_end": 40549, + "char_start": 517351, + "char_end": 517384, + "column_start": 20, + "column_end": 53 + }, + "surface_location": { + "line_start": 40549, + "line_end": 40549, + "char_start": 517354, + "char_end": 517358, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-519d673a970585f2fe05", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 46431, + "line_end": 46431, + "char_start": 574508, + "char_end": 574547, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 46431, + "line_end": 46431, + "char_start": 574529, + "char_end": 574535, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d23c2b6df88f3f9f8318", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "2514f971be9ce5bccd63d5e408e8f4e68aced9f0f58b2984acc298544b7ae0f2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 47423, + "line_end": 47423, + "char_start": 588463, + "char_end": 588489, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 47423, + "line_end": 47423, + "char_start": 588468, + "char_end": 588472, + "column_start": 5, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-56af02c2b13c88e8e40a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1f7802bc3d238ccb369b13e26cf93b7036750dcc35d1b6d12378a5dd17d23bc6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 47738, + "line_end": 47738, + "char_start": 594773, + "char_end": 594819, + "column_start": 70, + "column_end": 116 + }, + "surface_location": { + "line_start": 47738, + "line_end": 47738, + "char_start": 594814, + "char_end": 594818, + "column_start": 111, + "column_end": 115 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-15e687ee531b92522e6f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1f7802bc3d238ccb369b13e26cf93b7036750dcc35d1b6d12378a5dd17d23bc6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 47864, + "line_end": 47864, + "char_start": 597433, + "char_end": 597479, + "column_start": 70, + "column_end": 116 + }, + "surface_location": { + "line_start": 47864, + "line_end": 47864, + "char_start": 597474, + "char_end": 597478, + "column_start": 111, + "column_end": 115 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c57f42bdeed02eabf866", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02143179a372ffe5989d3cee9b042b3349edb41f3f63f915a5bec3576ceca09", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 48026, + "line_end": 48026, + "char_start": 600659, + "char_end": 600708, + "column_start": 95, + "column_end": 144 + }, + "surface_location": { + "line_start": 48026, + "line_end": 48026, + "char_start": 600683, + "char_end": 600687, + "column_start": 119, + "column_end": 123 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-75684db95a7870b3e0a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "96ae9b92afe03bb41589f1a7c5889e8974e62c18a6dc40bd0bd3ecdbdc87fe3e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 48608, + "line_end": 48608, + "char_start": 606775, + "char_end": 606784, + "column_start": 0, + "column_end": 9 + }, + "surface_location": { + "line_start": 48608, + "line_end": 48608, + "char_start": 606777, + "char_end": 606781, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b1fb23d2c39b2237d6ff", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02143179a372ffe5989d3cee9b042b3349edb41f3f63f915a5bec3576ceca09", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 48770, + "line_end": 48770, + "char_start": 609240, + "char_end": 609289, + "column_start": 100, + "column_end": 149 + }, + "surface_location": { + "line_start": 48770, + "line_end": 48770, + "char_start": 609264, + "char_end": 609268, + "column_start": 124, + "column_end": 128 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-65c2be95115c3f209b65", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 48926, + "line_end": 48926, + "char_start": 613094, + "char_end": 613133, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 48926, + "line_end": 48926, + "char_start": 613115, + "char_end": 613121, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6aeee3b426cb46cbf62b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 49231, + "line_end": 49231, + "char_start": 620428, + "char_end": 620467, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 49231, + "line_end": 49231, + "char_start": 620449, + "char_end": 620455, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5fc5722ae0edcb46f6f7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3ff137f4f70c7c113ce84bf0df269bc8be49504fea958117b44548cf1c187838", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 50820, + "line_end": 50823, + "char_start": 642514, + "char_end": 642694, + "column_start": 235, + "column_end": 21 + }, + "surface_location": { + "line_start": 50820, + "line_end": 50820, + "char_start": 642604, + "char_end": 642610, + "column_start": 325, + "column_end": 331 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3301a29974ccbf21a3f8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "78ace5ec6d74fb28e25a23fe39f4795eb3f57495b81698b69d69eef1ce79a0ee", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 51915, + "line_end": 51915, + "char_start": 654792, + "char_end": 654805, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 51915, + "line_end": 51915, + "char_start": 654796, + "char_end": 654800, + "column_start": 4, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c81c874fd0f45dad2a00", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "5386cd8da8ea8e333043ec2e5ca4f8ce1ba1a3032373b0ecf56566f22c66285b", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 51945, + "line_end": 51945, + "char_start": 654998, + "char_end": 655008, + "column_start": 0, + "column_end": 10 + }, + "surface_location": { + "line_start": 51945, + "line_end": 51945, + "char_start": 655001, + "char_end": 655007, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角悄然勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f92879fef1e25c01ea3c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 52494, + "line_end": 52494, + "char_start": 663179, + "char_end": 663218, + "column_start": 72, + "column_end": 111 + }, + "surface_location": { + "line_start": 52494, + "line_end": 52494, + "char_start": 663200, + "char_end": 663206, + "column_start": 93, + "column_end": 99 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-adbde45d31e78122f69b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ae2483be0ec95a8bdf8a7cabe808db4fdaf2de53d8bac1eaa88c0812a381d0ef", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 55239, + "line_end": 55239, + "char_start": 700335, + "char_end": 700384, + "column_start": 68, + "column_end": 117 + }, + "surface_location": { + "line_start": 55239, + "line_end": 55239, + "char_start": 700366, + "char_end": 700372, + "column_start": 99, + "column_end": 105 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6ddedb6d60a9d5668a56", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 57173, + "line_end": 57173, + "char_start": 724877, + "char_end": 724916, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 57173, + "line_end": 57173, + "char_start": 724898, + "char_end": 724904, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8025007419657d16638f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "40335a38c99d93bebdbbf290e4d5b805737fa64207177944a9abc9f9ad136913", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 58433, + "line_end": 58433, + "char_start": 740031, + "char_end": 740072, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 58433, + "line_end": 58433, + "char_start": 740033, + "char_end": 740037, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ed075592f8ec0107cf1f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 60350, + "line_end": 60350, + "char_start": 761280, + "char_end": 761319, + "column_start": 73, + "column_end": 112 + }, + "surface_location": { + "line_start": 60350, + "line_end": 60350, + "char_start": 761301, + "char_end": 761307, + "column_start": 94, + "column_end": 100 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ee41b43c932bbd2e27c9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 60542, + "line_end": 60542, + "char_start": 765454, + "char_end": 765493, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 60542, + "line_end": 60542, + "char_start": 765475, + "char_end": 765481, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-410095fad2eef1d0ca5f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 61583, + "line_end": 61583, + "char_start": 780814, + "char_end": 780853, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 61583, + "line_end": 61583, + "char_start": 780835, + "char_end": 780841, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ad42535298a0605dbf1d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "5e986ac9efe00a2ee82087c5d842d2f9d6260dd0f5f71afa3a1aa646d1a46d34", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 62399, + "line_end": 62399, + "char_start": 793835, + "char_end": 793852, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 62399, + "line_end": 62399, + "char_start": 793843, + "char_end": 793851, + "column_start": 8, + "column_end": 16 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c9f63777e44edcd9bec2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 62495, + "line_end": 62495, + "char_start": 795593, + "char_end": 795632, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 62495, + "line_end": 62495, + "char_start": 795614, + "char_end": 795620, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dd6bf4b0e11f56b63cfe", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 64297, + "line_end": 64297, + "char_start": 819688, + "char_end": 819727, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 64297, + "line_end": 64297, + "char_start": 819709, + "char_end": 819715, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a756828cb6edfba80905", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 64626, + "line_end": 64626, + "char_start": 827345, + "char_end": 827384, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 64626, + "line_end": 64626, + "char_start": 827366, + "char_end": 827372, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-545719e3a88b77406b59", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1f7802bc3d238ccb369b13e26cf93b7036750dcc35d1b6d12378a5dd17d23bc6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 64932, + "line_end": 64932, + "char_start": 834361, + "char_end": 834407, + "column_start": 70, + "column_end": 116 + }, + "surface_location": { + "line_start": 64932, + "line_end": 64932, + "char_start": 834402, + "char_end": 834406, + "column_start": 111, + "column_end": 115 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cebb161d6a05196cd528", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 66570, + "line_end": 66570, + "char_start": 857374, + "char_end": 857413, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 66570, + "line_end": 66570, + "char_start": 857395, + "char_end": 857401, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e4ef5b04b18e3b6ca89d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "056842a4cd6b274dc4a20cdcefb9521fbc627e785af56217399e56efe872b330", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 70149, + "line_end": 70149, + "char_start": 902253, + "char_end": 902318, + "column_start": 0, + "column_end": 65 + }, + "surface_location": { + "line_start": 70149, + "line_end": 70149, + "char_start": 902253, + "char_end": 902257, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bad515569a15296c94d5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ec37ba2128d306de3a41ac631e43a043dd632c255f4976a07dfb28efa44fb19a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 78530, + "line_end": 78530, + "char_start": 998790, + "char_end": 998833, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 78530, + "line_end": 78530, + "char_start": 998797, + "char_end": 998801, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bf55cbe9b9b6793b96e4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d8992b8f501a78922e8cbe4f437fc2a2d9f2d0c1c8c204c3441996bbc5d09f18", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 78560, + "line_end": 78560, + "char_start": 999461, + "char_end": 999509, + "column_start": 68, + "column_end": 116 + }, + "surface_location": { + "line_start": 78560, + "line_end": 78560, + "char_start": 999491, + "char_end": 999497, + "column_start": 98, + "column_end": 104 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f78c377cba46897b3b7a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "809468c7790534087db6914f8202e844a55c67c9ab88cf894464d136bff8cdfb", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 79034, + "line_end": 79034, + "char_start": 1005570, + "char_end": 1005581, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 79034, + "line_end": 79034, + "char_start": 1005572, + "char_end": 1005576, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c481a497d91fb7649b6c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9064b9692c7035b17799b01c29ba0a33127af810c2da263af285a18479145c8d", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 79622, + "line_end": 79622, + "char_start": 1011467, + "char_end": 1011484, + "column_start": 11, + "column_end": 28 + }, + "surface_location": { + "line_start": 79622, + "line_end": 79622, + "char_start": 1011477, + "char_end": 1011481, + "column_start": 21, + "column_end": 25 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-696a35ed0c0256b720a4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "6abdcd2eddb2972e7f1ee01dfc863b5e9ef3a8127dacef51a0f3037a3164e8ab", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 84218, + "line_end": 84218, + "char_start": 1069564, + "char_end": 1069620, + "column_start": 0, + "column_end": 56 + }, + "surface_location": { + "line_start": 84218, + "line_end": 84218, + "char_start": 1069567, + "char_end": 1069571, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ccb13147faf73c183847", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9a9a95fc061cee053d028c88fc76676fcfd9707aab76af7ae6ea4ed58497b9ff", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 90805, + "line_end": 90805, + "char_start": 1134702, + "char_end": 1134726, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 90805, + "line_end": 90805, + "char_start": 1134702, + "char_end": 1134706, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4adce2fe63bf0623d4f7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e71e1ee876d17c2bca2ee672d54d521a2a0721178ba56bb0ea5c11ce67c9c416", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 93720, + "line_end": 93720, + "char_start": 1170148, + "char_end": 1170164, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 93720, + "line_end": 93720, + "char_start": 1170155, + "char_end": 1170159, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0c332509189fc937cfb8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 98477, + "line_end": 98477, + "char_start": 1224654, + "char_end": 1224693, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 98477, + "line_end": 98477, + "char_start": 1224675, + "char_end": 1224681, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f020817237399910725b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ae2483be0ec95a8bdf8a7cabe808db4fdaf2de53d8bac1eaa88c0812a381d0ef", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 99503, + "line_end": 99503, + "char_start": 1239524, + "char_end": 1239573, + "column_start": 68, + "column_end": 117 + }, + "surface_location": { + "line_start": 99503, + "line_end": 99503, + "char_start": 1239555, + "char_end": 1239561, + "column_start": 99, + "column_end": 105 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-482d15d28686782d1239", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3365b4c56dfd985caece60e414b2195ca762c62bee580bb9eb4913a2bd2b6b07", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 99913, + "line_end": 99913, + "char_start": 1248420, + "char_end": 1248466, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 99913, + "line_end": 99913, + "char_start": 1248440, + "char_end": 1248444, + "column_start": 20, + "column_end": 24 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-30621b6a23a4fe26677f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "10f63dd7374af7f504b6501c74dc42622257ec794d9b5a6da37eb4940aff1bea", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 102433, + "line_end": 102433, + "char_start": 1274719, + "char_end": 1274759, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 102433, + "line_end": 102433, + "char_start": 1274727, + "char_end": 1274733, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cb3cf8e1362e08c9c057", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b64091996266b42add9b48b961f3551e53e2d888a0dc48667e4946799940d7df", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 112441, + "line_end": 112441, + "char_start": 1377957, + "char_end": 1377965, + "column_start": 0, + "column_end": 8 + }, + "surface_location": { + "line_start": 112441, + "line_end": 112441, + "char_start": 1377960, + "char_end": 1377964, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-72d3f0aa8cfc33f34e57", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 113794, + "line_end": 113794, + "char_start": 1392350, + "char_end": 1392389, + "column_start": 71, + "column_end": 110 + }, + "surface_location": { + "line_start": 113794, + "line_end": 113794, + "char_start": 1392371, + "char_end": 1392377, + "column_start": 92, + "column_end": 98 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-609110783a02c03a5fe6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0d6d57ea11465d327c5b51d5038a4f017d9541fcd9fe2f5c35d5a8ec402dfad9", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 114739, + "line_end": 114739, + "char_start": 1406586, + "char_end": 1406626, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 114739, + "line_end": 114739, + "char_start": 1406603, + "char_end": 1406607, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7eada7f28eff3c01e24f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d681efd40b52d3eb50e6e1c42ea14852e738d1a90e84c56e3d148269696b031a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 116529, + "line_end": 116529, + "char_start": 1428090, + "char_end": 1428202, + "column_start": 0, + "column_end": 112 + }, + "surface_location": { + "line_start": 116529, + "line_end": 116529, + "char_start": 1428134, + "char_end": 1428138, + "column_start": 44, + "column_end": 48 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-597afe53ffd7b8712a7d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a2de184d36ef2b09293cc4aadededf8a1fed6312fa90f3e0d1694beb838ecc7a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 116913, + "line_end": 116913, + "char_start": 1432988, + "char_end": 1433004, + "column_start": 39, + "column_end": 55 + }, + "surface_location": { + "line_start": 116913, + "line_end": 116913, + "char_start": 1432992, + "char_end": 1432996, + "column_start": 43, + "column_end": 47 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-29d2daec5b13bc3d7cd7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 118140, + "line_end": 118140, + "char_start": 1446115, + "char_end": 1446154, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 118140, + "line_end": 118140, + "char_start": 1446136, + "char_end": 1446142, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a5d7a8abaca0779a6afc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ea976c639131b37a7100a6e04cce46681c8d6e0caafd50dd6159cb3fa3d7b735", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 120854, + "line_end": 120854, + "char_start": 1475573, + "char_end": 1475590, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 120854, + "line_end": 120854, + "char_start": 1475581, + "char_end": 1475585, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5c1a11a80c7d1ec5a5ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1b074b9597e732fb9c365cca2ae5da48ffc5fa7e4d2ab2003c6720af677b274f", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 123472, + "line_end": 123472, + "char_start": 1506131, + "char_end": 1506175, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 123472, + "line_end": 123472, + "char_start": 1506147, + "char_end": 1506151, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9742df0f67300569c03c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "5e0c92f1db954e7b65f1f7de232956635985a3562f065787d1536b448491f403", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 124291, + "line_end": 124291, + "char_start": 1515396, + "char_end": 1515415, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 124291, + "line_end": 124291, + "char_start": 1515396, + "char_end": 1515400, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-45dcd73146c2eabeb78b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 124705, + "line_end": 124705, + "char_start": 1520586, + "char_end": 1520625, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 124705, + "line_end": 124705, + "char_start": 1520607, + "char_end": 1520613, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-39a815362f87cee51141", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 125098, + "line_end": 125098, + "char_start": 1530742, + "char_end": 1530781, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 125098, + "line_end": 125098, + "char_start": 1530763, + "char_end": 1530769, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b96150c28fd364de1e5c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 125296, + "line_end": 125296, + "char_start": 1535474, + "char_end": 1535513, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 125296, + "line_end": 125296, + "char_start": 1535495, + "char_end": 1535501, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1e0604177991726d48be", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "afbd9c2be869e05ad7dbf2cc3a1b1c97533e7bcac5c3d9110113c67be23f7fdc", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 126984, + "line_end": 126984, + "char_start": 1555480, + "char_end": 1555515, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 126984, + "line_end": 126984, + "char_start": 1555506, + "char_end": 1555510, + "column_start": 26, + "column_end": 30 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4d60ee1759a49e70fcce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e6b0d65240ead29a39f504a74758d16ee7c96340cac7dff7457a8f8d75b6920a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 127215, + "line_end": 127215, + "char_start": 1558445, + "char_end": 1558455, + "column_start": 0, + "column_end": 10 + }, + "surface_location": { + "line_start": 127215, + "line_end": 127215, + "char_start": 1558450, + "char_end": 1558454, + "column_start": 5, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5b36ce815235b91b33f0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "8ad52ac4eb39bd671d6f3e4669fd5ad3dfd00b2ccceda1802812af6fc4d16355", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 127842, + "line_end": 127842, + "char_start": 1566645, + "char_end": 1566705, + "column_start": 0, + "column_end": 60 + }, + "surface_location": { + "line_start": 127842, + "line_end": 127842, + "char_start": 1566685, + "char_end": 1566689, + "column_start": 40, + "column_end": 44 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7b9ebc09d324210f4673", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "99b800ef59518e904940515587e881debc9933be113f13fd14f344e9ae237f1a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 130137, + "line_end": 130138, + "char_start": 1591361, + "char_end": 1591377, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 130137, + "line_end": 130137, + "char_start": 1591371, + "char_end": 1591375, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fabd0d7c3b0673578790", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "86c2b38c4bb51b54054000b3e6a757d96d27e02a6257f17a25480ea70f320cd1", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 131784, + "line_end": 131784, + "char_start": 1606715, + "char_end": 1606726, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 131784, + "line_end": 131784, + "char_start": 1606721, + "char_end": 1606725, + "column_start": 6, + "column_end": 10 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4a15da0fcaa6abb41c89", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "bcb0d03231a4b1172db9079f9023e450e753928c2cbb465dc8d42445417c3728", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 136515, + "line_end": 136515, + "char_start": 1657095, + "char_end": 1657135, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 136515, + "line_end": 136515, + "char_start": 1657126, + "char_end": 1657130, + "column_start": 31, + "column_end": 35 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7be3d05b64efa2ff73fc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4dfa634d6fe48f7107365fef92e141452037a0aa3a77e37175f638f43b7ea9e3", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 143864, + "line_end": 143864, + "char_start": 1731514, + "char_end": 1731552, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 143864, + "line_end": 143864, + "char_start": 1731547, + "char_end": 1731551, + "column_start": 33, + "column_end": 37 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d43bbc96a9c53feda783", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "68a6ab499e1f163bcffffee87c350583dae3aa2a04728e15f827275ced49ed62", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 144110, + "line_end": 144110, + "char_start": 1734043, + "char_end": 1734080, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 144110, + "line_end": 144110, + "char_start": 1734067, + "char_end": 1734079, + "column_start": 24, + "column_end": 36 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "一股恐怖的气息从身上弥漫", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7c4309055bdac62cee70", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d1a5b087b1161c4edc4c0b98f6ae54a81f4e8ff7837ce8a68774c506a0e63b04", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 146087, + "line_end": 146087, + "char_start": 1754520, + "char_end": 1754552, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 146087, + "line_end": 146087, + "char_start": 1754522, + "char_end": 1754526, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-872936b69b51a93615bf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9d07c7cdab5460dfc6a4d396ceb8dde43c6a069bbd64f27cfff7b2ce7de92378", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 150650, + "line_end": 150650, + "char_start": 1801884, + "char_end": 1801897, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 150650, + "line_end": 150650, + "char_start": 1801892, + "char_end": 1801896, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bf731fc816d910c80c8a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 156455, + "line_end": 156455, + "char_start": 1865054, + "char_end": 1865093, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 156455, + "line_end": 156455, + "char_start": 1865075, + "char_end": 1865081, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-31497a7132139d0086b7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02143179a372ffe5989d3cee9b042b3349edb41f3f63f915a5bec3576ceca09", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 157529, + "line_end": 157529, + "char_start": 1878895, + "char_end": 1878944, + "column_start": 98, + "column_end": 147 + }, + "surface_location": { + "line_start": 157529, + "line_end": 157529, + "char_start": 1878919, + "char_end": 1878923, + "column_start": 122, + "column_end": 126 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-73f09659b8c7a53196f9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7a415db6483f418378f69cbc20b2fdb66babe25f6e50ea7a03068e11aab71ebe", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 157925, + "line_end": 157925, + "char_start": 1883446, + "char_end": 1883475, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 157925, + "line_end": 157925, + "char_start": 1883468, + "char_end": 1883474, + "column_start": 22, + "column_end": 28 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角悄然勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-491199e73afe571d5420", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "64ddf5067501566e7a93bb8bbcaaa0c442617849deaa441c6345419c25ce65d2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 160973, + "line_end": 160974, + "char_start": 1914859, + "char_end": 1914896, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 160973, + "line_end": 160973, + "char_start": 1914882, + "char_end": 1914886, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-abfeb826b1d4ebd295e9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "744e2e87d81d5fbd3046af23d93c7245e10ced614e46e179c30d101d6b081b6b", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 161138, + "line_end": 161138, + "char_start": 1916667, + "char_end": 1916710, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 161138, + "line_end": 161138, + "char_start": 1916677, + "char_end": 1916681, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e5d37049d3027af37e24", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02143179a372ffe5989d3cee9b042b3349edb41f3f63f915a5bec3576ceca09", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 162674, + "line_end": 162674, + "char_start": 1933824, + "char_end": 1933873, + "column_start": 98, + "column_end": 147 + }, + "surface_location": { + "line_start": 162674, + "line_end": 162674, + "char_start": 1933848, + "char_end": 1933852, + "column_start": 122, + "column_end": 126 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-76981a52f242f325e6dc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e6eea9e7ff6147645fdfbc67d7deedb790aa372aee583bd120405dd2660a431e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 164498, + "line_end": 164498, + "char_start": 1953889, + "char_end": 1953915, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 164498, + "line_end": 164498, + "char_start": 1953893, + "char_end": 1953899, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ac86fd04a10256da32f0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "473b48e0692fbba37672043129f099ae239d1064ba86e2f70a16a33c48f7cf89", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 166262, + "line_end": 166262, + "char_start": 1972898, + "char_end": 1972912, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 166262, + "line_end": 166262, + "char_start": 1972900, + "char_end": 1972904, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b019d6dc364d519886ea", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 166415, + "line_end": 166415, + "char_start": 1974580, + "char_end": 1974619, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 166415, + "line_end": 166415, + "char_start": 1974601, + "char_end": 1974607, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8e5522b5555575204015", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "77e6ccf9420318baea8f12059bd8b4d87f43f76378c8e910161f1919611ca2e0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 167159, + "line_end": 167159, + "char_start": 1982600, + "char_end": 1982628, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 167159, + "line_end": 167159, + "char_start": 1982600, + "char_end": 1982604, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9543039879daecd85a09", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d337db6492b52d91164e1070a7ebb7bf61646129c984da723e60e85e0403cc81", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 176039, + "line_end": 176039, + "char_start": 2078028, + "char_end": 2078054, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 176039, + "line_end": 176039, + "char_start": 2078030, + "char_end": 2078034, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-966217a07ad667722aa3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "8c7d7c04ef071362fca9725daac8bb0bc87d7074109a5a75b97edaa75f1528d8", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 176723, + "line_end": 176723, + "char_start": 2087209, + "char_end": 2087249, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 176723, + "line_end": 176723, + "char_start": 2087237, + "char_end": 2087241, + "column_start": 28, + "column_end": 32 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4407ed5c01ff8cde21af", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 182384, + "line_end": 182384, + "char_start": 2147211, + "char_end": 2147250, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 182384, + "line_end": 182384, + "char_start": 2147232, + "char_end": 2147238, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6ded300cc937aad47b54", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0f6ff060acf66b26878f0ed6efc732f3bde66954baeb982764a1ed351ed0846e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 186041, + "line_end": 186041, + "char_start": 2188762, + "char_end": 2188805, + "column_start": 10, + "column_end": 53 + }, + "surface_location": { + "line_start": 186041, + "line_end": 186041, + "char_start": 2188766, + "char_end": 2188770, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dbd6935acd76e8a8d68f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "5156fb234d7cbd6ec45b1be6a17c6b15c22742be7c63623763d77c39eb9da75e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 186587, + "line_end": 186587, + "char_start": 2194449, + "char_end": 2194474, + "column_start": 61, + "column_end": 86 + }, + "surface_location": { + "line_start": 186587, + "line_end": 186587, + "char_start": 2194452, + "char_end": 2194456, + "column_start": 64, + "column_end": 68 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-652d1eff81e43dca2bfb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3f5365d372ba3f63e8b5b8341021bccb0baa57588a73bd0977892abd6f6ed773", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 191090, + "line_end": 191090, + "char_start": 2240871, + "char_end": 2240893, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 191090, + "line_end": 191090, + "char_start": 2240886, + "char_end": 2240890, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1617e188c4874640b1c9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b281996834ee388ddbc5fe23cc9109800c5ce1e030970f460ce79ea12b884534", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 192332, + "line_end": 192332, + "char_start": 2252203, + "char_end": 2252230, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 192332, + "line_end": 192332, + "char_start": 2252218, + "char_end": 2252222, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8242186704f8d9d2b4be", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e5a3a62498ffe16805318168e0e6300585f6ca6b3b3fa48f04b1624b91ba67e0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 193613, + "line_end": 193613, + "char_start": 2264978, + "char_end": 2264997, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 193613, + "line_end": 193613, + "char_start": 2264992, + "char_end": 2264996, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-eb8cac6799d38d9ad057", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e3059f17c3009e905438ec9ecbe1a08db576e2847dca752d2dd0d70e0f8d719c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 198704, + "line_end": 198704, + "char_start": 2312254, + "char_end": 2312317, + "column_start": 51, + "column_end": 114 + }, + "surface_location": { + "line_start": 198704, + "line_end": 198704, + "char_start": 2312290, + "char_end": 2312294, + "column_start": 87, + "column_end": 91 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4671eae221355e2980c6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 207941, + "line_end": 207941, + "char_start": 2404989, + "char_end": 2405028, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 207941, + "line_end": 207941, + "char_start": 2405010, + "char_end": 2405016, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-91d287ece4396ece8c8c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 210596, + "line_end": 210596, + "char_start": 2436132, + "char_end": 2436171, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 210596, + "line_end": 210596, + "char_start": 2436153, + "char_end": 2436159, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8959f5deb104e31b3b8a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7a3aed4a0f8e8afff525fff4efd525480203e37803c76f9d7aafd2b1e4d50aeb", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 211373, + "line_end": 211373, + "char_start": 2445992, + "char_end": 2446024, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 211373, + "line_end": 211373, + "char_start": 2446017, + "char_end": 2446023, + "column_start": 25, + "column_end": 31 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过异彩", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0cf8dfc92025218f10ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7c86398c69eb0127cbf3d1752928c5927111fbd7534597a41464be4514c5f027", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 213947, + "line_end": 213947, + "char_start": 2468019, + "char_end": 2468063, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 213947, + "line_end": 213947, + "char_start": 2468049, + "char_end": 2468053, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4499cfa56fd7cd4c1e14", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 215144, + "line_end": 215144, + "char_start": 2478376, + "char_end": 2478415, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 215144, + "line_end": 215144, + "char_start": 2478397, + "char_end": 2478403, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0cb89e73e7354318d083", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4d7e184f1050576688a398a545a5171616553e3bff175f399adbc7a8883f1cbc", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 215894, + "line_end": 215894, + "char_start": 2488061, + "char_end": 2488079, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 215894, + "line_end": 215894, + "char_start": 2488061, + "char_end": 2488065, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-654640f6fb3b86f352dd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "2ae1922519d47a91884247b101645e7e4ca7a20afe88de35a4d2542d5cc2988a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 215957, + "line_end": 215957, + "char_start": 2488869, + "char_end": 2488906, + "column_start": 27, + "column_end": 64 + }, + "surface_location": { + "line_start": 215957, + "line_end": 215957, + "char_start": 2488901, + "char_end": 2488905, + "column_start": 59, + "column_end": 63 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cad66651985747ae77c3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d1c9ff42d491e44c939cb34ba8e96443692c4824f48855db40ee4c89a1302bf2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 216680, + "line_end": 216680, + "char_start": 2496383, + "char_end": 2496430, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 216680, + "line_end": 216680, + "char_start": 2496401, + "char_end": 2496405, + "column_start": 18, + "column_end": 22 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-45a91df162800231e426", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "07f3b6394bc8bba65b3d5e0dc4dc403248e2bec1e473df4c6eee8d8c76455aa5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 219716, + "line_end": 219716, + "char_start": 2525977, + "char_end": 2526022, + "column_start": 0, + "column_end": 45 + }, + "surface_location": { + "line_start": 219716, + "line_end": 219716, + "char_start": 2526009, + "char_end": 2526013, + "column_start": 32, + "column_end": 36 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6b53c61e74eabb1d270a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1912c25e529b4d3318edb7fe4556019150caeae1328504d2d1c060b50d009121", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 219842, + "line_end": 219842, + "char_start": 2527323, + "char_end": 2527383, + "column_start": 0, + "column_end": 60 + }, + "surface_location": { + "line_start": 219842, + "line_end": 219842, + "char_start": 2527340, + "char_end": 2527344, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-58675b0a577a69953e57", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "589afefcc3021eed7527558c1668560d1aac276d31b9aad8391d62897faa2e28", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 220067, + "line_end": 220067, + "char_start": 2529626, + "char_end": 2529674, + "column_start": 0, + "column_end": 48 + }, + "surface_location": { + "line_start": 220067, + "line_end": 220067, + "char_start": 2529628, + "char_end": 2529632, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c6bee125702be23e433f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4071db849def3d795b0778385df7219627540626af3190bc039727a892d83199", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 220931, + "line_end": 220931, + "char_start": 2539215, + "char_end": 2539243, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 220931, + "line_end": 220931, + "char_start": 2539217, + "char_end": 2539221, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5dc08ee5ebea70c3b98c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "cadbee1e084787c39c95ed823af8fd8fd8c3c9635faf25a0f34c67d9f36415ea", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 221513, + "line_end": 221513, + "char_start": 2544941, + "char_end": 2544952, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 221513, + "line_end": 221513, + "char_start": 2544943, + "char_end": 2544947, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-83c9a799ecf3b7bceb64", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 223550, + "line_end": 223550, + "char_start": 2564254, + "char_end": 2564293, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 223550, + "line_end": 223550, + "char_start": 2564275, + "char_end": 2564281, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cf42256800cdd57b7c57", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "de8f2277cfcf1761ea27f31695b0252c0f5ec901aa1b134b677d3ce1017f0c63", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 223889, + "line_end": 223889, + "char_start": 2569102, + "char_end": 2569134, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 223889, + "line_end": 223889, + "char_start": 2569127, + "char_end": 2569131, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a2a102b407431681efe5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a0668f0728bd5c725e49e53e851530bc3f7a60f108a088e8605f07a2885ce8cb", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 227750, + "line_end": 227750, + "char_start": 2603772, + "char_end": 2603798, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 227750, + "line_end": 227750, + "char_start": 2603789, + "char_end": 2603793, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-79502108ec559f85d262", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b5d0846d9139ecc88f08b8a3deeca054c156a343d2b4828dc77f6dd9e39d2fab", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 232307, + "line_end": 232307, + "char_start": 2648188, + "char_end": 2648204, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 232307, + "line_end": 232307, + "char_start": 2648191, + "char_end": 2648195, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f8e0047480caa471e4cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "946c7f9c0f739b6beb37f9e262f521fa28e804ffab8dd5d7d706107a669f7a7e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 234743, + "line_end": 234743, + "char_start": 2672888, + "char_end": 2672902, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 234743, + "line_end": 234743, + "char_start": 2672895, + "char_end": 2672901, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-eba6e6693a10b71b7b09", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "75528baac494f11ba8f4f65eb5a3ba631c32a9e6d9b100cea6f78417ff240af5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 234854, + "line_end": 234854, + "char_start": 2674001, + "char_end": 2674033, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 234854, + "line_end": 234854, + "char_start": 2674002, + "char_end": 2674006, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-057fb21c0eff76610884", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "85a67f6ccff34ff0b292cc15b4f6d129ba9d6df834ccc3475b95786f958d7b27", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 235826, + "line_end": 235826, + "char_start": 2682764, + "char_end": 2682783, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 235826, + "line_end": 235826, + "char_start": 2682778, + "char_end": 2682782, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2e96a4029ee1aaacc4f0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "766195dda4e79ca3aca578f13ae3a286d55fbb738f9d3b1fe48249dfc985cad6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 240086, + "line_end": 240086, + "char_start": 2723888, + "char_end": 2723908, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 240086, + "line_end": 240086, + "char_start": 2723896, + "char_end": 2723900, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d623ad327b95f4b14512", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "766195dda4e79ca3aca578f13ae3a286d55fbb738f9d3b1fe48249dfc985cad6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 240449, + "line_end": 240449, + "char_start": 2727019, + "char_end": 2727039, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 240449, + "line_end": 240449, + "char_start": 2727027, + "char_end": 2727031, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a700d14ebc85b1f81675", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ba4d22aab17dfc67d9403ad2b9bdd97abc29480a3d3bdfdccb1501464fb7bbc2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 245519, + "line_end": 245519, + "char_start": 2772269, + "char_end": 2772305, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 245519, + "line_end": 245519, + "char_start": 2772272, + "char_end": 2772276, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-88136e213be7b5e0d13d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "fefb45518205b219de8916a361a0689e2f373d9cf19f89663f6db7a56b290bf4", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 245687, + "line_end": 245687, + "char_start": 2773797, + "char_end": 2773848, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 245687, + "line_end": 245687, + "char_start": 2773799, + "char_end": 2773803, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-014127dddfd7fa2e924f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9ec0ccf462dda7f00a0280814a830e74d94e4c79322a9b5734ace4d24638273d", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 248006, + "line_end": 248006, + "char_start": 2792443, + "char_end": 2792472, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 248006, + "line_end": 248006, + "char_start": 2792467, + "char_end": 2792471, + "column_start": 24, + "column_end": 28 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-58a34a10101a4a4e3e72", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4e28b2a4543866016853495227dd3f6404a240661f25ceec4691197d51f81590", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 249185, + "line_end": 249185, + "char_start": 2802137, + "char_end": 2802177, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 249185, + "line_end": 249185, + "char_start": 2802150, + "char_end": 2802156, + "column_start": 13, + "column_end": 19 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0c0f05d4590aa4cff2c8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e1f1c85cc0f5b84db8777add81745bfb04c0bf5ccb86208c25ed7fa5790d7aea", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 251759, + "line_end": 251759, + "char_start": 2823119, + "char_end": 2823149, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 251759, + "line_end": 251759, + "char_start": 2823119, + "char_end": 2823123, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bae1c8b3df1cab4a947d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b609523bbf677e298c6a12071aa44b5db291bef39dffc4982d71577f32d6007b", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 252242, + "line_end": 252242, + "char_start": 2827032, + "char_end": 2827058, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 252242, + "line_end": 252242, + "char_start": 2827051, + "char_end": 2827057, + "column_start": 19, + "column_end": 25 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6130ef9b8ce3d246508e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7dc1e612760bd41f1b1d82097d04b805f8f4f4abb1456e750c58a80595876a42", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 253874, + "line_end": 253874, + "char_start": 2841737, + "char_end": 2841747, + "column_start": 0, + "column_end": 10 + }, + "surface_location": { + "line_start": 253874, + "line_end": 253874, + "char_start": 2841740, + "char_end": 2841746, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fbd5cb77953c3bea0458", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "10534904d50ed331fdccacb3b08db64e38d564c491a60403e686ca4b5d959de4", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 254264, + "line_end": 254264, + "char_start": 2846218, + "char_end": 2846238, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 254264, + "line_end": 254264, + "char_start": 2846220, + "char_end": 2846224, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bc4ef6fa068f658297c8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1dddd625ed59d92f42e6685bd206fee90e2205bd4802ca5efb9991984928d9a1", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 254609, + "line_end": 254609, + "char_start": 2849313, + "char_end": 2849364, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 254609, + "line_end": 254609, + "char_start": 2849324, + "char_end": 2849328, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9ecba996455290ea522e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3160be8fd865ee319381e78c89d3dfb1d668e0e491d66580618b5ee151940dbd", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 256220, + "line_end": 256220, + "char_start": 2864276, + "char_end": 2864289, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 256220, + "line_end": 256220, + "char_start": 2864284, + "char_end": 2864288, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ed0464d719c163c7b3c6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "50ab75acbc686e08e3aec1ec0e8040d9c205acb7544eb8028836b44b45fb8c9c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 262049, + "line_end": 262049, + "char_start": 2917168, + "char_end": 2917192, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 262049, + "line_end": 262049, + "char_start": 2917180, + "char_end": 2917184, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-76b6a2c8987668e7c762", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "eafcda6e0663788b4c0d675a22bee757278e8566d88e08e7a19518f379479282", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 263549, + "line_end": 263549, + "char_start": 2931424, + "char_end": 2931447, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 263549, + "line_end": 263549, + "char_start": 2931426, + "char_end": 2931430, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-86a44b7dad3efba090e6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "cda9cae09268ee476b5023315605328440579ac2778d7a862c4d3d4282726797", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 263969, + "line_end": 263969, + "char_start": 2935111, + "char_end": 2935154, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 263969, + "line_end": 263969, + "char_start": 2935115, + "char_end": 2935119, + "column_start": 4, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-15a153ee3ac14a22e4c6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "479169d326e30bd31c717244e41a104033f2b7b45cdfbfce828f1e18b3b9bc8e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 264116, + "line_end": 264116, + "char_start": 2936358, + "char_end": 2936418, + "column_start": 4, + "column_end": 64 + }, + "surface_location": { + "line_start": 264116, + "line_end": 264116, + "char_start": 2936361, + "char_end": 2936365, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5ca75480f09fbccb1452", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e090137a8f14fcad6e870de3de469a859be373aa284da13d712d661e1bb25013", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 264560, + "line_end": 264560, + "char_start": 2940187, + "char_end": 2940234, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 264560, + "line_end": 264560, + "char_start": 2940210, + "char_end": 2940214, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cbc9d14b18ec603658ea", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e14fe50047d97bfbec602f368bc1b7f61c6d17f6a79a6f052d608fb1263885a1", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 266384, + "line_end": 266384, + "char_start": 2959573, + "char_end": 2959620, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 266384, + "line_end": 266384, + "char_start": 2959587, + "char_end": 2959591, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dc53f8b488dc84645a42", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "8c4a8e73221902bf793e39d0289344d5448f3036700f82fe63f0b19d936d33e4", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 266675, + "line_end": 266675, + "char_start": 2962863, + "char_end": 2962874, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 266675, + "line_end": 266675, + "char_start": 2962865, + "char_end": 2962873, + "column_start": 2, + "column_end": 10 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cdeef87e9b9b0494f734", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9acb082bbd401bcf81591247b3be2a650a0e7d1473a5f4b3b5defbf113516fa7", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 268019, + "line_end": 268019, + "char_start": 2975643, + "char_end": 2975689, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 268019, + "line_end": 268019, + "char_start": 2975672, + "char_end": 2975676, + "column_start": 29, + "column_end": 33 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c73291d2ba1bb933f57b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "fc8af8b8b540744d48f3619c3827d39a29f175d9b195bb9f5fd736fa8a42c205", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 272210, + "line_end": 272210, + "char_start": 3017828, + "char_end": 3017853, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 272210, + "line_end": 272210, + "char_start": 3017828, + "char_end": 3017834, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fff1683eddb9f4ec5058", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e64c389389800083f1e691da67587cb7e7aa0ad10943983400f91194bec196eb", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 274349, + "line_end": 274349, + "char_start": 3038189, + "char_end": 3038264, + "column_start": 0, + "column_end": 75 + }, + "surface_location": { + "line_start": 274349, + "line_end": 274349, + "char_start": 3038219, + "char_end": 3038223, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b7e864817ad8218e8237", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0e5eb2b11cc2df5795d0b96729f46420eb7363bff9ef102cc58b92ddf3f905a8", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 274415, + "line_end": 274415, + "char_start": 3038873, + "char_end": 3038885, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 274415, + "line_end": 274415, + "char_start": 3038876, + "char_end": 3038880, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5e6b528e9ec1d14ecad6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0512c14acb63df5593ad66b230011627071bed1ec3a0d217ed4094dd68955540", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 274589, + "line_end": 274589, + "char_start": 3040688, + "char_end": 3040743, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 274589, + "line_end": 274589, + "char_start": 3040691, + "char_end": 3040695, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-18d963170e366f0cd0ab", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "554d6c87fec84c00707b0ad560bdc7d9f74c25f5ca5582c25ebbc117940ae3cd", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 275762, + "line_end": 275762, + "char_start": 3051642, + "char_end": 3051677, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 275762, + "line_end": 275762, + "char_start": 3051654, + "char_end": 3051658, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-35e22e7d5e8e875b0e34", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "2971b9a60168089bc56d568b4814bc6bfab98f93fd7234ee924a381b0da53b0f", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 276446, + "line_end": 276446, + "char_start": 3057107, + "char_end": 3057149, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 276446, + "line_end": 276446, + "char_start": 3057115, + "char_end": 3057119, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0dd0de105e5daec12f9c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4932b7e7477cb7dda704936b95908c93715f1d136f16d570dfe1f11034a3fd4b", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 281723, + "line_end": 281724, + "char_start": 3104467, + "char_end": 3104496, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 281723, + "line_end": 281723, + "char_start": 3104469, + "char_end": 3104473, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-970cffb05a4287317c9c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f7ecc4f036fe634661cc63f4eb8c8b5903aef66d2f190e3fdfe07014f573f453", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 284075, + "line_end": 284075, + "char_start": 3124946, + "char_end": 3124958, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 284075, + "line_end": 284075, + "char_start": 3124949, + "char_end": 3124953, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dc9c1d786df04a4af16e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "6f7b9353e6bd8e3e97023de9d2691886c7b6039fb7ef14511cb00a86aea2543c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 286835, + "line_end": 286835, + "char_start": 3148710, + "char_end": 3148741, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 286835, + "line_end": 286835, + "char_start": 3148729, + "char_end": 3148733, + "column_start": 19, + "column_end": 23 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f9b80d25d773d2e8693e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "899b4a4ab5271ff26f35a75ab626e01ac0e748ff957fcebf7e00447b4ff128da", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 287372, + "line_end": 287372, + "char_start": 3153604, + "char_end": 3153663, + "column_start": 0, + "column_end": 59 + }, + "surface_location": { + "line_start": 287372, + "line_end": 287372, + "char_start": 3153614, + "char_end": 3153618, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d647797cf8305e18d596", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "dffe0dfc01dd1a51a5e22a5cac7c3a8d5c191dc07d1f93661d0822af9da0e98c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 287936, + "line_end": 287936, + "char_start": 3160145, + "char_end": 3160174, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 287936, + "line_end": 287936, + "char_start": 3160155, + "char_end": 3160159, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a660ebcbf2bad5757a12", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "fa913eace4511587a09146bd288e0a7a49185029a0672488dcfca712b5e531f8", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 288284, + "line_end": 288284, + "char_start": 3163359, + "char_end": 3163385, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 288284, + "line_end": 288284, + "char_start": 3163368, + "char_end": 3163372, + "column_start": 9, + "column_end": 13 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f3e824c908a4face0e82", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "72b2fb7e4cf21340aed66b1a53c20112906f0aa228acef9573f39919e3a27f3f", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 302696, + "line_end": 302697, + "char_start": 3276730, + "char_end": 3276753, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 302696, + "line_end": 302696, + "char_start": 3276732, + "char_end": 3276736, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f6eff94768791a953d0a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ca57a4f62543e30cb0907ad7aa22357159fdd8996dabca34c64e9ecbf44ffbca", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 306122, + "line_end": 306122, + "char_start": 3303140, + "char_end": 3303180, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 306122, + "line_end": 306122, + "char_start": 3303140, + "char_end": 3303144, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bed2b1b03376bee80dcf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "fcf1c636c1272c09c44737c79c3e012e10f574a73715b35683bf6f344399c7d9", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 310598, + "line_end": 310598, + "char_start": 3340543, + "char_end": 3340571, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 310598, + "line_end": 310598, + "char_start": 3340559, + "char_end": 3340563, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-85fda750cc76895dfa0f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "496feb73fe76b46d770e6851faacc2d4fd3e3d309ad869d6868427497276473d", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 312764, + "line_end": 312764, + "char_start": 3358773, + "char_end": 3358805, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 312764, + "line_end": 312764, + "char_start": 3358775, + "char_end": 3358779, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1ca78a2a22ee15790239", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "71b902af3e32b580c803f55e8e9048608c52de94ea00d754a167320dc1ee54bc", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 314399, + "line_end": 314399, + "char_start": 3372308, + "char_end": 3372317, + "column_start": 0, + "column_end": 9 + }, + "surface_location": { + "line_start": 314399, + "line_end": 314399, + "char_start": 3372310, + "char_end": 3372314, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-aa57848989ab453a1ee3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "400d9076ed6299b0271945e4f337f501c0eafa9ee2c152d56be22a7efbfbde60", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 316856, + "line_end": 316856, + "char_start": 3390629, + "char_end": 3390653, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 316856, + "line_end": 316856, + "char_start": 3390644, + "char_end": 3390648, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b6b286ea75e6dece31c4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "776d0aeebb38051eb11ae1012b38934a310f7b9a4dffef01c407783a1cef91f3", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 317423, + "line_end": 317423, + "char_start": 3394852, + "char_end": 3394868, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 317423, + "line_end": 317423, + "char_start": 3394863, + "char_end": 3394867, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c0e956146506ef113c9b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b84f7f0ca7dcab9f3d0af8c63e2ba7505d2e4044b94edd510e9532cc363a142e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 322943, + "line_end": 322943, + "char_start": 3449521, + "char_end": 3449561, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 322943, + "line_end": 322943, + "char_start": 3449531, + "char_end": 3449535, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ca55ca8e4cc01e54d68f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ff24d48d3882a93e18ec2a17137691a813ff603057f43567085f181c1db9605e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 326576, + "line_end": 326576, + "char_start": 3480331, + "char_end": 3480374, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 326576, + "line_end": 326576, + "char_start": 3480348, + "char_end": 3480352, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-95d633055a0cfde88195", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7ae90666402721340a5f0fcc4f584d964f008cd153f567537f37110937a1c1d6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 326768, + "line_end": 326768, + "char_start": 3482000, + "char_end": 3482034, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 326768, + "line_end": 326768, + "char_start": 3482017, + "char_end": 3482021, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1f5fb8560b95883a5d5b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9928ed896809711ee868b06510c9ccba166c5142fe6ce4b21ea0677b47cd1987", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 329036, + "line_end": 329036, + "char_start": 3503029, + "char_end": 3503069, + "column_start": 8, + "column_end": 48 + }, + "surface_location": { + "line_start": 329036, + "line_end": 329036, + "char_start": 3503057, + "char_end": 3503061, + "column_start": 36, + "column_end": 40 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4f556791056a4dc317e4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "16521e0d9036247876d03b2c3498428e3e56cde4f8efa9fb984da1e11ddec4c7", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 330785, + "line_end": 330785, + "char_start": 3517499, + "char_end": 3517520, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 330785, + "line_end": 330785, + "char_start": 3517499, + "char_end": 3517503, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9f80c30d8c8cf1f64882", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d99c505b6294f116f7b28d64aa85340a4a28a6a8623a0e7caaa50ea395e3dd96", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 335684, + "line_end": 335684, + "char_start": 3559180, + "char_end": 3559213, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 335684, + "line_end": 335684, + "char_start": 3559182, + "char_end": 3559186, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bd259336340f322f383b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "378cff21247b0c9a21c7f32b9799540e1c7517b05552f669fe49d1206610f94a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 336491, + "line_end": 336491, + "char_start": 3566200, + "char_end": 3566221, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 336491, + "line_end": 336491, + "char_start": 3566202, + "char_end": 3566206, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3f09005a6775793ec948", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "cb32858511d862d702c81f3450007e62fef8688f379c0eaad43dd63344f0c3c8", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 338156, + "line_end": 338156, + "char_start": 3581085, + "char_end": 3581122, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 338156, + "line_end": 338156, + "char_start": 3581095, + "char_end": 3581099, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a16f89b235a313106c83", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "866239e2f77a6c21a35390200abfafd644823f11038a9e07f86da17c5875d272", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 339362, + "line_end": 339362, + "char_start": 3593021, + "char_end": 3593063, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 339362, + "line_end": 339362, + "char_start": 3593023, + "char_end": 3593027, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-621ce774bfe72f8dafbf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f5f86aee965e871629d23f08c4ef106e17fabe102e2203a83db21aefcf9288d0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 339743, + "line_end": 339743, + "char_start": 3596309, + "char_end": 3596325, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 339743, + "line_end": 339743, + "char_start": 3596311, + "char_end": 3596317, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ed562689316130d1d9ef", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "273dd32433019e266cd84c34c090b9ea0cc344c6228cd79ded02fb5fa8c0c88c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 339869, + "line_end": 339869, + "char_start": 3597240, + "char_end": 3597254, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 339869, + "line_end": 339869, + "char_start": 3597242, + "char_end": 3597246, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-de3d6ea91f6cf0454acb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "38e50f777795ae4ad2a3766863fe617f5946c7f8fb34b601dfae7f430e8eda44", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 351506, + "line_end": 351506, + "char_start": 3685587, + "char_end": 3685617, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 351506, + "line_end": 351506, + "char_start": 3685595, + "char_end": 3685599, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bc289218bf44bf086b80", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "fbe695aa06079a12121f8a6447ea973e45b549aed4c7aaf9ea199815371a8775", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 355352, + "line_end": 355352, + "char_start": 3714371, + "char_end": 3714394, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 355352, + "line_end": 355352, + "char_start": 3714371, + "char_end": 3714375, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-460788291a49080197ce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "76ae2eeff7731510b8ed9deb288488cf166fe6e7ec12fda11b820f5857cea662", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 369761, + "line_end": 369761, + "char_start": 3825716, + "char_end": 3825727, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 369761, + "line_end": 369761, + "char_start": 3825718, + "char_end": 3825722, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4ec54cb626d1eb8178d9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "800626819c784b5eff766cf1a29f0687361dd9d07bf870e0314c1fe5e450d0b7", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 373088, + "line_end": 373088, + "char_start": 3848740, + "char_end": 3848757, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 373088, + "line_end": 373088, + "char_start": 3848745, + "char_end": 3848749, + "column_start": 5, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3851362a493d8392a8b8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0246fb99cb8414d5ea152b3c87e7022297187d3ec2c0daad5823097334516241", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 374231, + "line_end": 374231, + "char_start": 3856301, + "char_end": 3856330, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 374231, + "line_end": 374231, + "char_start": 3856313, + "char_end": 3856317, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e46207863d0c11d26052", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "dba3b028e51580d856d0a07008f3ed89a107e89f797b13a4bbbbebcc4c3cbdf3", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 379970, + "line_end": 379970, + "char_start": 3901006, + "char_end": 3901031, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 379970, + "line_end": 379970, + "char_start": 3901022, + "char_end": 3901026, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fdc58decb8d6ec97a148", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7919582f29b17f4e4b6490ef9f51fd499de823ed36af38100b7c13bd2b510b78", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 383783, + "line_end": 383783, + "char_start": 3935936, + "char_end": 3935974, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 383783, + "line_end": 383783, + "char_start": 3935962, + "char_end": 3935966, + "column_start": 26, + "column_end": 30 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2314f3f5070dc2a452a1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "87baccc9ec59037d2240ca716dab3d63ceae931cec4b052d2b3c7f627824efd0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 389483, + "line_end": 389483, + "char_start": 3990661, + "char_end": 3990682, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 389483, + "line_end": 389483, + "char_start": 3990665, + "char_end": 3990669, + "column_start": 4, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-436e2bd2943051a10521", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "cb35d72c00c1da30c3fd4f38a74ee57624ffa3f9868c0b31dbb60eed97aeb2d9", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 392654, + "line_end": 392654, + "char_start": 4021924, + "char_end": 4021975, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 392654, + "line_end": 392654, + "char_start": 4021926, + "char_end": 4021930, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b04dfc15971712189382", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 394808, + "line_end": 394808, + "char_start": 4045598, + "char_end": 4045637, + "column_start": 69, + "column_end": 108 + }, + "surface_location": { + "line_start": 394808, + "line_end": 394808, + "char_start": 4045619, + "char_end": 4045625, + "column_start": 90, + "column_end": 96 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c308da3420664a6a385a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3adbb44fbe2382354bccd0421376393e63d7b861e7f9df8b78205ad142b876b5", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 395420, + "line_end": 395420, + "char_start": 4054262, + "char_end": 4054301, + "column_start": 68, + "column_end": 107 + }, + "surface_location": { + "line_start": 395420, + "line_end": 395420, + "char_start": 4054283, + "char_end": 4054289, + "column_start": 89, + "column_end": 95 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1c922f97c2cc3ecb73f3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f5f86aee965e871629d23f08c4ef106e17fabe102e2203a83db21aefcf9288d0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 396740, + "line_end": 396740, + "char_start": 4067690, + "char_end": 4067706, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 396740, + "line_end": 396740, + "char_start": 4067692, + "char_end": 4067698, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1e2abfc7b4ba4e4f4ef0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "273dd32433019e266cd84c34c090b9ea0cc344c6228cd79ded02fb5fa8c0c88c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 396866, + "line_end": 396866, + "char_start": 4068623, + "char_end": 4068637, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 396866, + "line_end": 396866, + "char_start": 4068625, + "char_end": 4068629, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-abfa9ac5d11b240f6b19", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f632d045baf99f935429817a5372602862f6129f752f630f1e4fce1f42377d19", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 400277, + "line_end": 400277, + "char_start": 4099598, + "char_end": 4099616, + "column_start": 0, + "column_end": 18 + }, + "surface_location": { + "line_start": 400277, + "line_end": 400277, + "char_start": 4099611, + "char_end": 4099615, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b7f91e75cf4593b27c46", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "844cf93eb12e6b5b4046c433a5a242c72f90de9a39a2ee45184037776475d990", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 402068, + "line_end": 402068, + "char_start": 4116276, + "char_end": 4116320, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 402068, + "line_end": 402068, + "char_start": 4116309, + "char_end": 4116315, + "column_start": 33, + "column_end": 39 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a79d2217ab283e1c675d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02372e9af0886bd60bde3fc4ddb2068b91d267f97a8c5a077e6d6f9fc4bf5ce", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 402248, + "line_end": 402248, + "char_start": 4117604, + "char_end": 4117617, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 402248, + "line_end": 402248, + "char_start": 4117606, + "char_end": 4117612, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5a9887836614bf09c883", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "16684bd3422ba7d770b0120092eb3330597ac7224284fcb76247f43a2d93ddae", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 402929, + "line_end": 402929, + "char_start": 4123224, + "char_end": 4123238, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 402929, + "line_end": 402929, + "char_start": 4123226, + "char_end": 4123230, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-378a33a225f659aea4cf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "647bd79b8855a2f66c1144918cf20d7bccdf3a2d08a73647163608ef8a462ae3", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 406301, + "line_end": 406301, + "char_start": 4151924, + "char_end": 4151940, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 406301, + "line_end": 406301, + "char_start": 4151931, + "char_end": 4151935, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8db1be42a31369e74562", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "1e3ee0ab12d1c4e2bd38518b6ee2775b9715a8c53bea9d6098099e2f1258a09c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 417404, + "line_end": 417404, + "char_start": 4239030, + "char_end": 4239045, + "column_start": 0, + "column_end": 15 + }, + "surface_location": { + "line_start": 417404, + "line_end": 417404, + "char_start": 4239034, + "char_end": 4239040, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c5a14b30759b24f0fb82", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "82cc53e32d6bc364a44231325cc04515da541ad1c0a608c3a37654e5b226c562", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 422537, + "line_end": 422537, + "char_start": 4280796, + "char_end": 4280836, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 422537, + "line_end": 422537, + "char_start": 4280825, + "char_end": 4280831, + "column_start": 29, + "column_end": 35 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-aa0a61ee2351182cd3b7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 423809, + "line_end": 423809, + "char_start": 4293362, + "char_end": 4293399, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 423809, + "line_end": 423809, + "char_start": 4293381, + "char_end": 4293387, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8cdd5899aab40a8f992b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "4dfa634d6fe48f7107365fef92e141452037a0aa3a77e37175f638f43b7ea9e3", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 424016, + "line_end": 424016, + "char_start": 4296776, + "char_end": 4296814, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 424016, + "line_end": 424016, + "char_start": 4296809, + "char_end": 4296813, + "column_start": 33, + "column_end": 37 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5850045ab654e94703bf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 424655, + "line_end": 424655, + "char_start": 4303930, + "char_end": 4303967, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 424655, + "line_end": 424655, + "char_start": 4303949, + "char_end": 4303955, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c5a7f9f1203619c3a97f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9d07c7cdab5460dfc6a4d396ceb8dde43c6a069bbd64f27cfff7b2ce7de92378", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 427229, + "line_end": 427229, + "char_start": 4332437, + "char_end": 4332450, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 427229, + "line_end": 427229, + "char_start": 4332445, + "char_end": 4332449, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4c99eb383510acf6b3d1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 428615, + "line_end": 428615, + "char_start": 4348340, + "char_end": 4348377, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 428615, + "line_end": 428615, + "char_start": 4348359, + "char_end": 4348365, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-daddac5d495dc04baef5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "3eaaebe6614f1b62cf2ebace9c3be42060fb105c36f6850c0ca7f4c267ad6994", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 429518, + "line_end": 429519, + "char_start": 4359317, + "char_end": 4359422, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 429518, + "line_end": 429518, + "char_start": 4359417, + "char_end": 4359421, + "column_start": 100, + "column_end": 104 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3933455581d83742ffce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 429674, + "line_end": 429674, + "char_start": 4361607, + "char_end": 4361644, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 429674, + "line_end": 429674, + "char_start": 4361626, + "char_end": 4361632, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4e2377212d721cab36c2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7a415db6483f418378f69cbc20b2fdb66babe25f6e50ea7a03068e11aab71ebe", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 430514, + "line_end": 430514, + "char_start": 4372026, + "char_end": 4372055, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 430514, + "line_end": 430514, + "char_start": 4372048, + "char_end": 4372054, + "column_start": 22, + "column_end": 28 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角悄然勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ed115c42de15598749d0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "295421ea58f4daeb52d4661aa09f3b0068864a073bb7415ac0b095afa5c8e0cd", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 432707, + "line_end": 432707, + "char_start": 4396078, + "char_end": 4396171, + "column_start": 51, + "column_end": 144 + }, + "surface_location": { + "line_start": 432707, + "line_end": 432707, + "char_start": 4396146, + "char_end": 4396150, + "column_start": 119, + "column_end": 123 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-daee268df40a524e0cf9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "473b48e0692fbba37672043129f099ae239d1064ba86e2f70a16a33c48f7cf89", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 433313, + "line_end": 433313, + "char_start": 4402066, + "char_end": 4402080, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 433313, + "line_end": 433313, + "char_start": 4402068, + "char_end": 4402072, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ce0b613827d3c6f2c897", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 433367, + "line_end": 433367, + "char_start": 4402922, + "char_end": 4402959, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 433367, + "line_end": 433367, + "char_start": 4402941, + "char_end": 4402947, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d2fc63ad11a361cc03df", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "77e6ccf9420318baea8f12059bd8b4d87f43f76378c8e910161f1919611ca2e0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 433703, + "line_end": 433703, + "char_start": 4406523, + "char_end": 4406551, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 433703, + "line_end": 433703, + "char_start": 4406523, + "char_end": 4406527, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4a78379097ea20adc930", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "67b8e99af5e6323629e65fae6921eede8aaaac4ae6aa206fa6a70022e2378dc2", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 443492, + "line_end": 443492, + "char_start": 4512008, + "char_end": 4512059, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 443492, + "line_end": 443492, + "char_start": 4512021, + "char_end": 4512025, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-99b2f9eb6f327e325abf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 446567, + "line_end": 446567, + "char_start": 4544160, + "char_end": 4544197, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 446567, + "line_end": 446567, + "char_start": 4544179, + "char_end": 4544185, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a4903002525cdfd3977e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 446669, + "line_end": 446669, + "char_start": 4547095, + "char_end": 4547132, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 446669, + "line_end": 446669, + "char_start": 4547114, + "char_end": 4547120, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e6c4a69f3f4e3f4ab35a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 452270, + "line_end": 452270, + "char_start": 4607953, + "char_end": 4607990, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 452270, + "line_end": 452270, + "char_start": 4607972, + "char_end": 4607978, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-19a6219f996f2f02bce7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 453062, + "line_end": 453062, + "char_start": 4620412, + "char_end": 4620449, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 453062, + "line_end": 453062, + "char_start": 4620431, + "char_end": 4620437, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-074ac7005d064e17fa81", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 453116, + "line_end": 453116, + "char_start": 4621864, + "char_end": 4621901, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 453116, + "line_end": 453116, + "char_start": 4621883, + "char_end": 4621889, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a6fb2f82ef55cc70d896", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d66241828010d42a8b53dd350137dadb725cee7e83bf8c899a663f0359e1c936", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 456383, + "line_end": 456383, + "char_start": 4655315, + "char_end": 4655382, + "column_start": 0, + "column_end": 67 + }, + "surface_location": { + "line_start": 456383, + "line_end": 456383, + "char_start": 4655332, + "char_end": 4655336, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3fbd14e063dc677c1dc3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "589afefcc3021eed7527558c1668560d1aac276d31b9aad8391d62897faa2e28", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 456521, + "line_end": 456521, + "char_start": 4656775, + "char_end": 4656823, + "column_start": 0, + "column_end": 48 + }, + "surface_location": { + "line_start": 456521, + "line_end": 456521, + "char_start": 4656777, + "char_end": 4656781, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-73f4882bc7ea052f90ce", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 458075, + "line_end": 458075, + "char_start": 4673927, + "char_end": 4673964, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 458075, + "line_end": 458075, + "char_start": 4673946, + "char_end": 4673952, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1979d984cf6ca2f98b2d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "de8f2277cfcf1761ea27f31695b0252c0f5ec901aa1b134b677d3ce1017f0c63", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 458168, + "line_end": 458168, + "char_start": 4675396, + "char_end": 4675428, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 458168, + "line_end": 458168, + "char_start": 4675421, + "char_end": 4675425, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ff4487114a978d4d1dad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b422cb92fe87fc60006e2ea1654126576c83a239f1ad5b9c44e44e524741d690", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 464732, + "line_end": 464732, + "char_start": 4739490, + "char_end": 4739527, + "column_start": 68, + "column_end": 105 + }, + "surface_location": { + "line_start": 464732, + "line_end": 464732, + "char_start": 4739509, + "char_end": 4739515, + "column_start": 87, + "column_end": 93 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "空气仿佛凝固", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b4fc1acc3e5e4c1f7a32", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e1f1c85cc0f5b84db8777add81745bfb04c0bf5ccb86208c25ed7fa5790d7aea", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 469817, + "line_end": 469817, + "char_start": 4787223, + "char_end": 4787253, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 469817, + "line_end": 469817, + "char_start": 4787223, + "char_end": 4787227, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-279cae1e44465557e872", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7dc1e612760bd41f1b1d82097d04b805f8f4f4abb1456e750c58a80595876a42", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 471136, + "line_end": 471136, + "char_start": 4799792, + "char_end": 4799802, + "column_start": 0, + "column_end": 10 + }, + "surface_location": { + "line_start": 471136, + "line_end": 471136, + "char_start": 4799795, + "char_end": 4799801, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-94bb1cf938079b8824e6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0f96474bbf708d4f47913f8f3a54d4a467a0cd589e24a15b2f057bd1ccbee284", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 471409, + "line_end": 471409, + "char_start": 4802848, + "char_end": 4802897, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 471409, + "line_end": 471409, + "char_start": 4802857, + "char_end": 4802861, + "column_start": 9, + "column_end": 13 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-13c2a577a15bef5ab1e1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "cda9cae09268ee476b5023315605328440579ac2778d7a862c4d3d4282726797", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 476071, + "line_end": 476071, + "char_start": 4848399, + "char_end": 4848442, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 476071, + "line_end": 476071, + "char_start": 4848403, + "char_end": 4848407, + "column_start": 4, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-035cda977cfd30941098", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "479169d326e30bd31c717244e41a104033f2b7b45cdfbfce828f1e18b3b9bc8e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 476212, + "line_end": 476212, + "char_start": 4849618, + "char_end": 4849678, + "column_start": 4, + "column_end": 64 + }, + "surface_location": { + "line_start": 476212, + "line_end": 476212, + "char_start": 4849621, + "char_end": 4849625, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2e17fd357ff3a28da08b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e090137a8f14fcad6e870de3de469a859be373aa284da13d712d661e1bb25013", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 476392, + "line_end": 476392, + "char_start": 4851612, + "char_end": 4851659, + "column_start": 0, + "column_end": 47 + }, + "surface_location": { + "line_start": 476392, + "line_end": 476392, + "char_start": 4851635, + "char_end": 4851639, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1224f36ff2b65a726897", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "8c4a8e73221902bf793e39d0289344d5448f3036700f82fe63f0b19d936d33e4", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 477403, + "line_end": 477403, + "char_start": 4862416, + "char_end": 4862427, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 477403, + "line_end": 477403, + "char_start": 4862418, + "char_end": 4862426, + "column_start": 2, + "column_end": 10 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d6285297f2e8435d1e9b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9acb082bbd401bcf81591247b3be2a650a0e7d1473a5f4b3b5defbf113516fa7", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 478093, + "line_end": 478093, + "char_start": 4869062, + "char_end": 4869108, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 478093, + "line_end": 478093, + "char_start": 4869091, + "char_end": 4869095, + "column_start": 29, + "column_end": 33 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a76a49da0759113d7125", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "e64c389389800083f1e691da67587cb7e7aa0ad10943983400f91194bec196eb", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 479410, + "line_end": 479410, + "char_start": 4882443, + "char_end": 4882518, + "column_start": 0, + "column_end": 75 + }, + "surface_location": { + "line_start": 479410, + "line_end": 479410, + "char_start": 4882473, + "char_end": 4882477, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-594a0678b139c949caa5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0e5eb2b11cc2df5795d0b96729f46420eb7363bff9ef102cc58b92ddf3f905a8", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 479473, + "line_end": 479473, + "char_start": 4883115, + "char_end": 4883127, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 479473, + "line_end": 479473, + "char_start": 4883118, + "char_end": 4883122, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1b851a6d5a682b376653", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "0512c14acb63df5593ad66b230011627071bed1ec3a0d217ed4094dd68955540", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 479539, + "line_end": 479539, + "char_start": 4883940, + "char_end": 4883995, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 479539, + "line_end": 479539, + "char_start": 4883943, + "char_end": 4883947, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e5d0af41b2d188d95c54", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "554d6c87fec84c00707b0ad560bdc7d9f74c25f5ca5582c25ebbc117940ae3cd", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 480088, + "line_end": 480088, + "char_start": 4889323, + "char_end": 4889358, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 480088, + "line_end": 480088, + "char_start": 4889335, + "char_end": 4889339, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-901acb498a585afcd3ea", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "2971b9a60168089bc56d568b4814bc6bfab98f93fd7234ee924a381b0da53b0f", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 480460, + "line_end": 480460, + "char_start": 4892426, + "char_end": 4892468, + "column_start": 0, + "column_end": 42 + }, + "surface_location": { + "line_start": 480460, + "line_end": 480460, + "char_start": 4892434, + "char_end": 4892438, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a0b72ca936ebfc657195", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f7ecc4f036fe634661cc63f4eb8c8b5903aef66d2f190e3fdfe07014f573f453", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 482830, + "line_end": 482830, + "char_start": 4913038, + "char_end": 4913050, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 482830, + "line_end": 482830, + "char_start": 4913041, + "char_end": 4913045, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-be9025547554acc4dc7f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "dffe0dfc01dd1a51a5e22a5cac7c3a8d5c191dc07d1f93661d0822af9da0e98c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 484687, + "line_end": 484687, + "char_start": 4930416, + "char_end": 4930445, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 484687, + "line_end": 484687, + "char_start": 4930426, + "char_end": 4930430, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-50c19713c6cbfd8cc340", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "b84f7f0ca7dcab9f3d0af8c63e2ba7505d2e4044b94edd510e9532cc363a142e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 491908, + "line_end": 491908, + "char_start": 4993289, + "char_end": 4993329, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 491908, + "line_end": 491908, + "char_start": 4993299, + "char_end": 4993303, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f6dc850263637b7a7747", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "ff24d48d3882a93e18ec2a17137691a813ff603057f43567085f181c1db9605e", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 493684, + "line_end": 493684, + "char_start": 5008485, + "char_end": 5008528, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 493684, + "line_end": 493684, + "char_start": 5008502, + "char_end": 5008506, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9c74b0894d0ec20fa332", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7ae90666402721340a5f0fcc4f584d964f008cd153f567537f37110937a1c1d6", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 493759, + "line_end": 493759, + "char_start": 5009242, + "char_end": 5009276, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 493759, + "line_end": 493759, + "char_start": 5009259, + "char_end": 5009263, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-47a346dc87d8aee549bb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "9928ed896809711ee868b06510c9ccba166c5142fe6ce4b21ea0677b47cd1987", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 494869, + "line_end": 494869, + "char_start": 5021328, + "char_end": 5021368, + "column_start": 8, + "column_end": 48 + }, + "surface_location": { + "line_start": 494869, + "line_end": 494869, + "char_start": 5021356, + "char_end": 5021360, + "column_start": 36, + "column_end": 40 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-04ca38c795ea66e5b2bb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "16521e0d9036247876d03b2c3498428e3e56cde4f8efa9fb984da1e11ddec4c7", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 495739, + "line_end": 495739, + "char_start": 5028828, + "char_end": 5028849, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 495739, + "line_end": 495739, + "char_start": 5028828, + "char_end": 5028832, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cc390448cdc267287e76", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "d99c505b6294f116f7b28d64aa85340a4a28a6a8623a0e7caaa50ea395e3dd96", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 498154, + "line_end": 498154, + "char_start": 5050221, + "char_end": 5050254, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 498154, + "line_end": 498154, + "char_start": 5050223, + "char_end": 5050227, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-89747d4ffc5c69ff0b90", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "378cff21247b0c9a21c7f32b9799540e1c7517b05552f669fe49d1206610f94a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 498406, + "line_end": 498406, + "char_start": 5052486, + "char_end": 5052507, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 498406, + "line_end": 498406, + "char_start": 5052488, + "char_end": 5052492, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8f66b9dc29fcccca3bcc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "8a358621730dc525a32e3e6a03942f51dbe950663f5afaa9cf3dfaf9ab1da98c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 504220, + "line_end": 504220, + "char_start": 5100365, + "char_end": 5100402, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 504220, + "line_end": 504220, + "char_start": 5100380, + "char_end": 5100384, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-69b1047bf6836315fc5f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "18bcd5f983e3f8cc1126389c590cdbad9834f28320666cef16ee34139f0cb6e0", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 505837, + "line_end": 505838, + "char_start": 5112891, + "char_end": 5112914, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 505837, + "line_end": 505837, + "char_start": 5112891, + "char_end": 5112895, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c69239cad88c4f6c0461", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "7ef30933173ea54bc4cb78041f2c50035115f5a09f4cf52c73e3179b9f470896", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 514456, + "line_end": 514456, + "char_start": 5188437, + "char_end": 5188472, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 514456, + "line_end": 514456, + "char_start": 5188448, + "char_end": 5188452, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-29fe5e1f8ec3f1b2b4ad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "2d8b8a77d5dc25d4512eacd4c7a4ad58785ed977a2969c422f0fd74020d3d3bf", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 515761, + "line_end": 515761, + "char_start": 5202874, + "char_end": 5202932, + "column_start": 0, + "column_end": 58 + }, + "surface_location": { + "line_start": 515761, + "line_end": 515761, + "char_start": 5202876, + "char_end": 5202880, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-89a60cedb8dbde120f7b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a02372e9af0886bd60bde3fc4ddb2068b91d267f97a8c5a077e6d6f9fc4bf5ce", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 518704, + "line_end": 518704, + "char_start": 5235526, + "char_end": 5235539, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 518704, + "line_end": 518704, + "char_start": 5235528, + "char_end": 5235534, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ec461e52699b2d496d58", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "22b9419fb77dadf8fd65a2d6842b8ab1ced515c8da00c0b8ce52c32d084caa40", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 524029, + "line_end": 524029, + "char_start": 5288890, + "char_end": 5288958, + "column_start": 0, + "column_end": 68 + }, + "surface_location": { + "line_start": 524029, + "line_end": 524029, + "char_start": 5288947, + "char_end": 5288953, + "column_start": 57, + "column_end": 63 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角轻轻勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-68d15115e1591e03d749", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a3f2ba3f1a898545917d3958981bb20b9a7b9899b5b8f80dae724aac0d70032f", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 528061, + "line_end": 528061, + "char_start": 5319966, + "char_end": 5319995, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 528061, + "line_end": 528061, + "char_start": 5319974, + "char_end": 5319978, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1005cba695da5e3b1162", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "a3c5b1ba572e1105b4d5761ae4ff9a78a1ef781307a8504430c391aaf7064a9c", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 529108, + "line_end": 529108, + "char_start": 5328362, + "char_end": 5328399, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 529108, + "line_end": 529108, + "char_start": 5328382, + "char_end": 5328388, + "column_start": 20, + "column_end": 26 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8681515e7562afd87481", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "aab617ab31f744e8a2912b389c86daf2997426c33074c6a54addff196421f965", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 529315, + "line_end": 529315, + "char_start": 5329977, + "char_end": 5330004, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 529315, + "line_end": 529315, + "char_start": 5329977, + "char_end": 5329981, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-adaaf2019cfdd0fc96dc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "excerpt_sha256": "f59f6b553d9597d2f5f55ece670eaf0364bc0da93f50bb54e780621739b4c78a", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "location": { + "line_start": 529798, + "line_end": 529798, + "char_start": 5333920, + "char_end": 5333957, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 529798, + "line_end": 529798, + "char_start": 5333952, + "char_end": 5333956, + "column_start": 32, + "column_end": 36 + }, + "work_ref": "ref:机破星河_当年离歌" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e6611623fdb554c60ad8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "9b2dfdf11cbe7b8dd36357bc4d704d089c6ef105938da3de03739a78f0f4f88f", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 25906, + "line_end": 25906, + "char_start": 227098, + "char_end": 227141, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 25906, + "line_end": 25906, + "char_start": 227118, + "char_end": 227124, + "column_start": 20, + "column_end": 26 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-449b971f6a691adc74a3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "2b318b4a84d3bee5e2a90a22cdc3a1f419572d67deadc469cdc802b5986738f9", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 34945, + "line_end": 34945, + "char_start": 307919, + "char_end": 307938, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 34945, + "line_end": 34945, + "char_start": 307922, + "char_end": 307928, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-94fe4fc9899c93259dd8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "808a70355f65c1edced4c0a2ad3048c6618eef746610a2a973b7033df9c2eaad", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 36409, + "line_end": 36409, + "char_start": 320449, + "char_end": 320478, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 36409, + "line_end": 36409, + "char_start": 320449, + "char_end": 320455, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-884c7684c32c9c4e1bab", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "7c1344c75b798fecf4f927b9268cc155cd3706fcc8e6f2e72b046977d2d719a1", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 39766, + "line_end": 39767, + "char_start": 350953, + "char_end": 351003, + "column_start": 108, + "column_end": 0 + }, + "surface_location": { + "line_start": 39766, + "line_end": 39766, + "char_start": 350953, + "char_end": 350959, + "column_start": 108, + "column_end": 114 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ee40372718a4f6719c40", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "b93ad02d5557033da794d96c4f3f960f2d8aba0f7156528c09cb0a97b91efbaa", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 102061, + "line_end": 102061, + "char_start": 885430, + "char_end": 885447, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 102061, + "line_end": 102061, + "char_start": 885432, + "char_end": 885440, + "column_start": 2, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一丝精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fec973d9bad780e93d91", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "879445c589a141f5bfee110116aebf6ab27ded1b23cf72aad548a8c2023e076f", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 115486, + "line_end": 115486, + "char_start": 992870, + "char_end": 992902, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 115486, + "line_end": 115486, + "char_start": 992872, + "char_end": 992878, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8cbe2bfbe63962809582", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "3fed5395fcab0593e93371e690d2e188a45dd866a4e7656d5a64c9a304989d44", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 115855, + "line_end": 115855, + "char_start": 995983, + "char_end": 996019, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 115855, + "line_end": 115855, + "char_start": 995995, + "char_end": 996001, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1460e1fd09ef5338bc64", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "4e9a7c591bca00ecc9768a2c9bb0a0d7c5c0e6a41541e054aa2b09763cbc5cb2", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 192790, + "line_end": 192790, + "char_start": 1613617, + "char_end": 1613654, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 192790, + "line_end": 192790, + "char_start": 1613618, + "char_end": 1613624, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-27ee6bf97dc3c60e33a9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "78ecfaccafdcf5e2e31ab50fb8cb8f185022774926d4b822be9a8763017d9b9d", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 199078, + "line_end": 199078, + "char_start": 1662107, + "char_end": 1662127, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 199078, + "line_end": 199078, + "char_start": 1662111, + "char_end": 1662117, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ce14c4941f013b57dd6a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "c6ccf72f35a4b52f5cfce2fa4a28b467b489f969106241e0486baa7407cd5490", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 226198, + "line_end": 226198, + "char_start": 1878275, + "char_end": 1878292, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 226198, + "line_end": 226198, + "char_start": 1878280, + "char_end": 1878286, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7f92799bafc2ed35c270", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "5388fdaa67d646eb1aa76dce10d4fd8b39d5448ee5408a57640d592562cb7abe", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 226783, + "line_end": 226783, + "char_start": 1883051, + "char_end": 1883067, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 226783, + "line_end": 226783, + "char_start": 1883053, + "char_end": 1883059, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c99e851ba3d8664f8df1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "e68b1beda8333b4d7a841e848e23dc0f09872cf2775ecebf540ce23d52ad62db", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 232966, + "line_end": 232966, + "char_start": 1929022, + "char_end": 1929043, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 232966, + "line_end": 232966, + "char_start": 1929024, + "char_end": 1929030, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-321e542b04bb17e8cb2f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "74198fc91753e4b42c4017934fc22e5294a6af1f9390b207a4cc566b0d673f4f", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 233176, + "line_end": 233176, + "char_start": 1931127, + "char_end": 1931157, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 233176, + "line_end": 233176, + "char_start": 1931135, + "char_end": 1931141, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3598365cd2d2b5621517", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "ec9f2aeef72d40972934742d8362b1ea93118bfd5b2469d9827ab53517f2223c", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 233674, + "line_end": 233674, + "char_start": 1935036, + "char_end": 1935097, + "column_start": 0, + "column_end": 61 + }, + "surface_location": { + "line_start": 233674, + "line_end": 233674, + "char_start": 1935074, + "char_end": 1935080, + "column_start": 38, + "column_end": 44 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b006f5f25fb0efbdb4a5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "18be83b4731e84f53f891d01c15e98838091301244c9e9dfa323ea65d2e39d13", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 234139, + "line_end": 234139, + "char_start": 1938737, + "char_end": 1938767, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 234139, + "line_end": 234139, + "char_start": 1938746, + "char_end": 1938752, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-49a4f0f4cece94868071", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "a488041b18b95552e6d78baa3ccf0008032d85a5c2b2ae1927214050d3777466", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 234352, + "line_end": 234352, + "char_start": 1940269, + "char_end": 1940300, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 234352, + "line_end": 234352, + "char_start": 1940271, + "char_end": 1940277, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-127723ef12ba6cb9a725", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "1b9d66d7a83bb2326021552acef3b305196acb14cc263eaf175ab673f01e2407", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 237358, + "line_end": 237358, + "char_start": 1963728, + "char_end": 1963768, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 237358, + "line_end": 237358, + "char_start": 1963732, + "char_end": 1963738, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-668b49def402c74dcaf2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "b9c6baa0f1b5d3394e36282df8bae8329f317afd764035d32a00e36da2a78fee", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 240766, + "line_end": 240766, + "char_start": 1989757, + "char_end": 1989800, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 240766, + "line_end": 240766, + "char_start": 1989764, + "char_end": 1989768, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-28f3d3d370e3469727ad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "92a47e5e7dad5284321c80910651e1c12c2b42b9f98e93fa482fd0e1c6ef4ea5", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 241030, + "line_end": 241030, + "char_start": 1991632, + "char_end": 1991673, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 241030, + "line_end": 241030, + "char_start": 1991639, + "char_end": 1991645, + "column_start": 7, + "column_end": 13 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6998e228d86782af25dd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "d7dd37cbea67bd6e357499b14a79d37d9e75558c01635ede0a943c6c57c94606", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 250471, + "line_end": 250471, + "char_start": 2065325, + "char_end": 2065353, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 250471, + "line_end": 250471, + "char_start": 2065327, + "char_end": 2065333, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-983dd4cdd451b171c469", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "f287f0556a265df95afde659c1d45d0fd1656859ae84328acd73f5e870a8cea5", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 251440, + "line_end": 251440, + "char_start": 2073251, + "char_end": 2073272, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 251440, + "line_end": 251440, + "char_start": 2073253, + "char_end": 2073259, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-506379ac50124239be55", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "3cca8a38eead35450339131d6c95a7ba14d6822f463075ccef185239eff261b6", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 253438, + "line_end": 253438, + "char_start": 2088248, + "char_end": 2088289, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 253438, + "line_end": 253438, + "char_start": 2088249, + "char_end": 2088255, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7ce840f4069248cfca44", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "5ea752ad343bab204e96b71c65950a6247407183306068db366d3cfc700b31ca", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 276709, + "line_end": 276709, + "char_start": 2267835, + "char_end": 2267888, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 276709, + "line_end": 276709, + "char_start": 2267838, + "char_end": 2267844, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a992da75a1813d54b0a6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "962e7ecd5a308e02ae261fd0f1a84f61e3b7045e2bb422e19a73611a9d74c918", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 285151, + "line_end": 285151, + "char_start": 2335226, + "char_end": 2335275, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 285151, + "line_end": 285151, + "char_start": 2335237, + "char_end": 2335243, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bbe84972321ed7a069e4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "40d3bf74daf4458cba7159079a99d03783f539928005b24e9102e0f3ce17f97b", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 286819, + "line_end": 286819, + "char_start": 2347990, + "char_end": 2348027, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 286819, + "line_end": 286819, + "char_start": 2347993, + "char_end": 2347999, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-efe938cfebde3c14ef06", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "ceb20cb1e974c2ec6e6145a16bbd834d4fd916d2ad3949db6db7b02062208554", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 310315, + "line_end": 310315, + "char_start": 2535280, + "char_end": 2535301, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 310315, + "line_end": 310315, + "char_start": 2535294, + "char_end": 2535300, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e2b5fefd83a47adabcc3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "5c857b2fbc903237bb7d91f0c07a0452a7aa85d2a48f5d3c25f8a083f6ca1311", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 315223, + "line_end": 315223, + "char_start": 2572987, + "char_end": 2573014, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 315223, + "line_end": 315223, + "char_start": 2573009, + "char_end": 2573013, + "column_start": 22, + "column_end": 26 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1313b56d8a7c796db5b2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "44c7c7bd729526d167f589a1f5d780e81399d83eed017281c73e70f9d2cfdab1", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 316258, + "line_end": 316258, + "char_start": 2581813, + "char_end": 2581843, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 316258, + "line_end": 316258, + "char_start": 2581838, + "char_end": 2581842, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-982196bb13366be180df", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "5971728195ab4af6844299f543535d2f21a9042496d36e3c37a7085a83ee2a0b", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 319816, + "line_end": 319816, + "char_start": 2610621, + "char_end": 2610652, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 319816, + "line_end": 319816, + "char_start": 2610625, + "char_end": 2610631, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8f1fb26a4fab8bf306ef", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "c7d0a16b9c9ea6f98e2801ff70d4ea4ec3dd867f3090afa5b71b4237a7cc4fcb", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 321529, + "line_end": 321529, + "char_start": 2624463, + "char_end": 2624500, + "column_start": 0, + "column_end": 37 + }, + "surface_location": { + "line_start": 321529, + "line_end": 321529, + "char_start": 2624466, + "char_end": 2624472, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-27fd2aabf3305f0fd927", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "f6a8cbd45c9f60ab7fd398c276f8f06bd641d3e2e8f52ce61fabfb070b553656", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 322525, + "line_end": 322525, + "char_start": 2632156, + "char_end": 2632201, + "column_start": 0, + "column_end": 45 + }, + "surface_location": { + "line_start": 322525, + "line_end": 322525, + "char_start": 2632168, + "char_end": 2632174, + "column_start": 12, + "column_end": 18 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fb82a24f6bb4dd98d25b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "84436c7eca256b38bcb3b0c1b8a00569d397356e55de351f9bd985911af5ba16", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 323572, + "line_end": 323572, + "char_start": 2640556, + "char_end": 2640592, + "column_start": 0, + "column_end": 36 + }, + "surface_location": { + "line_start": 323572, + "line_end": 323572, + "char_start": 2640564, + "char_end": 2640570, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d44eefdb19c1180f3a70", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "a83048830baa624e50e132166bb6dc8275c260d15c6c2f0b6f655d47826cb38d", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 323686, + "line_end": 323686, + "char_start": 2641441, + "char_end": 2641492, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 323686, + "line_end": 323686, + "char_start": 2641445, + "char_end": 2641451, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c095e1786909b0ac15b7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "ba7170cc0515858be675136682cf4f172e7114183a623f1471ca1b0448ebd329", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 358087, + "line_end": 358087, + "char_start": 2920265, + "char_end": 2920309, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 358087, + "line_end": 358087, + "char_start": 2920270, + "char_end": 2920276, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-51a52132363c149ad57b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "4e754afeb7d63a39a92872f4e88cb9d09058660dd525970253d3ca08e630c8a0", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 374179, + "line_end": 374179, + "char_start": 3050698, + "char_end": 3050723, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 374179, + "line_end": 374179, + "char_start": 3050706, + "char_end": 3050712, + "column_start": 8, + "column_end": 14 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f1d3e2f48009293bb436", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "bae0d89247548525750c543c5b7e54b1407958b255f3bfc8409d59d52567e06a", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 378643, + "line_end": 378643, + "char_start": 3086484, + "char_end": 3086516, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 378643, + "line_end": 378643, + "char_start": 3086500, + "char_end": 3086506, + "column_start": 16, + "column_end": 22 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5a7176069f4fbb304c61", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "6dd5622eca0f6bcf96cec09fc81bb8885bb692c2e1812e4159f06176d86eb3a0", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 379375, + "line_end": 379375, + "char_start": 3092202, + "char_end": 3092213, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 379375, + "line_end": 379375, + "char_start": 3092205, + "char_end": 3092211, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0afe6db0c709354be341", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "b62cb486345e0d8e699b5063647aa4748527852db5e758e6b63dc823ea86157b", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 379465, + "line_end": 379465, + "char_start": 3092848, + "char_end": 3092870, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 379465, + "line_end": 379465, + "char_start": 3092851, + "char_end": 3092857, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ccdbe51578230df7c6cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "03bea74217d7c516fd01c41348768d58b22c9ad0dd5efa4c8a71bf4f5de5b865", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 380344, + "line_end": 380344, + "char_start": 3099112, + "char_end": 3099145, + "column_start": 0, + "column_end": 33 + }, + "surface_location": { + "line_start": 380344, + "line_end": 380344, + "char_start": 3099136, + "char_end": 3099142, + "column_start": 24, + "column_end": 30 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ad486739ae14d97af4d7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "4d0e5850eb45b01680e31835d8d0059df6ef941cf869d146f0c7fd09862ae89a", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 381253, + "line_end": 381253, + "char_start": 3106512, + "char_end": 3106535, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 381253, + "line_end": 381253, + "char_start": 3106517, + "char_end": 3106523, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c2e48ecd1e6b83948374", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "cb7c7e2f7453d9ba6fd8085a8d737dd06ced8fe06505877cd73583bb8b275b7d", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 381727, + "line_end": 381727, + "char_start": 3110615, + "char_end": 3110637, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 381727, + "line_end": 381727, + "char_start": 3110618, + "char_end": 3110624, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-79b22217b294b7245189", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "5ff694eea79b2a26edd83861e5dfb9b1b7e894d1ab0ef145554d838c97b2be42", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 382204, + "line_end": 382204, + "char_start": 3114314, + "char_end": 3114363, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 382204, + "line_end": 382204, + "char_start": 3114323, + "char_end": 3114329, + "column_start": 9, + "column_end": 15 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5af2290d18f1f66942ac", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "9b13161c63fb6c87844e0c2b5c48b3b409c543372d09b7384c8ea2bd637f2887", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 382552, + "line_end": 382552, + "char_start": 3116905, + "char_end": 3116939, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 382552, + "line_end": 382552, + "char_start": 3116910, + "char_end": 3116914, + "column_start": 5, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-920ed053f3931cea0364", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "cf11d7be0aaba07310b15c9ac71bfcd89c049bbb864c92d41b522fed9b7ac18a", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 383527, + "line_end": 383527, + "char_start": 3124408, + "char_end": 3124440, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 383527, + "line_end": 383527, + "char_start": 3124411, + "char_end": 3124417, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-6c39e65d425b174456bb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "41534b9669734eaba52b31ba8fa76ea37dd8cb217f7dc33b93da572ed45c18be", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 383698, + "line_end": 383698, + "char_start": 3125882, + "char_end": 3125922, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 383698, + "line_end": 383698, + "char_start": 3125887, + "char_end": 3125893, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fb80c3c3a3c3ee82d986", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "d4fc518c0cf559e6929e9a332f9795b85bb1c5ce2f8373eb22dae8db2ca41a57", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 384634, + "line_end": 384634, + "char_start": 3133444, + "char_end": 3133470, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 384634, + "line_end": 384634, + "char_start": 3133447, + "char_end": 3133453, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-186948b122a36fa148a7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "36972e06c5e9626b75c2320e4de3546b92320385c9073ac90b8894e8fb4c5ea4", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 386026, + "line_end": 386026, + "char_start": 3144347, + "char_end": 3144385, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 386026, + "line_end": 386026, + "char_start": 3144358, + "char_end": 3144364, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ec83fa8c2a3d9b562077", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "b91f8b11364cfa0892c2155fb389c6f72473132755cce97621effc5e4044bb14", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 389818, + "line_end": 389818, + "char_start": 3174553, + "char_end": 3174582, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 389818, + "line_end": 389818, + "char_start": 3174577, + "char_end": 3174581, + "column_start": 24, + "column_end": 28 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-99d6e0b73022c2197240", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "3da12038e5429045a5fe3626ec5daa0ba840f8cbc8204cb733b20a53c48af431", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 413536, + "line_end": 413536, + "char_start": 3359305, + "char_end": 3359330, + "column_start": 0, + "column_end": 25 + }, + "surface_location": { + "line_start": 413536, + "line_end": 413536, + "char_start": 3359316, + "char_end": 3359322, + "column_start": 11, + "column_end": 17 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5945f0428bcca631f457", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "3ade743aee6ff18b760748abdbb0a58af831eee298a80c198442a450271c9c72", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 419881, + "line_end": 419881, + "char_start": 3407563, + "char_end": 3407602, + "column_start": 0, + "column_end": 39 + }, + "surface_location": { + "line_start": 419881, + "line_end": 419881, + "char_start": 3407584, + "char_end": 3407590, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-343212960d95148e9677", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "d79fd473988ec541593de7b67f15f36b9a9d55326faef000796e49454e8586aa", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 421186, + "line_end": 421186, + "char_start": 3417248, + "char_end": 3417292, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 421186, + "line_end": 421186, + "char_start": 3417253, + "char_end": 3417259, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5fd334da2e6f3a96d6b7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "3770eaaca68a8c1cce64c339a677814a358a27d0db2e3b1ee04b1cd0d6fedc0c", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 426157, + "line_end": 426157, + "char_start": 3453628, + "char_end": 3453655, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 426157, + "line_end": 426157, + "char_start": 3453629, + "char_end": 3453635, + "column_start": 1, + "column_end": 7 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-78241989da0df7b40ea9", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "cfa47ad9904b5602a5e446ec3f48de29d73cd68cca97cc1c3db11a4d6c969cf4", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 442198, + "line_end": 442198, + "char_start": 3582945, + "char_end": 3583008, + "column_start": 0, + "column_end": 63 + }, + "surface_location": { + "line_start": 442198, + "line_end": 442198, + "char_start": 3582994, + "char_end": 3583000, + "column_start": 49, + "column_end": 55 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a19415929810d195c7cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "949b58cdea1959c0476253b73825eb531ee4333efa6f25bc14cf887febd90903", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 447337, + "line_end": 447337, + "char_start": 3622311, + "char_end": 3622351, + "column_start": 0, + "column_end": 40 + }, + "surface_location": { + "line_start": 447337, + "line_end": 447337, + "char_start": 3622315, + "char_end": 3622321, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8962e30e17c9e423827c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "b5877a1cdc229ebbd026c1dd2df5d24ab77969665f2a9f8fa4d0d02337665f7e", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 448387, + "line_end": 448387, + "char_start": 3630336, + "char_end": 3630363, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 448387, + "line_end": 448387, + "char_start": 3630340, + "char_end": 3630346, + "column_start": 4, + "column_end": 10 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ba4f07ca6999a5544288", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "823b8c20316207f583997aee59bb43133b46d70a0f79ea260823ae01f8ab22dc", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 449524, + "line_end": 449524, + "char_start": 3638623, + "char_end": 3638645, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 449524, + "line_end": 449524, + "char_start": 3638625, + "char_end": 3638631, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-10b48ac6d1c0b698f11d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "2d12c31bcd0bde7967160937eef6deb633ec48d16b97df950823477d8883241b", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 451168, + "line_end": 451168, + "char_start": 3651515, + "char_end": 3651532, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 451168, + "line_end": 451168, + "char_start": 3651518, + "char_end": 3651524, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ba9c03f9a2b7052e8edc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "91e6cdefc28a5de1505d6c97cb2949e444d2f466ddcd28833899eb5457e0b409", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 451699, + "line_end": 451699, + "char_start": 3655213, + "char_end": 3655243, + "column_start": 0, + "column_end": 30 + }, + "surface_location": { + "line_start": 451699, + "line_end": 451699, + "char_start": 3655216, + "char_end": 3655222, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-26699d6c9a956755d57d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "83afc8fa2606a6a67ef76dafee80ec8b86beb1d0c191ea9782a29f6cf835f9d0", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 478792, + "line_end": 478792, + "char_start": 3868597, + "char_end": 3868628, + "column_start": 0, + "column_end": 31 + }, + "surface_location": { + "line_start": 478792, + "line_end": 478792, + "char_start": 3868600, + "char_end": 3868606, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-92e1e22b1e0d5b18004e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "8bf678ee6091caa5a875c94618d536c000dddeecb144315f7c1581e1fcbb588a", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 479329, + "line_end": 479329, + "char_start": 3873147, + "char_end": 3873186, + "column_start": 0, + "column_end": 39 + }, + "surface_location": { + "line_start": 479329, + "line_end": 479329, + "char_start": 3873150, + "char_end": 3873156, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9c20458f14c39f0f5116", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "excerpt_sha256": "6a1f96b9ce439eebcf713e48a0ae607bacb086eccba42f47c3d250ea14300995", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "location": { + "line_start": 480010, + "line_end": 480010, + "char_start": 3878768, + "char_end": 3878809, + "column_start": 0, + "column_end": 41 + }, + "surface_location": { + "line_start": 480010, + "line_end": 480010, + "char_start": 3878771, + "char_end": 3878777, + "column_start": 3, + "column_end": 9 + }, + "work_ref": "ref:深空之影_虚伪王庭" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-857ce58e56901fe328c1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "3eb4e7b2162db1b88486c56f640bc272b052b50d948e3980959f8858d1bcdd1f", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 8713, + "line_end": 8713, + "char_start": 118967, + "char_end": 119016, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 8713, + "line_end": 8713, + "char_start": 118984, + "char_end": 118988, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4c7397f0c006e090812d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0f9e3f0887de969e09f0c6c91ea986516cbf84092e0122f675861b238a741e85", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 23167, + "line_end": 23167, + "char_start": 311342, + "char_end": 311361, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 23167, + "line_end": 23167, + "char_start": 311356, + "char_end": 311360, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0c954571cfdbd6c91e49", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5294166e133c90fafb5c27553cdedfa51b2920733fbda44ca264d6c6bb246a91", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 23194, + "line_end": 23200, + "char_start": 311779, + "char_end": 311959, + "column_start": 78, + "column_end": 103 + }, + "surface_location": { + "line_start": 23200, + "line_end": 23200, + "char_start": 311869, + "char_end": 311873, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9fcbeff0b5316aa7fb80", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "256090870555b9deec49043b27590e4e21b493ff374e4a4eccf275cdfa9161c0", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 29317, + "line_end": 29317, + "char_start": 398793, + "char_end": 398846, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 29317, + "line_end": 29317, + "char_start": 398820, + "char_end": 398824, + "column_start": 27, + "column_end": 31 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b44deb7e1b89ef6c6a37", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c483cbecaf72f8691fa5f2098906da0c2c3844c8521ad74e3648754f333d9773", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 37051, + "line_end": 37051, + "char_start": 520328, + "char_end": 520432, + "column_start": 0, + "column_end": 104 + }, + "surface_location": { + "line_start": 37051, + "line_end": 37051, + "char_start": 520370, + "char_end": 520374, + "column_start": 42, + "column_end": 46 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-278c677fdf023e015e9a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "45544bf03529a9900bedf5af5d4cc1f0296e2533e37ea27fb6083e35112b6c16", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 37780, + "line_end": 37780, + "char_start": 530769, + "char_end": 530863, + "column_start": 0, + "column_end": 94 + }, + "surface_location": { + "line_start": 37780, + "line_end": 37780, + "char_start": 530791, + "char_end": 530797, + "column_start": 22, + "column_end": 28 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ec60e34b8d434e8002fa", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "2a1240742cbbbeabea2cf3d4ef7d6d93335352a5fdabaf4d237de206db8c6390", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 41176, + "line_end": 41176, + "char_start": 579103, + "char_end": 579187, + "column_start": 0, + "column_end": 84 + }, + "surface_location": { + "line_start": 41176, + "line_end": 41176, + "char_start": 579103, + "char_end": 579109, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d908f3c7dc6be1dc982e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "4441989641137cd3188a12647c90c90f8d324065e10fefc699059174bc107c1a", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 45286, + "line_end": 45286, + "char_start": 640658, + "char_end": 640710, + "column_start": 0, + "column_end": 52 + }, + "surface_location": { + "line_start": 45286, + "line_end": 45286, + "char_start": 640705, + "char_end": 640709, + "column_start": 47, + "column_end": 51 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bb0e9ff894b7bfee6243", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5914ab4ac97a7bcb3cfeff131e53e355b48b20597ebbb9449f70e41a71956872", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 51004, + "line_end": 51004, + "char_start": 724196, + "char_end": 724257, + "column_start": 0, + "column_end": 61 + }, + "surface_location": { + "line_start": 51004, + "line_end": 51004, + "char_start": 724234, + "char_end": 724238, + "column_start": 38, + "column_end": 42 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-184f186d0e2ffb07860e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "cd89378f59e8954384ba6352302015d53240cc5020a3768c3ce64ca3f40f11af", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 54886, + "line_end": 54886, + "char_start": 779402, + "char_end": 779431, + "column_start": 0, + "column_end": 29 + }, + "surface_location": { + "line_start": 54886, + "line_end": 54886, + "char_start": 779412, + "char_end": 779416, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-76423f80e98a35dd7ca0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "82db2b6826f3fd02e03fbcfaa8999f31abb04d8aa7393ca45bf74d69932d8bf3", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 54901, + "line_end": 54901, + "char_start": 779673, + "char_end": 779743, + "column_start": 0, + "column_end": 70 + }, + "surface_location": { + "line_start": 54901, + "line_end": 54901, + "char_start": 779691, + "char_end": 779695, + "column_start": 18, + "column_end": 22 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a9ad2921991dd528f521", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "cc90f0085cf7ac7e057d4a55ece749a97c3d723cd0811a1aaa35fc36444a519b", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 56749, + "line_end": 56749, + "char_start": 810083, + "char_end": 810160, + "column_start": 0, + "column_end": 77 + }, + "surface_location": { + "line_start": 56749, + "line_end": 56749, + "char_start": 810136, + "char_end": 810140, + "column_start": 53, + "column_end": 57 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fe1964fc96784bf9d553", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "1fadbf75fe46f2c6caddede63564454e13d4e3867a326e519ccd4c792aadabc5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 57187, + "line_end": 57187, + "char_start": 816941, + "char_end": 816953, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 57187, + "line_end": 57187, + "char_start": 816943, + "char_end": 816947, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-26a1be82c6200170ef99", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "53f47733a081c082817d42d887a54202d9f4f3182bea905c9f098f66bb2b809c", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 61849, + "line_end": 61849, + "char_start": 887209, + "char_end": 887231, + "column_start": 7, + "column_end": 29 + }, + "surface_location": { + "line_start": 61849, + "line_end": 61849, + "char_start": 887212, + "char_end": 887218, + "column_start": 10, + "column_end": 16 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过异彩", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-84de968407b83988f413", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "2dafa3d3334c8dffef9ed43d5259bf4770d34e9ad37218033cbad7125d987f79", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 65458, + "line_end": 65458, + "char_start": 949874, + "char_end": 949886, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 65458, + "line_end": 65458, + "char_start": 949879, + "char_end": 949885, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-926e5cd7957558fa908c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0a87a31d7ab4c59a97fc4046eb9366b373b4c6bed0777740cfc49b923a075948", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 68752, + "line_end": 68752, + "char_start": 1008237, + "char_end": 1008283, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 68752, + "line_end": 68752, + "char_start": 1008253, + "char_end": 1008257, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f3c22a5b640ad4d1c9c7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "627fb187bed859696707c9331c4a871909082b837342c7ced5a91f889499a09a", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 72835, + "line_end": 72835, + "char_start": 1073311, + "char_end": 1073335, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 72835, + "line_end": 72835, + "char_start": 1073311, + "char_end": 1073315, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-60ccf052c615b10bdd13", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "a41d46188a78e0a77493fe23297ec54c1881e8befd78c44a2b62715d6fa5b1ad", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 74275, + "line_end": 74281, + "char_start": 1093996, + "char_end": 1094176, + "column_start": 21, + "column_end": 91 + }, + "surface_location": { + "line_start": 74281, + "line_end": 74281, + "char_start": 1094086, + "char_end": 1094090, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c7979b97ba01344fb47d", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "75a1f0d2cd8dd4c3c38634d7095cf2e4cf31037b4e1fcc14158cc8013da3cb73", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 74926, + "line_end": 74927, + "char_start": 1105040, + "char_end": 1105060, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 74926, + "line_end": 74926, + "char_start": 1105048, + "char_end": 1105052, + "column_start": 8, + "column_end": 12 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9f611f9780b9ea0c3111", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d3654db57f234193badce4cc0197a93395a462830a2030feb57eebd92ad734ed", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 77320, + "line_end": 77320, + "char_start": 1143630, + "char_end": 1143643, + "column_start": 0, + "column_end": 13 + }, + "surface_location": { + "line_start": 77320, + "line_end": 77320, + "char_start": 1143631, + "char_end": 1143635, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-17363e98cf3d9933edcc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "4f475fe7f657c2df40c5d88a99daa8efafb401f79985bc93ecb679963a38ff29", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 79765, + "line_end": 79765, + "char_start": 1187883, + "char_end": 1187983, + "column_start": 0, + "column_end": 100 + }, + "surface_location": { + "line_start": 79765, + "line_end": 79765, + "char_start": 1187929, + "char_end": 1187935, + "column_start": 46, + "column_end": 52 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-62d684fe8ac09fd000a2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5cbbb420de0a3f1d87846bc7a4af70c2725c81a12b8329f6be0356b928ba3f6a", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 83395, + "line_end": 83395, + "char_start": 1247773, + "char_end": 1247833, + "column_start": 0, + "column_end": 60 + }, + "surface_location": { + "line_start": 83395, + "line_end": 83395, + "char_start": 1247786, + "char_end": 1247790, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-82d5030d18647b63495c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e85258a9d7686aad2413470777f4c6a8b8079266a1b27d1bd6a71f5e886e87ab", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 83494, + "line_end": 83494, + "char_start": 1249591, + "char_end": 1249711, + "column_start": 0, + "column_end": 120 + }, + "surface_location": { + "line_start": 83494, + "line_end": 83494, + "char_start": 1249689, + "char_end": 1249693, + "column_start": 98, + "column_end": 102 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4a1d87c4c5c862003a6f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "fd4f19560dcda180223d4068547904e31ac56d959d1258b8d9e5a245ba1a3c08", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 83503, + "line_end": 83503, + "char_start": 1249753, + "char_end": 1249806, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 83503, + "line_end": 83503, + "char_start": 1249783, + "char_end": 1249787, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9dced3ba391c76acbb4b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e3b6040de2b7e15034a620b7cb1d5c7cd2237fe08403e85ced9f36f747aa78ff", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 84145, + "line_end": 84145, + "char_start": 1259025, + "char_end": 1259142, + "column_start": 0, + "column_end": 117 + }, + "surface_location": { + "line_start": 84145, + "line_end": 84145, + "char_start": 1259025, + "char_end": 1259031, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8745ec0f4b4b8117eb94", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "8e0a6662875e6a80cc94dcd20095b9113b9620529b7728a6bf9f1c68d7f38cb3", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 85537, + "line_end": 85537, + "char_start": 1280380, + "char_end": 1280441, + "column_start": 0, + "column_end": 61 + }, + "surface_location": { + "line_start": 85537, + "line_end": 85537, + "char_start": 1280393, + "char_end": 1280397, + "column_start": 13, + "column_end": 17 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-24a106d57b25a5b38bbf", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "acd8f875ae9167f7688a883e20584a91438cb18bc49a0a828498a80b7e60ea0b", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 85633, + "line_end": 85633, + "char_start": 1282201, + "char_end": 1282324, + "column_start": 0, + "column_end": 123 + }, + "surface_location": { + "line_start": 85633, + "line_end": 85633, + "char_start": 1282300, + "char_end": 1282304, + "column_start": 99, + "column_end": 103 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-60763079415d174a265b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "fd4f19560dcda180223d4068547904e31ac56d959d1258b8d9e5a245ba1a3c08", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 85642, + "line_end": 85642, + "char_start": 1282366, + "char_end": 1282419, + "column_start": 0, + "column_end": 53 + }, + "surface_location": { + "line_start": 85642, + "line_end": 85642, + "char_start": 1282396, + "char_end": 1282400, + "column_start": 30, + "column_end": 34 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ba2eb96fa75e230bfedc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e3b6040de2b7e15034a620b7cb1d5c7cd2237fe08403e85ced9f36f747aa78ff", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 86278, + "line_end": 86278, + "char_start": 1291729, + "char_end": 1291846, + "column_start": 0, + "column_end": 117 + }, + "surface_location": { + "line_start": 86278, + "line_end": 86278, + "char_start": 1291729, + "char_end": 1291735, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2e1f86b35007de382e8f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "b0c07dee328400adb186bc54911a857fb27196d7133547cbe82e93954578f83d", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 87310, + "line_end": 87310, + "char_start": 1307142, + "char_end": 1307157, + "column_start": 11, + "column_end": 26 + }, + "surface_location": { + "line_start": 87310, + "line_end": 87310, + "char_start": 1307150, + "char_end": 1307154, + "column_start": 19, + "column_end": 23 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-2b9e85b48a1e9af5ba13", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "9493b4a3ff56b6fc7ae08f94f6f4dc4ba2330ec76ce540573fa19615a33e125f", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 88801, + "line_end": 88801, + "char_start": 1330233, + "char_end": 1330276, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 88801, + "line_end": 88801, + "char_start": 1330233, + "char_end": 1330239, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-cca6f21a621d67af88c1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0e7aa437c7302bdef8e2d33153b5345b058b52df8b89e6a863fd4388b915819c", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 98110, + "line_end": 98110, + "char_start": 1478635, + "char_end": 1478691, + "column_start": 109, + "column_end": 165 + }, + "surface_location": { + "line_start": 98110, + "line_end": 98110, + "char_start": 1478672, + "char_end": 1478676, + "column_start": 146, + "column_end": 150 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-e13cb411697f24a79921", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "eac8f593acf5a4c9921390196ba905f0469cfae8c9871f53db09ead080c2e868", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 98836, + "line_end": 98836, + "char_start": 1489589, + "char_end": 1489627, + "column_start": 0, + "column_end": 38 + }, + "surface_location": { + "line_start": 98836, + "line_end": 98836, + "char_start": 1489610, + "char_end": 1489614, + "column_start": 21, + "column_end": 25 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1d5ce876de86b3e17137", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d61768c42fe3ab467b8544433fd5715fe3f481b0ecc067bf25ef4e02c73de79e", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 110422, + "line_end": 110422, + "char_start": 1678803, + "char_end": 1678829, + "column_start": 13, + "column_end": 39 + }, + "surface_location": { + "line_start": 110422, + "line_end": 110422, + "char_start": 1678814, + "char_end": 1678818, + "column_start": 24, + "column_end": 28 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-77e28fe836558e8b335e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "7148e28eb8635898d7cba5fcf1be37496ac2ee4b8510ba9eed2ff76d611a620f", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 111148, + "line_end": 111148, + "char_start": 1690262, + "char_end": 1690289, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 111148, + "line_end": 111148, + "char_start": 1690264, + "char_end": 1690268, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-faa01b974e4878e8fc0f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "43df12108cd63f49757eba10894c7abda569799af0d899b0c0561acfec2be752", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 113353, + "line_end": 113353, + "char_start": 1719716, + "char_end": 1719732, + "column_start": 12, + "column_end": 28 + }, + "surface_location": { + "line_start": 113353, + "line_end": 113353, + "char_start": 1719727, + "char_end": 1719731, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-10a325d54924cf1a95a4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c04ca8a093ac97ba4fde6e8893dc5660ee4f743f62062ab844a1ec4116c4b51d", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 114730, + "line_end": 114730, + "char_start": 1737772, + "char_end": 1737804, + "column_start": 0, + "column_end": 32 + }, + "surface_location": { + "line_start": 114730, + "line_end": 114730, + "char_start": 1737782, + "char_end": 1737786, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7844997985205594cac3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "665116b69f2df42387e1911cd40b8265169034f20d238f6bb12fa618f75e9225", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 117157, + "line_end": 117157, + "char_start": 1770025, + "char_end": 1770048, + "column_start": 0, + "column_end": 23 + }, + "surface_location": { + "line_start": 117157, + "line_end": 117157, + "char_start": 1770030, + "char_end": 1770036, + "column_start": 5, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角微微勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8480478ba0272403e96f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "37ca90bed5a707b865b3301cf6a4c94484077e9b798fbef7c17ce70c74bd48e5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 124318, + "line_end": 124318, + "char_start": 1867850, + "char_end": 1868003, + "column_start": 0, + "column_end": 153 + }, + "surface_location": { + "line_start": 124318, + "line_end": 124318, + "char_start": 1867945, + "char_end": 1867951, + "column_start": 95, + "column_end": 101 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-06011a827e929f28f9bd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "ecab8c01a88c991f926f119228483dfc875b4bd6aabc884f1e5b918f78d89ac3", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 125176, + "line_end": 125177, + "char_start": 1882585, + "char_end": 1882675, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 125176, + "line_end": 125176, + "char_start": 1882657, + "char_end": 1882661, + "column_start": 72, + "column_end": 76 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7d2e6a3f508b603ffd95", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "ae4148af21123776eeb773dd39f5012ab88704beb4cddd7d8b11618d8fc180b5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 132340, + "line_end": 132340, + "char_start": 1984485, + "char_end": 1984502, + "column_start": 0, + "column_end": 17 + }, + "surface_location": { + "line_start": 132340, + "line_end": 132340, + "char_start": 1984494, + "char_end": 1984498, + "column_start": 9, + "column_end": 13 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f299f5beb230e1e96a0e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "999c83b5a6516d326e66026e30126fe46143c48115783378afad7801e1f11d15", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 132523, + "line_end": 132523, + "char_start": 1987466, + "char_end": 1987583, + "column_start": 0, + "column_end": 117 + }, + "surface_location": { + "line_start": 132523, + "line_end": 132523, + "char_start": 1987499, + "char_end": 1987505, + "column_start": 33, + "column_end": 39 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8baac57b6ef1b1630785", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e5299d5796a004a19091ac7216b63da3a69baa2af854f5b32275c2c3fc4c4de0", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 133579, + "line_end": 133579, + "char_start": 2003850, + "char_end": 2003977, + "column_start": 0, + "column_end": 127 + }, + "surface_location": { + "line_start": 133579, + "line_end": 133579, + "char_start": 2003920, + "char_end": 2003924, + "column_start": 70, + "column_end": 74 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4fe57aa4cd9a05045ee8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "fd1e467bc31ac2fd67cae306722cd4473dd7c7a83f591f575a519816dc5d4832", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 150391, + "line_end": 150391, + "char_start": 2246375, + "char_end": 2246403, + "column_start": 0, + "column_end": 28 + }, + "surface_location": { + "line_start": 150391, + "line_end": 150391, + "char_start": 2246382, + "char_end": 2246386, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-02828d08a593171fac85", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0a8bae9ce1417957aa2d71f4fabfb4a0231a5e02fa02bc42c1938b62805b3ff8", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 163798, + "line_end": 163798, + "char_start": 2436190, + "char_end": 2436271, + "column_start": 0, + "column_end": 81 + }, + "surface_location": { + "line_start": 163798, + "line_end": 163798, + "char_start": 2436200, + "char_end": 2436204, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0e1d024dd0eae08ae37b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d29401465e8b4794450749dbd6b2bb20e4053857974bec0a25a9eba75d133173", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 173746, + "line_end": 173746, + "char_start": 2578643, + "char_end": 2578725, + "column_start": 9, + "column_end": 91 + }, + "surface_location": { + "line_start": 173746, + "line_end": 173746, + "char_start": 2578651, + "char_end": 2578655, + "column_start": 17, + "column_end": 21 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c8c0438542f120724c55", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "ced88e0b6d3ddb35d99b8765dd6d777327f1a6760b019cc0d5f44d83dfc16d29", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 176842, + "line_end": 176842, + "char_start": 2627265, + "char_end": 2627277, + "column_start": 0, + "column_end": 12 + }, + "surface_location": { + "line_start": 176842, + "line_end": 176842, + "char_start": 2627267, + "char_end": 2627271, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fd90cf522e71065e8675", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "02ba16df51ab55a8ddfbdb6424344d3122cedc830a74cc56ce45d4a5f9b936c9", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 187024, + "line_end": 187024, + "char_start": 2786089, + "char_end": 2786123, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 187024, + "line_end": 187024, + "char_start": 2786103, + "char_end": 2786107, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d9d26f9c051e351f273f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5e5cb3f167c45b1233f133e1ec74838302adcd4e3785145ade36aa49b765b361", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 189595, + "line_end": 189595, + "char_start": 2823367, + "char_end": 2823424, + "column_start": 0, + "column_end": 57 + }, + "surface_location": { + "line_start": 189595, + "line_end": 189595, + "char_start": 2823415, + "char_end": 2823423, + "column_start": 48, + "column_end": 56 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-1ffa7187b45a39c4b191", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "7305b7617dff6b91813859559bbabb0c49fcd72e1d90fb8d00480e0e442bae48", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 189874, + "line_end": 189874, + "char_start": 2827813, + "char_end": 2827868, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 189874, + "line_end": 189874, + "char_start": 2827850, + "char_end": 2827854, + "column_start": 37, + "column_end": 41 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bd6d2074bba2e9b98d95", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "7060ed9d55740b997b2d97ff9a02775209a178cd676f531e716c1233e2249f82", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 193702, + "line_end": 193702, + "char_start": 2888810, + "char_end": 2888900, + "column_start": 0, + "column_end": 90 + }, + "surface_location": { + "line_start": 193702, + "line_end": 193702, + "char_start": 2888810, + "char_end": 2888816, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-18f67c9419a1161a552a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0df95924956d18d3b68f26e5b47bbefa955eea647e4d2ef844c213eb14c5973b", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 196516, + "line_end": 196516, + "char_start": 2928500, + "char_end": 2928606, + "column_start": 0, + "column_end": 106 + }, + "surface_location": { + "line_start": 196516, + "line_end": 196516, + "char_start": 2928551, + "char_end": 2928555, + "column_start": 51, + "column_end": 55 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f94fcaf52cf5fffeff74", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d13905b3f3b6b3578fcd8a0ca0d15cd2e1a805ae682de687db4a36587b3cb506", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 201304, + "line_end": 201304, + "char_start": 3000277, + "char_end": 3000296, + "column_start": 0, + "column_end": 19 + }, + "surface_location": { + "line_start": 201304, + "line_end": 201304, + "char_start": 3000287, + "char_end": 3000291, + "column_start": 10, + "column_end": 14 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f4dcd62a379f91e35fc8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "15a70956b0e36c9f50629b4b6f98ef09c9fa9f3b4867cd527385b867b4b9d7ba", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 207049, + "line_end": 207049, + "char_start": 3084703, + "char_end": 3084769, + "column_start": 0, + "column_end": 66 + }, + "surface_location": { + "line_start": 207049, + "line_end": 207049, + "char_start": 3084703, + "char_end": 3084709, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-36bec4d7594d00221c40", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e73025a6aa895c084570cc26ced673104758439aa7f3081b91a4123d365280f8", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 209755, + "line_end": 209755, + "char_start": 3128778, + "char_end": 3128797, + "column_start": 34, + "column_end": 53 + }, + "surface_location": { + "line_start": 209755, + "line_end": 209755, + "char_start": 3128782, + "char_end": 3128786, + "column_start": 38, + "column_end": 42 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a3c59dbca1b3fc0dd3b1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e0f37280968e2e8a62ba79af7713ef8fafc001c4f9619bcc9db390f09f1b255d", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 210673, + "line_end": 210673, + "char_start": 3141487, + "char_end": 3141512, + "column_start": 8, + "column_end": 33 + }, + "surface_location": { + "line_start": 210673, + "line_end": 210673, + "char_start": 3141490, + "char_end": 3141494, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c2ffd30113e6af80831e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "75c084d856fceebf00986e3c7cdd0e4addad84fb4f59fac62f390ae2ae84f192", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 212275, + "line_end": 212275, + "char_start": 3167899, + "char_end": 3167943, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 212275, + "line_end": 212275, + "char_start": 3167933, + "char_end": 3167937, + "column_start": 34, + "column_end": 38 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fed55e3f18ab2166f9f2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "61b73611703e4b83ddfafa489b62e029e149fe7b33e6a43735d3779d2ed5af91", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 215452, + "line_end": 215452, + "char_start": 3213964, + "char_end": 3214028, + "column_start": 0, + "column_end": 64 + }, + "surface_location": { + "line_start": 215452, + "line_end": 215452, + "char_start": 3213964, + "char_end": 3213970, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-80113d4e58d4bb004a16", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "26eeb713ed78859c825d693d15302417580c7065a8c78aefa7eb897e29f395d6", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 217015, + "line_end": 217015, + "char_start": 3236286, + "char_end": 3236344, + "column_start": 0, + "column_end": 58 + }, + "surface_location": { + "line_start": 217015, + "line_end": 217015, + "char_start": 3236286, + "char_end": 3236292, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ca6632c9107d155fc2dc", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "569972c638462318fc93dc7e61afab875919b01e48f1555f4626d08218b3b62d", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 219406, + "line_end": 219406, + "char_start": 3269658, + "char_end": 3269748, + "column_start": 0, + "column_end": 90 + }, + "surface_location": { + "line_start": 219406, + "line_end": 219406, + "char_start": 3269658, + "char_end": 3269664, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-8120173409711e11ff84", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "362a6e25f7b5851f52594ab67123c037c3158bbe2f19af62d7f5656c43a169b5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 221395, + "line_end": 221395, + "char_start": 3298012, + "char_end": 3298056, + "column_start": 0, + "column_end": 44 + }, + "surface_location": { + "line_start": 221395, + "line_end": 221395, + "char_start": 3298013, + "char_end": 3298017, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-46c6c32a5b99009979fd", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "ea8c8e675951b0da8e276ac6c6eaf1605084b49dccbd74fd13577a5e0f26cc53", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 221482, + "line_end": 221482, + "char_start": 3299454, + "char_end": 3299500, + "column_start": 17, + "column_end": 63 + }, + "surface_location": { + "line_start": 221482, + "line_end": 221482, + "char_start": 3299475, + "char_end": 3299479, + "column_start": 38, + "column_end": 42 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-247946a9225606723ef2", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "4bca8e39faabbcc121ba96071ea74af09e0c23f50a61c3029ec7c461ef7c237e", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 225487, + "line_end": 225488, + "char_start": 3359054, + "char_end": 3359144, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 225487, + "line_end": 225487, + "char_start": 3359138, + "char_end": 3359142, + "column_start": 84, + "column_end": 88 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-21eef9d3a699f08034b0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "085ec57ebf6c274f5584372737b9ae7353ba409e16fd1a92a3c019f0fe63fa69", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 231439, + "line_end": 231439, + "char_start": 3439394, + "char_end": 3439410, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 231439, + "line_end": 231439, + "char_start": 3439401, + "char_end": 3439405, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-4047e630e22523d097b1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "bd10b95a4b71f256068af4fec44ff0b00767a49f046c3ef8f46915f2d41d0ac9", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 234712, + "line_end": 234712, + "char_start": 3487708, + "char_end": 3487750, + "column_start": 37, + "column_end": 79 + }, + "surface_location": { + "line_start": 234712, + "line_end": 234712, + "char_start": 3487712, + "char_end": 3487716, + "column_start": 41, + "column_end": 45 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-31fdca7efb5535dafaed", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d009b6ecb7e080910f49b9c5e5135fa7538f449297e57611a32859d9618aa074", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 235558, + "line_end": 235558, + "char_start": 3499142, + "char_end": 3499193, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 235558, + "line_end": 235558, + "char_start": 3499142, + "char_end": 3499146, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-798e07e86c54b797914a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "0d3f8fe0e6657afa634131ea51c1ee464da9e859f636355cd828225492d87a22", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 237727, + "line_end": 237727, + "char_start": 3530295, + "char_end": 3530338, + "column_start": 17, + "column_end": 60 + }, + "surface_location": { + "line_start": 237727, + "line_end": 237727, + "char_start": 3530299, + "char_end": 3530305, + "column_start": 21, + "column_end": 27 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bab62782a037bd65f9c3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "92bc9f153d7647c65aa0a7f32300c430b53a17ec5a3bf2bbb80d9a2c21a552a5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 238360, + "line_end": 238360, + "char_start": 3538503, + "char_end": 3538514, + "column_start": 0, + "column_end": 11 + }, + "surface_location": { + "line_start": 238360, + "line_end": 238360, + "char_start": 3538505, + "char_end": 3538513, + "column_start": 2, + "column_end": 10 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-c44507124c96d2ba80c6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "540606ef8332d3de4082482b38d625b6e37b55ae26c0720b629d1422e47d869f", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 243658, + "line_end": 243659, + "char_start": 3618571, + "char_end": 3618583, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 243658, + "line_end": 243658, + "char_start": 3618573, + "char_end": 3618581, + "column_start": 2, + "column_end": 10 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-62a68a4abd0046a435df", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "676e4b0cee757ad3e3ba2135b1fb5a67fb89893b468b05ab76de94e9fd5b7ffa", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 247234, + "line_end": 247234, + "char_start": 3670884, + "char_end": 3670927, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 247234, + "line_end": 247234, + "char_start": 3670891, + "char_end": 3670895, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-87de709cfd019988a3db", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "311d2c1ea757b6e6a08a2b1ea71e648d9f8e44979100c87817fae16c25e66ac8", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 248434, + "line_end": 248434, + "char_start": 3689037, + "char_end": 3689158, + "column_start": 0, + "column_end": 121 + }, + "surface_location": { + "line_start": 248434, + "line_end": 248434, + "char_start": 3689051, + "char_end": 3689057, + "column_start": 14, + "column_end": 20 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7cac975ca8e04c3d816f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "ac5b058eb4e02cc6a50da1f1f6ec40e2126e32533527cd416bbd23f03e5bc02e", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 248911, + "line_end": 248911, + "char_start": 3695589, + "char_end": 3695648, + "column_start": 0, + "column_end": 59 + }, + "surface_location": { + "line_start": 248911, + "line_end": 248911, + "char_start": 3695614, + "char_end": 3695618, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-552147feb61bdb5a0b8a", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "9ae44de6ad60e5198a5173df75fa6ac78bb5566d57319bc2ba18c72ce06b0ff5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 250024, + "line_end": 250024, + "char_start": 3710170, + "char_end": 3710240, + "column_start": 0, + "column_end": 70 + }, + "surface_location": { + "line_start": 250024, + "line_end": 250024, + "char_start": 3710172, + "char_end": 3710178, + "column_start": 2, + "column_end": 8 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得注意的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-98ad1ad4dbb9eaf077c5", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "baa2cdb26f75354edeef6c6cc40761d794a65c9a9687e2f859bd38afacc8b0c6", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 252889, + "line_end": 252889, + "char_start": 3748179, + "char_end": 3748201, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 252889, + "line_end": 252889, + "char_start": 3748195, + "char_end": 3748199, + "column_start": 16, + "column_end": 20 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-7f62e2f7783d942da97f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "77143006342e81e15577db3e15aebfceef8fa3c5770f94cdc44fc18842419d27", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 255703, + "line_end": 255703, + "char_start": 3788253, + "char_end": 3788326, + "column_start": 0, + "column_end": 73 + }, + "surface_location": { + "line_start": 255703, + "line_end": 255703, + "char_start": 3788253, + "char_end": 3788259, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5c750e707f2f2022d3f1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c9baa228f2c7053984b995c16810b4ee85337c03d037db926ca8bd093f18c18b", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 255790, + "line_end": 255791, + "char_start": 3789893, + "char_end": 3789932, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 255790, + "line_end": 255790, + "char_start": 3789916, + "char_end": 3789920, + "column_start": 23, + "column_end": 27 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-5bd580b5701b64ae09b7", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "bd46c8e6f3390e267013ddd4ca47703e230b538999401810f16d84ff48583ce4", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 256069, + "line_end": 256069, + "char_start": 3793399, + "char_end": 3793425, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 256069, + "line_end": 256069, + "char_start": 3793400, + "char_end": 3793404, + "column_start": 1, + "column_end": 5 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-173a47d76dc1585d5ea8", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "3248bc9590719202d5e9ebe3d9197b9cfac0bc551e15cf6de03e7fc69e234410", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 257737, + "line_end": 257738, + "char_start": 3818042, + "char_end": 3818075, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 257737, + "line_end": 257737, + "char_start": 3818068, + "char_end": 3818072, + "column_start": 26, + "column_end": 30 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-80e3c24e9614014a182e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5de6405787a119eb2140a1925018b3b3c1d9dee238d7e4d64170608b9fdc2dea", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 260581, + "line_end": 260581, + "char_start": 3857787, + "char_end": 3857807, + "column_start": 0, + "column_end": 20 + }, + "surface_location": { + "line_start": 260581, + "line_end": 260581, + "char_start": 3857802, + "char_end": 3857806, + "column_start": 15, + "column_end": 19 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-277e98324c0a20101c7e", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "1726af6c9dc35272c07c6e5ecdc3f61d8b6981649ac5d2404e285ebc84af9c26", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 261307, + "line_end": 261307, + "char_start": 3870719, + "char_end": 3870781, + "column_start": 30, + "column_end": 92 + }, + "surface_location": { + "line_start": 261307, + "line_end": 261307, + "char_start": 3870776, + "char_end": 3870780, + "column_start": 87, + "column_end": 91 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0c651974df1d06d345cb", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "6844b36aba80d8059b76a13a5679bfea795739bb13986af61976b2e05b90f974", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 261481, + "line_end": 261481, + "char_start": 3873531, + "char_end": 3873580, + "column_start": 0, + "column_end": 49 + }, + "surface_location": { + "line_start": 261481, + "line_end": 261481, + "char_start": 3873570, + "char_end": 3873574, + "column_start": 39, + "column_end": 43 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-9f12ca40e843ad69ba6b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "6a0363ad2e18b1ae3bda91f42e5874a6f0b84f0dea8d15c0d5595b32ce352de5", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 261532, + "line_end": 261532, + "char_start": 3874535, + "char_end": 3874578, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 261532, + "line_end": 261532, + "char_start": 3874535, + "char_end": 3874541, + "column_start": 0, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "值得一提的是", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-dd5b2f0ce24c4b42662c", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c173d84a409a62977b57b73afd63f8788cab46ba6860cba207a7f2a979fe3dd4", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 264031, + "line_end": 264031, + "char_start": 3911276, + "char_end": 3911351, + "column_start": 0, + "column_end": 75 + }, + "surface_location": { + "line_start": 264031, + "line_end": 264031, + "char_start": 3911285, + "char_end": 3911289, + "column_start": 9, + "column_end": 13 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d3905a9ba8b4fc9de803", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "9af4e427a092700ce42a0ade03147951ff50d3ba5d3451a9c1c124d2a682f8d8", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 271147, + "line_end": 271147, + "char_start": 4011993, + "char_end": 4012071, + "column_start": 0, + "column_end": 78 + }, + "surface_location": { + "line_start": 271147, + "line_end": 271147, + "char_start": 4012027, + "char_end": 4012031, + "column_start": 34, + "column_end": 38 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-698059f6f24f4e5c0466", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "1a6094ab822ab1affe70f93ee33dd40e213609b7326436a636ad6c042523a597", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 276412, + "line_end": 276413, + "char_start": 4087003, + "char_end": 4087066, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 276412, + "line_end": 276412, + "char_start": 4087027, + "char_end": 4087040, + "column_start": 24, + "column_end": 37 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.abstract_atmosphere", + "surface": "一股富婆包养的气息扑面而来", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-a9e77774fd85e03ccac4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5acefe2322c00f51e613b59cf0822ebf7e019c397be5f5558da78399a1a8a808", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 276526, + "line_end": 276527, + "char_start": 4088620, + "char_end": 4088653, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 276526, + "line_end": 276526, + "char_start": 4088639, + "char_end": 4088643, + "column_start": 19, + "column_end": 23 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3b015cafcdbd53e07e07", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "03516ea7663ede70c095628b32300f05dd050368bcbb12e55c0e72a8cb148bc0", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 277885, + "line_end": 277885, + "char_start": 4107644, + "char_end": 4107704, + "column_start": 0, + "column_end": 60 + }, + "surface_location": { + "line_start": 277885, + "line_end": 277885, + "char_start": 4107690, + "char_end": 4107694, + "column_start": 46, + "column_end": 50 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-03c83ad0cc99e57527c1", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d68ffc360278e86f6ddfeef636e2a4b4eea4a0dd988014185981c54788948c75", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 277909, + "line_end": 277909, + "char_start": 4107929, + "char_end": 4107964, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 277909, + "line_end": 277909, + "char_start": 4107957, + "char_end": 4107961, + "column_start": 28, + "column_end": 32 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-802ff85ce8bcd8f06147", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e56c4937d31ef7c9c5cf53c5329308cd4d2ae00a58d115aa91b015fb6f51c7c2", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 278188, + "line_end": 278188, + "char_start": 4112736, + "char_end": 4112762, + "column_start": 0, + "column_end": 26 + }, + "surface_location": { + "line_start": 278188, + "line_end": 278188, + "char_start": 4112757, + "char_end": 4112761, + "column_start": 21, + "column_end": 25 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-fd05c92b674ff2508bfe", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "352448e6a9a3de7eac35c9df67312bde628c1ada8e6d59fd93c179f088f611ff", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 279346, + "line_end": 279346, + "char_start": 4129537, + "char_end": 4129553, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 279346, + "line_end": 279346, + "char_start": 4129544, + "char_end": 4129548, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-46d51648b52a2fd4072f", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "2c3236a44bb6f823f68a89a92a418d413bdeac9794945ea3b0df19913ae9c4fd", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 282235, + "line_end": 282235, + "char_start": 4176002, + "char_end": 4176016, + "column_start": 0, + "column_end": 14 + }, + "surface_location": { + "line_start": 282235, + "line_end": 282235, + "char_start": 4176004, + "char_end": 4176008, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-731c758be6d14d3711ed", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "353ffb8445eed4e2cc551894b7ad8ba06752e58c920237428fa4c23294f85d69", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 286078, + "line_end": 286078, + "char_start": 4230657, + "char_end": 4230712, + "column_start": 0, + "column_end": 55 + }, + "surface_location": { + "line_start": 286078, + "line_end": 286078, + "char_start": 4230657, + "char_end": 4230661, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f13edbc884c4b706f516", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "b385ea96a2faeadca3f41cd0c1ccfc1456e41156a6afa810bb602777bd53d2e7", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 287707, + "line_end": 287707, + "char_start": 4252244, + "char_end": 4252314, + "column_start": 0, + "column_end": 70 + }, + "surface_location": { + "line_start": 287707, + "line_end": 287707, + "char_start": 4252269, + "char_end": 4252273, + "column_start": 25, + "column_end": 29 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3551353d40fc60b2a5a3", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "859c2e93ba8c356bfff4c9ec7fdd5ae5b7a767318e93b2e47c394d9fc76f148f", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 289414, + "line_end": 289414, + "char_start": 4278830, + "char_end": 4278911, + "column_start": 0, + "column_end": 81 + }, + "surface_location": { + "line_start": 289414, + "line_end": 289414, + "char_start": 4278869, + "char_end": 4278873, + "column_start": 39, + "column_end": 43 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-bb1a168b0a84fb536fa6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "17cd69da7e5109f1ff937edca887cfa4daf1aa671374c76e6ad551049680a0bc", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 291208, + "line_end": 291208, + "char_start": 4307581, + "char_end": 4307603, + "column_start": 0, + "column_end": 22 + }, + "surface_location": { + "line_start": 291208, + "line_end": 291208, + "char_start": 4307594, + "char_end": 4307602, + "column_start": 13, + "column_end": 21 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-eb44273fdf305b7c523b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "a31e0d0b991dfa006f4cb4474eca47e8e92ad715f24d64cfc3a7c3a57bc2fcdc", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 292903, + "line_end": 292903, + "char_start": 4335297, + "char_end": 4335321, + "column_start": 0, + "column_end": 24 + }, + "surface_location": { + "line_start": 292903, + "line_end": 292903, + "char_start": 4335316, + "char_end": 4335320, + "column_start": 19, + "column_end": 23 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-0b0c6aa047c3b63e6849", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e8f143982508f5e33f4ba9eb2d02f9e43c28147b1581ec498867ecf26bc1a388", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 310135, + "line_end": 310135, + "char_start": 4582938, + "char_end": 4583017, + "column_start": 0, + "column_end": 79 + }, + "surface_location": { + "line_start": 310135, + "line_end": 310135, + "char_start": 4583002, + "char_end": 4583006, + "column_start": 64, + "column_end": 68 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-47778e0a9cf3d87391a0", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e580259efd7cba075ee308502771137e832de1d97978693de30975f6c07a8f71", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 311098, + "line_end": 311098, + "char_start": 4595964, + "char_end": 4596056, + "column_start": 0, + "column_end": 92 + }, + "surface_location": { + "line_start": 311098, + "line_end": 311098, + "char_start": 4596051, + "char_end": 4596055, + "column_start": 87, + "column_end": 91 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-951180a59aba43c7d85b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "acd9a5515ab62ee18d9a7af8d7861fc556e298925b0036e1b0c973f43197d923", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 313750, + "line_end": 313750, + "char_start": 4630776, + "char_end": 4630827, + "column_start": 0, + "column_end": 51 + }, + "surface_location": { + "line_start": 313750, + "line_end": 313750, + "char_start": 4630790, + "char_end": 4630794, + "column_start": 14, + "column_end": 18 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ca436c1bf811ecffcd65", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "6384d7fc55364d6494593bf31aa30a5870b79b5f62022e4c0b69d40d9d049403", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 314809, + "line_end": 314809, + "char_start": 4646289, + "char_end": 4646305, + "column_start": 0, + "column_end": 16 + }, + "surface_location": { + "line_start": 314809, + "line_end": 314809, + "char_start": 4646300, + "char_end": 4646304, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f0f3798546c0354ec3e4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "9de5eb496d5e187e2c834526b3898e9e79a5666b010bb2e8270384d1ecef3045", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 320206, + "line_end": 320206, + "char_start": 4729854, + "char_end": 4729930, + "column_start": 0, + "column_end": 76 + }, + "surface_location": { + "line_start": 320206, + "line_end": 320206, + "char_start": 4729866, + "char_end": 4729870, + "column_start": 12, + "column_end": 16 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-ec5e3ed72b1407b65694", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "538a715f95a5d62e4669f96a60b084f79557569b00f938ae5e9f0c5eb4d024bb", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 324985, + "line_end": 324985, + "char_start": 4801575, + "char_end": 4801658, + "column_start": 34, + "column_end": 117 + }, + "surface_location": { + "line_start": 324985, + "line_end": 324985, + "char_start": 4801575, + "char_end": 4801579, + "column_start": 34, + "column_end": 38 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "众所周知", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-3c19a4fd5c456581092b", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c5490f1d9513f85fbb3421eb5320b897dfaaa80922e7de6eb150c39f9a5e445e", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 325348, + "line_end": 325349, + "char_start": 4806861, + "char_end": 4806901, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 325348, + "line_end": 325348, + "char_start": 4806886, + "char_end": 4806894, + "column_start": 25, + "column_end": 33 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d34cec1a0d7bf9694cc4", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d5d7ee7a2750ebfb92cda9ca6420a561f09300abfa6f5306f7372f87ec708c20", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 325798, + "line_end": 325798, + "char_start": 4813757, + "char_end": 4813784, + "column_start": 0, + "column_end": 27 + }, + "surface_location": { + "line_start": 325798, + "line_end": 325798, + "char_start": 4813764, + "char_end": 4813768, + "column_start": 7, + "column_end": 11 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角上扬", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-215e451b18f7e5ce3d03", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "c452c73d2f491c70a6ea6d8d6a06a96e8fc1699914b2196904bf211250f54c91", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 333139, + "line_end": 333139, + "char_start": 4917399, + "char_end": 4917464, + "column_start": 0, + "column_end": 65 + }, + "surface_location": { + "line_start": 333139, + "line_end": 333139, + "char_start": 4917404, + "char_end": 4917408, + "column_start": 5, + "column_end": 9 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-138005835cbd73662a62", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "df7d1b59745c2193bfdce0387c3c85308f149e5eaed25992539a41f81e1141b7", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 337681, + "line_end": 337681, + "char_start": 4985088, + "char_end": 4985123, + "column_start": 0, + "column_end": 35 + }, + "surface_location": { + "line_start": 337681, + "line_end": 337681, + "char_start": 4985088, + "char_end": 4985092, + "column_start": 0, + "column_end": 4 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "换句话说", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-b80493a46fd55b439121", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "d8f570931f3347ecdd7a52ca58c2714af4e231930772edf090523b3c3f9a2312", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 342571, + "line_end": 342571, + "char_start": 5057473, + "char_end": 5057516, + "column_start": 0, + "column_end": 43 + }, + "surface_location": { + "line_start": 342571, + "line_end": 342571, + "char_start": 5057507, + "char_end": 5057515, + "column_start": 34, + "column_end": 42 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "眼中闪过一抹精光", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-24f1c2e940e3ce38bdad", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "8be2b6ad779265fc9e570f3a3424d0f4d5cdc3acea05302562df70f2071fa00e", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 343651, + "line_end": 343652, + "char_start": 5073903, + "char_end": 5073932, + "column_start": 0, + "column_end": 0 + }, + "surface_location": { + "line_start": 343651, + "line_end": 343651, + "char_start": 5073905, + "char_end": 5073909, + "column_start": 2, + "column_end": 6 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-f24d1b68dea22402c3fe", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "4d7b0a3e7ed6db4065fd7f45f967b1224f6d3e49840f4b956b891d025616a173", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 346573, + "line_end": 346573, + "char_start": 5117154, + "char_end": 5117200, + "column_start": 0, + "column_end": 46 + }, + "surface_location": { + "line_start": 346573, + "line_end": 346573, + "char_start": 5117157, + "char_end": 5117161, + "column_start": 3, + "column_end": 7 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-68886c0f908748532bb6", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "f4e661c5262a33fc71c181a36d315b8cfe2b9eb1b6f52e5f806c1f110d5b2517", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 352978, + "line_end": 352978, + "char_start": 5206188, + "char_end": 5206222, + "column_start": 0, + "column_end": 34 + }, + "surface_location": { + "line_start": 352978, + "line_end": 352978, + "char_start": 5206217, + "char_end": 5206221, + "column_start": 29, + "column_end": 33 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-83d3d0e03f2ef8b1ff95", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "5a5a85b5407b6893d48942348a08b70c763b3e4c35489154cb0c55ea2cb52002", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 363805, + "line_end": 363805, + "char_start": 5366263, + "char_end": 5366284, + "column_start": 0, + "column_end": 21 + }, + "surface_location": { + "line_start": 363805, + "line_end": 363805, + "char_start": 5366274, + "char_end": 5366278, + "column_start": 11, + "column_end": 15 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.stock_micro_expression", + "surface": "嘴角勾起", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + }, + { + "schema_version": "ai-flavor-case-v1", + "id": "case-d24540303827ac247d87", + "card_type": "ai_flavor_case", + "state": "shadow", + "label": "unclassified", + "layer": "lexical", + "carrier": "unknown", + "capture_mode": "backfill", + "excerpt": "", + "context": "", + "source": { + "kind": "existing_work", + "license": "research_only", + "source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "excerpt_sha256": "e775b975996ada308fa78049f6df5030ad5213145f5790f3edfc789787345a87", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "location": { + "line_start": 364081, + "line_end": 364081, + "char_start": 5370418, + "char_end": 5370503, + "column_start": 0, + "column_end": 85 + }, + "surface_location": { + "line_start": 364081, + "line_end": 364081, + "char_start": 5370498, + "char_end": 5370502, + "column_start": 80, + "column_end": 84 + }, + "work_ref": "ref:超神机械师_齐佩甲" + }, + "observation": { + "pattern_key": "lexical.meta_disclaimer", + "surface": "不言而喻", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + "function_check": [ + "是否承担具体叙事功能", + "是否是角色/场内载体的有意声线" + ], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。" + } + } + ] +} diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/canonical-samples.yaml b/.claude/skills/capture-ai-flavor-cases/references/fixtures/canonical-samples.yaml new file mode 100644 index 0000000..491f30e --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/canonical-samples.yaml @@ -0,0 +1,56 @@ +schema_version: ai-flavor-case-v1 +cards: + - schema_version: ai-flavor-case-v1 + id: case-synthetic-meta-001 + card_type: ai_flavor_case + state: canonical + label: sf + layer: lexical + carrier: narration + capture_mode: synthetic + excerpt: 值得注意的是,门外下雨了。 + context: 她抬头看了眼窗缝。值得注意的是,门外下雨了。她继续翻账本。 + source: + kind: synthetic + license: synthetic + source_sha256: 6783305da4b565a52e97a2bbf99e7c1b2150eaf6d5598df14209ff009cb56338 + excerpt_sha256: dd5a51981e22ca2c07be6b2f74bce7876a2bddcdafc22e173ac1138a92cfb1fc + source_ref: synthetic://ai-flavor-v1/meta-001 + location: {line_start: 1, line_end: 1, char_start: 9, char_end: 22, column_start: 9, column_end: 22} + surface_location: {line_start: 1, line_end: 1, char_start: 9, char_end: 15, column_start: 9, column_end: 15} + work_ref: synthetic-work-a + observation: + pattern_key: lexical.meta_disclaimer + surface: 值得注意的是 + diagnosis: 删除元话语后事实和动作不变,当前样例判定为应修。 + function_check: [是否承担叙述者声线, 删除后信息是否保持] + risk_if_changed: 若属于固定评书腔,直接删除会损失声线。 + suggested_action: 删除或改为具体动作,待任务声线合同复核。 + review: {reviewer: fixture-review, note: 合成对照,确认本段没有额外功能} + - schema_version: ai-flavor-case-v1 + id: case-synthetic-meta-002 + card_type: ai_flavor_case + state: canonical + label: snf + layer: lexical + carrier: narration + capture_mode: synthetic + excerpt: 值得注意的是,这家茶馆只收旧账。 + context: 说书人敲了敲桌面:值得注意的是,这家茶馆只收旧账。听众安静下来。 + source: + kind: synthetic + license: synthetic + source_sha256: f9e0e88c10c1a199143431c5d7cb93e9159d117a6ad32763c32c243a751a654d + excerpt_sha256: 0468e1fc6513d05e6e809ded793ec951b76abc329dfd8c1f3796ec321652ad5c + source_ref: synthetic://ai-flavor-v1/meta-002 + location: {line_start: 1, line_end: 1, char_start: 9, char_end: 25, column_start: 9, column_end: 25} + surface_location: {line_start: 1, line_end: 1, char_start: 9, char_end: 15, column_start: 9, column_end: 15} + work_ref: synthetic-work-b + observation: + pattern_key: lexical.meta_disclaimer + surface: 值得注意的是 + diagnosis: 相同表面形式承担说书人节奏和悬念提示,当前样例判定为不应机械删除。 + function_check: [是否为角色/叙述者固定声线, 是否改变信息节奏] + risk_if_changed: 删除会抹掉叙述者声线并削弱悬念落点。 + suggested_action: 保留,或仅在声线合同允许时改写。 + review: {reviewer: fixture-review, note: 合成反例,确认表面形式有叙事功能} diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/revalidation-2026-08-14.json b/.claude/skills/capture-ai-flavor-cases/references/fixtures/revalidation-2026-08-14.json new file mode 100644 index 0000000..0ba8816 --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/revalidation-2026-08-14.json @@ -0,0 +1,7977 @@ +{ + "schema_version": "ai-flavor-revalidation-v1", + "checked_on": "2026-08-14", + "source_root_ref": "oh-my-muse", + "usable": true, + "totals": { + "cards": 788, + "card_mismatch": 0, + "stale": 0, + "unavailable": 0, + "verified": 788 + }, + "works": [ + { + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "card_count": 106, + "status_counts": { + "verified": 106 + } + }, + { + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "card_count": 83, + "status_counts": { + "verified": 83 + } + }, + { + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "card_count": 19, + "status_counts": { + "verified": 19 + } + }, + { + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "card_count": 173, + "status_counts": { + "verified": 173 + } + }, + { + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "card_count": 8, + "status_counts": { + "verified": 8 + } + }, + { + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "card_count": 226, + "status_counts": { + "verified": 226 + } + }, + { + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "card_count": 61, + "status_counts": { + "verified": 61 + } + }, + { + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "card_count": 112, + "status_counts": { + "verified": 112 + } + } + ], + "cards": [ + { + "card_id": "case-0b9ed3af70a7dbf315fc", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-aae465436b237ca0bde1", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a6dfe32afecea75926b4", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7ca8d1dc58f50d116b03", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e6da75c08e065e33f827", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-55a6f6af8dcf3cd0f345", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-383f8f97a73ed1baaff5", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cbb7d8655ec26019e8de", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-57f6e02e01b24bd1285f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5d7839147487b06620bb", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-72030eb77fa51db6c98a", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3d2b52ad3ede0f2e70a9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b086b59dd56e87e8339f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d4423abfd1d833bd8860", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-db9e35101650e4a037c9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c1d3a06e204313b77614", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d8b79723415b01efefaa", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d4d1cc58454fe62328cb", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-be911d44b75b1505bdce", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ead8c98296b8b79e8856", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1b1fea67dcb41a9273ed", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-41db0461da49faefc77e", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a0c16a455b5cacf01120", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cb11100bcbfe263e6f4c", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ea02cd145557edc41a1f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-199629f660cf5ba7a0ea", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c730db47328b17c70396", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4347fb17095cc7e298bc", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5a3c6dd3035e289df802", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-897a65f47205b3dec3e9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8e8c84711e01c8d2d736", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-40333f96b45f47cc74d4", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-590f7a19ad44991f2dc9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e8fe872192d1fb173698", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-73d31a6ef19bbb51ce01", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1481a87dc37b2e400411", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-47ea514efc644ef43c75", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9c100ffd9c2328fdf091", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-23d3e7715d12d9adc3f1", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-778a023c7e9fe87fe69d", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-76fdeb03b89921ed0056", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f34b9f4bc5c132b00759", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-74a417ac8b8feadefd20", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a80542f35aff0f6a8f5e", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5574a5a5d3d0a3a15b17", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-448afd8e54c51ade87a7", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5c04448b8841a71173d5", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c0684dc2e183f0b3cba2", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d5ffa5d0966ed838f0c5", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7bc3d565cc481e6f8acd", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c929454e127831cd0c33", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1bc6b5e0227576e54edc", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4482d9a7b26653682445", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ef9ad968060d03fa3be9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-04881d6d1d82ff1dc46c", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dad8a55974c9d79ea952", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-da74a02bbb7977802d27", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-383d52146f4e9498e330", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-10c89604b4c2ce0d0808", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-27e1e9b980968185a11c", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-704aad5bc234bdd6c694", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b4d114f536e147c4730f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-502f6466e5c502001d74", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-47a7fd08537dded68256", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6ddfa6d32e60b14aebe6", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8435c03ba667bd1d5de1", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8d2bf3b04b35a5e4d005", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ff24c7b4fc294e22c4b8", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f4fa0dd8ba308c52a68f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2af725eb0dcef8c9b5d1", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8bbb36e00539c3841262", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1966d68972b0ebbb9519", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e057b7a77df0d12732d0", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-01bc74ed6737d6f31f17", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-56b756be344490c5de7f", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d14e889a45ff9e078b8d", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8fa760b8a611c0d1cde9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f8131b9f69b98a35dcbe", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b9e16d8dab8a5142ba33", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c8caba5cb7285fe3804d", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-660562d37d748f99daf5", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-541b62cfb0c522a72fe1", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3d1c01105c0195d54657", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-334454141a7e70ee9415", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7518782e8318e0a29e2e", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fc5046e6ac5f282daa65", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a0f9fa0158239b64f6a9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-32175984741e65651bb7", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d67e9feef73d9d204541", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f3e3bbd6d8f7829b5e41", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-50f9cad213b00922e0c6", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-88a40c74311f4a009d13", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-74782859eb570641ea8d", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-335fd24f6aa92a80c010", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a07084d82a4781005310", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-959eedefbed3b7a41431", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-96d3efd30332cde879a9", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2c9e9579a417e6e96998", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2df144f3bac6a692cf11", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ca31ec28dbb0bd3ca7ad", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7fedf825be6e91f24d08", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-73fb5003cabce9987adb", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-87b2a4244f876b8ed663", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b4efaa4b0c3653b60f16", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8ce1cfc86323d504e475", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-40a4984c7fd305355743", + "work_ref": "ref:希泊尼战纪_虚伪王庭", + "source_ref": "小说清单/希泊尼战纪_虚伪王庭.txt", + "expected_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "actual_source_sha256": "7da8c9d3aef1e5dd7a7765f515569f37730bc82d6417c86ad4c7bf695e23ac5d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dd9f4015385477825571", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5049423d0576fc18633e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3f36965498455845ba83", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1779a7c0a64941cbb7f1", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9d80eef16dd2f46e01f5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-626515463b586c80b5b5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-acc4a6496f4c584cd728", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cda8fc2758d69da57732", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0e5bd54fe6a07fc47fae", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ac04870f085e25d913b0", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-69ee085c6378fb2c02a1", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f1bf351e924493dbcda6", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a67cda97da7db38ebadf", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e3b30cc4c87dea66ad01", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f211e720c16799af63ac", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-efefd51a120a1a7daf4d", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6c584999ec71d0968fd6", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6efd4118e33306d176ec", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-584ec3711ecb1e47833c", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3740e86bc12aecd7db7b", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dd63892f3e8782b8ca5a", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-95a8a0729dc310027755", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7d3d36248055169dbcb5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-728c1d86f18d9d54993c", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bd9259bbae3c276724b5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-de74473d8c0f0acfe149", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-19b82e35b76ea2b5ad9a", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1e5e8460e940ae2f51d2", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9f440ca4e223b888be5e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7092f84d585b6dde98fa", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-470c17f3ba692cc9b16e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-75bc328428076dbd403e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bfe0300b0bb839982c7a", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a44374eb0e22150452bd", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-76ca4b19fc5342fd5506", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7d995336d576f1aec506", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e1f0b7b89d40af6f942c", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3382a8288df9c64ecf79", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a545d42c512986d146d8", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-510f5261153e263b37ce", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b857a505e93f644722d5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2959d606ef220c5b57ac", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-49fadeb78b82b69ee1de", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2afaaa6fa42b8dc6a87f", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-aa2015bc4357de46c61c", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a5399b67d01bd461d57c", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b434441dbcd787ba9257", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fdc8fe19b977c9d7dbe4", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4d3e73bfd5d9c420262b", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fc8a8492cfe00d397575", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0e74352bbdf5e7008282", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d46ba809da88af6f37e2", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-24a6a704e54caa46161e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-767738fe750f63cd82db", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e87bceed207a954c6702", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1081d54b80af65a1cdbf", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0abedbefa0334859bf21", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ad6778600217fa7b8757", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-58e6259afc8b432f081a", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4038d065fd418a18333a", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0ccb7845510843227560", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e6cc0303dd23ce16eb4f", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-35b3828df3d9aa1681ac", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b5745735f69805c63352", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-67c906679a23cf06733e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-86674a4be3ace16e4190", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-07a095b89ee887c9ff5e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e80d601034fd80bb56b0", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a9e90b3b173388065bc0", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e77edaffeaadbcdff524", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f418f1adf0fb7ef829c4", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-11aa84f99cb9d1993a98", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d990aed8334b87dadcbd", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a6a0da526a864ad3a79b", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b2ba61c50bd24d286955", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-72de8359b8ce031cb3f6", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c1d6745f72ec1cb101f5", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7507d8df9ddcae752d9e", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-47061701d483ab0fc6e0", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-72ff540df38ac34645a6", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e528be012a24496c7f7f", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c54489a7c34e9d9d6471", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8ca8447f95bca0409585", + "work_ref": "ref:星环使命(虚伪王庭)", + "source_ref": "小说清单/星环使命(虚伪王庭).txt", + "expected_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "actual_source_sha256": "dd1a1e3aa20fd40bd147ffb8f7073ba9817f0a17587e71ba174de7a3955b5d59", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5996a2853f562a78d4c8", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0103ca241db8ca2c4c45", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-18eed63affab56992ff8", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-49a88454a01143b2fc05", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6bae33000c20db536226", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-74c7241abe80d6881823", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-14f1fb52b9e1c59ce099", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f769f6d41d4add42bb35", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9fb1ba9758c109c655c2", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e1f6a4422abb32a3f051", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ad033982f6ac4d3830df", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8928e70e253ec5ef0755", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b736a260bdd1cee9daff", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f2c64297c4ce5a8ab97f", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-31f56413543e126e3ebd", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bf726648ee7264080f36", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0997522fddccb2f0601f", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b483f77a40066921fa97", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-01896b9fd20c0b5c2902", + "work_ref": "ref:机动风暴_骷髅精灵", + "source_ref": "小说清单/机动风暴_骷髅精灵.txt", + "expected_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "actual_source_sha256": "3177ec43aad1363f65ac07f116ed068ba39a03512fff996fcafd42af6d19990d", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6b391363e91b6837811f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c2d8edbd901a2057c121", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9687fabc5a72f3958e78", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f1b56d116ed9c5354fb3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a477a2abbee79f1e40e1", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e4253feec14650a7be95", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b12c13f89200b3d4cf72", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f60f9514c3b76eb14bf7", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f734c4cbf2cab996df8e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2ff3d3ee3b7990ad2273", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-375145552984d473aea5", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c593f0e88cc67de51bb6", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f729735c09ff1e488be9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bdcbe232ea94e80f709d", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4c0a27e66ddb951ec0b0", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-75cab556404e43921d59", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f5be7f527b905db4a6fa", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-43a486c1338f7a7ee40f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-60271cd9c50952b7ef27", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-67a6c6f80295415ceaf8", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8bc1a61bfa7798b43a55", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-abe53afc34ba497bac3e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cd65ff6fc9bdbdbedbbd", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f6ef4ec912986165f07a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f649289866dc58415a07", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b2b0a675cd2df0052604", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-16c2e9845da0bbc7a32f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f19422453214e4711f50", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-604f032819cc34e89d43", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2743edaaea3bad7976f1", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1678e84dea052833f6a9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3b19a121753ca98980d4", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e3f0ac5c98cb7d9f6597", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d4ed82c2c5a0c770c380", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-22eb9b4ddccf194e0e88", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3974b39a0d547f79f4db", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5e315a507aae7f4fce36", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a27551f5081774f2e9ce", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-379765630f6b651d0063", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f82fe9d318c53953b54c", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a1215c9e140400514b54", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-329b7316686f810ab957", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c9b47c94dedbe6c0b49a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1bc3dc03aaea26d9b2ee", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ca49f4d8055246c8ebe2", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-78b0a7f3a370d521d9a0", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-13859754b067937814df", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cd88653b866518370886", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4fa5dfc78890a32dc466", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ee2ec09596777f9815bb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6fa3e3053210f09195f3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5f411c4cc6ca8180f257", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8ac2c4b70d425107d74a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bd1448f26ccb8603f5cb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-593b88aa3f0dd8f2130f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e9c3a20231ef703a261a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-29a0baa12e4f14611372", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-568b594cc17dba14c0e5", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ebf50988da3b34598b63", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8fd5f361e3f3b41faa18", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b9afed3d38fa9473e210", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a1d0c5c963376e26eaef", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-242c62253834887a1c78", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b77c4493dc75b68c4dc2", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-347e07cc9077c6206f89", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c9d4560816be43e1ea8e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b8260342c11aab84136a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7eb7ccd8791191f1749e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6258c8d3b4431ed8d71f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e5ee016e399ad4197c4a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a7aac5d12d1f26a72d34", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a8c26010a16830197c43", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9c76ab698657d78a95c5", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f44850934dbcb7be8f8e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7dce86e88140c43faf5e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d1c96646c6366911f4bc", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-756151a841e0fc189076", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7aa6c2787f72599ed91e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f948bc6f6b4d78c4a805", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-19be5a760cd432de9557", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-60692f51c8bd30c0e305", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5988d91f7188b6a4c96f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-89e9437d238dda667f60", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ad3e634c5cfd881fff79", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2e9f849caffa7202f8b2", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5e41e0f2f28c68f387b6", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-075c17cc50dda39d256f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-79fabfe15e9f02881787", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1d48561cdc0421272de7", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ea0375b5baff7c32263b", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ecac1a7d9a5118ac1131", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1b63e59f6c302781e622", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-eb06c0654f009bc62373", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1df3221dc117e9ff6d66", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5dfb8056599f1d690594", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-816d99e075f453cef811", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c3203755529b62d978e3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f90a8c4ab3e5f6364acb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1f5c06a6e50f0105bdc9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-528e715ff3ed3df43fee", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ce051adc75552b1c79f6", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2fad2b0f2580c1123355", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a38279b1ba9ed2bef49c", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dad4b27d350ab479de79", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-89ca35f2a960f7db8eb3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1e0b575732724d5ca04a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-97043f89fc33e3e558b3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f251a35855fe077f1b88", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8945bd6caa6878584d03", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0fcb7e765014026160cc", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bc31c81cca9ef2e2fe4d", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-33d4aee719c4164c6054", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8f96f35402c86faf769f", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-38e05c61180e343f5159", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1ea3970e862b2e67fa1a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-add768ddd18ef109ba3c", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e430845d44e9710235fd", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d2c64405c088bf18c14e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cee54e049ced73227249", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-02f2a85e229574cfb475", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b37a19d7a73931aabb41", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-42d069e90ca9bed66e16", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-930851c52bb079277df7", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-25683d64360d402a6cfb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d733a530778d1239f820", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-733fec2b72ddd360a433", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b1b9e5e7d9a83ff2f575", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fac853dc60ade5da2f75", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2c3a8ea1885f109f776e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-899d2266a81f4a7c01d9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-85b0c3d8933e60ea1613", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c2f092775425b86b65cb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-475891ef257243f0abce", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-598dafabb47fd31ea7e6", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-844343a318344b0484f7", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c92159394b3129fe725e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f9025f84c102f5b735c8", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-996a2c73dffbc1008f78", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8965f7ce445f587d058e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4ee9855a2dba216d9d0a", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7e10c6570e5686569f5b", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cf606fed0f80df3731d3", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-14a54c543698a121a029", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-54f095d0f28098aef600", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-19b1864795355c002ed9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-97fb98d1e4249c9a44c5", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5d2c750da43a4c70951c", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1356fc0993b064221dbf", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b79067fbfb7c2ff491e9", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-999c9cf8fc5b5d85d611", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-98736c9e85efeafdc69b", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c5c74324f69bbb5bea58", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ff441a7018e64303142e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dbb11e5d2bfdfc771ccb", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-db0cd4b10943614faf13", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-658ff7c81eb0bd510200", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-94156305cb6b0a2c452e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-168f8289b72ec00554fa", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ff292d411bbad1b3c8ad", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9b7e8c219e71763d94d7", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-930e67c69107c7498a3e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d056c582ecb0c480dc24", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-af9f2b53d531d678a3a2", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fdd9089d7669aaa55232", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-417431dafd90495fc212", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c0140d78f7e55267502e", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-57faebdf7158b2f88520", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9fa28ea5189389dcf715", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-12a736aaecf8b2777aef", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3dcb160c4b05cb507700", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3182cf13cdc0a2de7b78", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9929c0300fd35d03d2ed", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0b7cbf965394e28c75ae", + "work_ref": "ref:机战无限_亦醉", + "source_ref": "小说清单/机战无限_亦醉.txt", + "expected_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "actual_source_sha256": "89fa18123dbba68faa8b7564e787a1cb1698f42e366486009e03e7c1b40da8a9", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b30a87cf71392d137539", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fbc6d3303a7abc9cb790", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-acad535daec02fcb7ccb", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d2999fbb230cb1691651", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-443ad1d9b8a13bf47b3e", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0ba76378687e76b60368", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ce13d719212d99f7a133", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-33db2cad6dc59a9b7bac", + "work_ref": "ref:机武风暴_骷髅精灵", + "source_ref": "小说清单/机武风暴_骷髅精灵.txt", + "expected_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "actual_source_sha256": "31941dac8bde92c7565b0fe5e6191bc121f83eccaaa74d824fda0cfd12ac423c", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-16eb0c79e95a18f455de", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0284f3a370df4f9a5fb2", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5d11b6e4b0e1b5820b6f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-519d673a970585f2fe05", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d23c2b6df88f3f9f8318", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-56af02c2b13c88e8e40a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-15e687ee531b92522e6f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c57f42bdeed02eabf866", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-75684db95a7870b3e0a9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b1fb23d2c39b2237d6ff", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-65c2be95115c3f209b65", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6aeee3b426cb46cbf62b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5fc5722ae0edcb46f6f7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3301a29974ccbf21a3f8", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c81c874fd0f45dad2a00", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f92879fef1e25c01ea3c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-adbde45d31e78122f69b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6ddedb6d60a9d5668a56", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8025007419657d16638f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ed075592f8ec0107cf1f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ee41b43c932bbd2e27c9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-410095fad2eef1d0ca5f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ad42535298a0605dbf1d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c9f63777e44edcd9bec2", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dd6bf4b0e11f56b63cfe", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a756828cb6edfba80905", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-545719e3a88b77406b59", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cebb161d6a05196cd528", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e4ef5b04b18e3b6ca89d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bad515569a15296c94d5", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bf55cbe9b9b6793b96e4", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f78c377cba46897b3b7a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c481a497d91fb7649b6c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-696a35ed0c0256b720a4", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ccb13147faf73c183847", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4adce2fe63bf0623d4f7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0c332509189fc937cfb8", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f020817237399910725b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-482d15d28686782d1239", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-30621b6a23a4fe26677f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cb3cf8e1362e08c9c057", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-72d3f0aa8cfc33f34e57", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-609110783a02c03a5fe6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7eada7f28eff3c01e24f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-597afe53ffd7b8712a7d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-29d2daec5b13bc3d7cd7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a5d7a8abaca0779a6afc", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5c1a11a80c7d1ec5a5ac", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9742df0f67300569c03c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-45dcd73146c2eabeb78b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-39a815362f87cee51141", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b96150c28fd364de1e5c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1e0604177991726d48be", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4d60ee1759a49e70fcce", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5b36ce815235b91b33f0", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7b9ebc09d324210f4673", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fabd0d7c3b0673578790", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4a15da0fcaa6abb41c89", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7be3d05b64efa2ff73fc", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d43bbc96a9c53feda783", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7c4309055bdac62cee70", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-872936b69b51a93615bf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bf731fc816d910c80c8a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-31497a7132139d0086b7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-73f09659b8c7a53196f9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-491199e73afe571d5420", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-abfeb826b1d4ebd295e9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e5d37049d3027af37e24", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-76981a52f242f325e6dc", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ac86fd04a10256da32f0", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b019d6dc364d519886ea", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8e5522b5555575204015", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9543039879daecd85a09", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-966217a07ad667722aa3", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4407ed5c01ff8cde21af", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6ded300cc937aad47b54", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dbd6935acd76e8a8d68f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-652d1eff81e43dca2bfb", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1617e188c4874640b1c9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8242186704f8d9d2b4be", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-eb8cac6799d38d9ad057", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4671eae221355e2980c6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-91d287ece4396ece8c8c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8959f5deb104e31b3b8a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0cf8dfc92025218f10ac", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4499cfa56fd7cd4c1e14", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0cb89e73e7354318d083", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-654640f6fb3b86f352dd", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cad66651985747ae77c3", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-45a91df162800231e426", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6b53c61e74eabb1d270a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-58675b0a577a69953e57", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c6bee125702be23e433f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5dc08ee5ebea70c3b98c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-83c9a799ecf3b7bceb64", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cf42256800cdd57b7c57", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a2a102b407431681efe5", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-79502108ec559f85d262", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f8e0047480caa471e4cb", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-eba6e6693a10b71b7b09", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-057fb21c0eff76610884", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2e96a4029ee1aaacc4f0", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d623ad327b95f4b14512", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a700d14ebc85b1f81675", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-88136e213be7b5e0d13d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-014127dddfd7fa2e924f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-58a34a10101a4a4e3e72", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0c0f05d4590aa4cff2c8", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bae1c8b3df1cab4a947d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6130ef9b8ce3d246508e", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fbd5cb77953c3bea0458", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bc4ef6fa068f658297c8", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9ecba996455290ea522e", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ed0464d719c163c7b3c6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-76b6a2c8987668e7c762", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-86a44b7dad3efba090e6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-15a153ee3ac14a22e4c6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5ca75480f09fbccb1452", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cbc9d14b18ec603658ea", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dc53f8b488dc84645a42", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cdeef87e9b9b0494f734", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c73291d2ba1bb933f57b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fff1683eddb9f4ec5058", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b7e864817ad8218e8237", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5e6b528e9ec1d14ecad6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-18d963170e366f0cd0ab", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-35e22e7d5e8e875b0e34", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0dd0de105e5daec12f9c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-970cffb05a4287317c9c", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dc9c1d786df04a4af16e", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f9b80d25d773d2e8693e", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d647797cf8305e18d596", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a660ebcbf2bad5757a12", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f3e824c908a4face0e82", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f6eff94768791a953d0a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bed2b1b03376bee80dcf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-85fda750cc76895dfa0f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1ca78a2a22ee15790239", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-aa57848989ab453a1ee3", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b6b286ea75e6dece31c4", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c0e956146506ef113c9b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ca55ca8e4cc01e54d68f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-95d633055a0cfde88195", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1f5fb8560b95883a5d5b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4f556791056a4dc317e4", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9f80c30d8c8cf1f64882", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bd259336340f322f383b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3f09005a6775793ec948", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a16f89b235a313106c83", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-621ce774bfe72f8dafbf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ed562689316130d1d9ef", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-de3d6ea91f6cf0454acb", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bc289218bf44bf086b80", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-460788291a49080197ce", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4ec54cb626d1eb8178d9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3851362a493d8392a8b8", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e46207863d0c11d26052", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fdc58decb8d6ec97a148", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2314f3f5070dc2a452a1", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-436e2bd2943051a10521", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b04dfc15971712189382", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c308da3420664a6a385a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1c922f97c2cc3ecb73f3", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1e2abfc7b4ba4e4f4ef0", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-abfa9ac5d11b240f6b19", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b7f91e75cf4593b27c46", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a79d2217ab283e1c675d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5a9887836614bf09c883", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-378a33a225f659aea4cf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8db1be42a31369e74562", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c5a14b30759b24f0fb82", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-aa0a61ee2351182cd3b7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8cdd5899aab40a8f992b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5850045ab654e94703bf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c5a7f9f1203619c3a97f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4c99eb383510acf6b3d1", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-daddac5d495dc04baef5", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3933455581d83742ffce", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4e2377212d721cab36c2", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ed115c42de15598749d0", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-daee268df40a524e0cf9", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ce0b613827d3c6f2c897", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d2fc63ad11a361cc03df", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4a78379097ea20adc930", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-99b2f9eb6f327e325abf", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a4903002525cdfd3977e", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e6c4a69f3f4e3f4ab35a", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-19a6219f996f2f02bce7", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-074ac7005d064e17fa81", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a6fb2f82ef55cc70d896", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3fbd14e063dc677c1dc3", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-73f4882bc7ea052f90ce", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1979d984cf6ca2f98b2d", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ff4487114a978d4d1dad", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b4fc1acc3e5e4c1f7a32", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-279cae1e44465557e872", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-94bb1cf938079b8824e6", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-13c2a577a15bef5ab1e1", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-035cda977cfd30941098", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2e17fd357ff3a28da08b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1224f36ff2b65a726897", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d6285297f2e8435d1e9b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a76a49da0759113d7125", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-594a0678b139c949caa5", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1b851a6d5a682b376653", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e5d0af41b2d188d95c54", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-901acb498a585afcd3ea", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a0b72ca936ebfc657195", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-be9025547554acc4dc7f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-50c19713c6cbfd8cc340", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f6dc850263637b7a7747", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9c74b0894d0ec20fa332", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-47a346dc87d8aee549bb", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-04ca38c795ea66e5b2bb", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cc390448cdc267287e76", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-89747d4ffc5c69ff0b90", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8f66b9dc29fcccca3bcc", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-69b1047bf6836315fc5f", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c69239cad88c4f6c0461", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-29fe5e1f8ec3f1b2b4ad", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-89a60cedb8dbde120f7b", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ec461e52699b2d496d58", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-68d15115e1591e03d749", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1005cba695da5e3b1162", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8681515e7562afd87481", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-adaaf2019cfdd0fc96dc", + "work_ref": "ref:机破星河_当年离歌", + "source_ref": "小说清单/机破星河_当年离歌.txt", + "expected_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "actual_source_sha256": "b146aff2bcd8ab14472490fdbf5223d2752892aaab6830ca90740cf0707a0c1e", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e6611623fdb554c60ad8", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-449b971f6a691adc74a3", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-94fe4fc9899c93259dd8", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-884c7684c32c9c4e1bab", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ee40372718a4f6719c40", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fec973d9bad780e93d91", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8cbe2bfbe63962809582", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1460e1fd09ef5338bc64", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-27ee6bf97dc3c60e33a9", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ce14c4941f013b57dd6a", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7f92799bafc2ed35c270", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c99e851ba3d8664f8df1", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-321e542b04bb17e8cb2f", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3598365cd2d2b5621517", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b006f5f25fb0efbdb4a5", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-49a4f0f4cece94868071", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-127723ef12ba6cb9a725", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-668b49def402c74dcaf2", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-28f3d3d370e3469727ad", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6998e228d86782af25dd", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-983dd4cdd451b171c469", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-506379ac50124239be55", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7ce840f4069248cfca44", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a992da75a1813d54b0a6", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bbe84972321ed7a069e4", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-efe938cfebde3c14ef06", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e2b5fefd83a47adabcc3", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1313b56d8a7c796db5b2", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-982196bb13366be180df", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8f1fb26a4fab8bf306ef", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-27fd2aabf3305f0fd927", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fb82a24f6bb4dd98d25b", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d44eefdb19c1180f3a70", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c095e1786909b0ac15b7", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-51a52132363c149ad57b", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f1d3e2f48009293bb436", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5a7176069f4fbb304c61", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0afe6db0c709354be341", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ccdbe51578230df7c6cb", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ad486739ae14d97af4d7", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c2e48ecd1e6b83948374", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-79b22217b294b7245189", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5af2290d18f1f66942ac", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-920ed053f3931cea0364", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-6c39e65d425b174456bb", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fb80c3c3a3c3ee82d986", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-186948b122a36fa148a7", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ec83fa8c2a3d9b562077", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-99d6e0b73022c2197240", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5945f0428bcca631f457", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-343212960d95148e9677", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5fd334da2e6f3a96d6b7", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-78241989da0df7b40ea9", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a19415929810d195c7cb", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8962e30e17c9e423827c", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ba4f07ca6999a5544288", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-10b48ac6d1c0b698f11d", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ba9c03f9a2b7052e8edc", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-26699d6c9a956755d57d", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-92e1e22b1e0d5b18004e", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9c20458f14c39f0f5116", + "work_ref": "ref:深空之影_虚伪王庭", + "source_ref": "小说清单/深空之影_虚伪王庭.txt", + "expected_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "actual_source_sha256": "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-857ce58e56901fe328c1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4c7397f0c006e090812d", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0c954571cfdbd6c91e49", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9fcbeff0b5316aa7fb80", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b44deb7e1b89ef6c6a37", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-278c677fdf023e015e9a", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ec60e34b8d434e8002fa", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d908f3c7dc6be1dc982e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bb0e9ff894b7bfee6243", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-184f186d0e2ffb07860e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-76423f80e98a35dd7ca0", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a9ad2921991dd528f521", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fe1964fc96784bf9d553", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-26a1be82c6200170ef99", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-84de968407b83988f413", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-926e5cd7957558fa908c", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f3c22a5b640ad4d1c9c7", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-60ccf052c615b10bdd13", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c7979b97ba01344fb47d", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9f611f9780b9ea0c3111", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-17363e98cf3d9933edcc", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-62d684fe8ac09fd000a2", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-82d5030d18647b63495c", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4a1d87c4c5c862003a6f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9dced3ba391c76acbb4b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8745ec0f4b4b8117eb94", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-24a106d57b25a5b38bbf", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-60763079415d174a265b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ba2eb96fa75e230bfedc", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2e1f86b35007de382e8f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-2b9e85b48a1e9af5ba13", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-cca6f21a621d67af88c1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-e13cb411697f24a79921", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1d5ce876de86b3e17137", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-77e28fe836558e8b335e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-faa01b974e4878e8fc0f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-10a325d54924cf1a95a4", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7844997985205594cac3", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8480478ba0272403e96f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-06011a827e929f28f9bd", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7d2e6a3f508b603ffd95", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f299f5beb230e1e96a0e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8baac57b6ef1b1630785", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4fe57aa4cd9a05045ee8", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-02828d08a593171fac85", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0e1d024dd0eae08ae37b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c8c0438542f120724c55", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fd90cf522e71065e8675", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d9d26f9c051e351f273f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-1ffa7187b45a39c4b191", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bd6d2074bba2e9b98d95", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-18f67c9419a1161a552a", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f94fcaf52cf5fffeff74", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f4dcd62a379f91e35fc8", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-36bec4d7594d00221c40", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a3c59dbca1b3fc0dd3b1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c2ffd30113e6af80831e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fed55e3f18ab2166f9f2", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-80113d4e58d4bb004a16", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ca6632c9107d155fc2dc", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-8120173409711e11ff84", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-46c6c32a5b99009979fd", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-247946a9225606723ef2", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-21eef9d3a699f08034b0", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-4047e630e22523d097b1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-31fdca7efb5535dafaed", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-798e07e86c54b797914a", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bab62782a037bd65f9c3", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-c44507124c96d2ba80c6", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-62a68a4abd0046a435df", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-87de709cfd019988a3db", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7cac975ca8e04c3d816f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-552147feb61bdb5a0b8a", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-98ad1ad4dbb9eaf077c5", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-7f62e2f7783d942da97f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5c750e707f2f2022d3f1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-5bd580b5701b64ae09b7", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-173a47d76dc1585d5ea8", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-80e3c24e9614014a182e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-277e98324c0a20101c7e", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0c651974df1d06d345cb", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-9f12ca40e843ad69ba6b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-dd5b2f0ce24c4b42662c", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d3905a9ba8b4fc9de803", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-698059f6f24f4e5c0466", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-a9e77774fd85e03ccac4", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3b015cafcdbd53e07e07", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-03c83ad0cc99e57527c1", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-802ff85ce8bcd8f06147", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-fd05c92b674ff2508bfe", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-46d51648b52a2fd4072f", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-731c758be6d14d3711ed", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f13edbc884c4b706f516", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3551353d40fc60b2a5a3", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-bb1a168b0a84fb536fa6", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-eb44273fdf305b7c523b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-0b0c6aa047c3b63e6849", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-47778e0a9cf3d87391a0", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-951180a59aba43c7d85b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ca436c1bf811ecffcd65", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f0f3798546c0354ec3e4", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-ec5e3ed72b1407b65694", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-3c19a4fd5c456581092b", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d34cec1a0d7bf9694cc4", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-215e451b18f7e5ce3d03", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-138005835cbd73662a62", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-b80493a46fd55b439121", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-24f1c2e940e3ce38bdad", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-f24d1b68dea22402c3fe", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-68886c0f908748532bb6", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-83d3d0e03f2ef8b1ff95", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + }, + { + "card_id": "case-d24540303827ac247d87", + "work_ref": "ref:超神机械师_齐佩甲", + "source_ref": "小说清单/超神机械师_齐佩甲.txt", + "expected_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "actual_source_sha256": "45ee9cc70dfca06b37f8b83acb8683f0bb4898d74276fc5874fd438058318683", + "status": "verified", + "reason": "source_hash_and_anchor_match", + "checked_on": "2026-08-14" + } + ] +} diff --git a/.claude/skills/capture-ai-flavor-cases/references/fixtures/rule-candidates.yaml b/.claude/skills/capture-ai-flavor-cases/references/fixtures/rule-candidates.yaml new file mode 100644 index 0000000..308d526 --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/references/fixtures/rule-candidates.yaml @@ -0,0 +1,13 @@ +schema_version: ai-flavor-rule-candidate-v1 +rules: + - id: candidate-lexical-meta-disclaimer-v1 + name: 空洞元话语候选 + status: candidate + default_disposition: candidate + layer: lexical + carrier_scope: narration + trigger: {type: phrase_family, pattern_key: lexical.meta_disclaimer} + fix_hint: 先检查信息、声线和节奏功能;无功能时删除或改成具体动作,不能按词表硬删。 + case_card_ids: [case-synthetic-meta-001, case-synthetic-meta-002] + source_work_refs: [synthetic-work-a, synthetic-work-b] + evidence: 两个来源同时提供 sf 与 snf;仅作为候选,未进入生产规则。 diff --git a/.claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py b/.claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py new file mode 100644 index 0000000..d35c301 --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/scripts/capture_cases.py @@ -0,0 +1,988 @@ +#!/usr/bin/env python3 +"""AI 味案例卡的确定性发现、自动落库与候选规则构造。 + +扫描/回填/创作反馈命令默认在检测完成后自动写入 agent-example 的 +``muse-example``;``--offline`` 只用于明确的离线合同测试或恢复准备。 +脚本不调用模型;语义判断由人工或独立评审补上。 +""" + +from __future__ import annotations + +import argparse +import copy +import hashlib +import json +import re +import sys +from collections import Counter +from datetime import date +from pathlib import Path +from typing import Iterable + +import yaml + + +# 作为 CLI 执行时也注册稳定模块名,自动落库模块复用同一份合同异常类型, +# 避免失败路径被重复 import 变成未捕获 traceback。 +if __name__ == "__main__": + sys.modules.setdefault("capture_cases", sys.modules[__name__]) + + +SCHEMA_VERSION = "ai-flavor-case-v1" +CARD_TYPE = "ai_flavor_case" +REVALIDATION_SCHEMA_VERSION = "ai-flavor-revalidation-v1" +STATES = {"shadow", "canonical", "rejected", "archived"} +LABELS = {"unclassified", "sf", "snf", "boundary", "regression"} +LAYERS = {"mechanical", "lexical", "structural", "density", "semantic", "unknown"} +CARRIERS = {"narration", "dialogue", "monologue", "in_text_carrier", "mixed", "unknown"} +CAPTURE_MODES = {"backfill", "live_feedback", "synthetic", "review_import"} +REVALIDATION_STATUSES = {"verified", "stale", "unavailable", "card_mismatch"} +LICENSES = {"owned", "licensed", "public_domain", "research_only", "unauthorized", "synthetic"} +NON_REPO_LICENSES = {"research_only", "unauthorized"} +STOREABLE_LICENSES = LICENSES - NON_REPO_LICENSES + + +class CaseCardError(ValueError): + """案例卡合同或升级门禁失败。""" + + +PATTERNS = ( + { + "key": "lexical.meta_disclaimer", + "layer": "lexical", + "pattern": r"(?:值得注意的是|值得一提的是|不言而喻|众所周知|换句话说)", + "diagnosis": "叙述者元话语可能没有新增信息;必须检查是否承担声线或节奏功能。", + }, + { + "key": "lexical.stock_micro_expression", + "layer": "lexical", + "pattern": r"(?:嘴角(?:微微|轻轻|悄然)?(?:上扬|勾起)|眼中闪过(?:一丝|一抹)?(?:光|精光|异彩)|眸光(?:微闪|深邃))", + "diagnosis": "库存微表情候选;必须检查是否是角色签名动作或场景独有反应。", + }, + { + "key": "lexical.abstract_atmosphere", + "layer": "lexical", + "pattern": r"(?:一股[^。!?\n]{0,24}气息[^。!?\n]{0,24}(?:弥漫|袭来|扑面而来)|空气仿佛凝固)", + "diagnosis": "抽象气氛候选;必须检查感官细节、因果和场面功能,不能看到词就删除。", + }, +) + + +def _sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def text_sha256(text: str) -> str: + return _sha256_bytes(text.encode("utf-8")) + + +def _resolve_source_path(source_ref: str, source_root: Path | None = None) -> Path | None: + """把脱敏来源引用解析为受控本地文件;URI 和无法落到文件的引用返回 None。""" + if not source_ref or "://" in source_ref: + return None + ref = Path(source_ref).expanduser() + if ref.is_absolute(): + resolved = ref.resolve() + if source_root is not None: + try: + resolved.relative_to(source_root.expanduser().resolve()) + except ValueError: + return None + return resolved + if source_root is None: + return None + root = source_root.expanduser().resolve() + candidates = [root / ref] + # inventory 默认把 source_root_ref(例如“小说清单”)写进 source_ref; + # 调用方也可以直接把 source_root 指到该目录,因此兼容两种入口。 + if ref.parts and ref.parts[0] in {root.name, root.parent.name}: + candidates.append(root / Path(*ref.parts[1:])) + for candidate in candidates: + resolved = candidate.resolve() + try: + resolved.relative_to(root) + except ValueError: + continue + if resolved.is_file(): + return resolved + return None + + +def _source_snapshot(*, source_ref: str, source_root: Path | None = None, + source_path: Path | None = None, source_text: str | None = None) -> dict: + """读取一次来源,供单卡和批量重验证复用。""" + if source_text is not None and source_path is not None: + raise CaseCardError("source_text 与 source_path 不能同时提供") + if source_text is not None: + return {"status": "loaded", "actual_sha256": text_sha256(source_text), "text": source_text, "reason": ""} + path = source_path.expanduser().resolve() if source_path is not None else _resolve_source_path(source_ref, source_root) + if path is None: + return {"status": "unavailable", "actual_sha256": None, "text": None, "reason": "source_ref_not_file"} + try: + raw = path.read_bytes() + except OSError: + return {"status": "unavailable", "actual_sha256": None, "text": None, "reason": "source_not_found"} + actual_sha256 = _sha256_bytes(raw) + try: + text = raw.decode("utf-8") + except UnicodeError: + return {"status": "unavailable", "actual_sha256": actual_sha256, "text": None, + "reason": "source_not_utf8"} + return {"status": "loaded", "actual_sha256": actual_sha256, "text": text, "reason": ""} + + +def _card_anchor_reason(card: dict, text: str) -> str | None: + """在全文哈希相同后,再检查卡片的位置和命中是否仍自洽。""" + source = card["source"] + location = source.get("location") or {} + start, end = location.get("char_start"), location.get("char_end") + if not isinstance(start, int) or not isinstance(end, int) or start < 0 or end < start or end > len(text): + return "card_anchor_out_of_range" + if text_sha256(text[start:end]) != source["excerpt_sha256"]: + return "card_excerpt_hash_mismatch" + surface_location = source.get("surface_location") + if surface_location: + surface_start, surface_end = surface_location.get("char_start"), surface_location.get("char_end") + expected_surface = card.get("observation", {}).get("surface", "") + if (not isinstance(surface_start, int) or not isinstance(surface_end, int) + or surface_start < 0 or surface_end < surface_start or surface_end > len(text)): + return "card_surface_anchor_out_of_range" + if text[surface_start:surface_end] != expected_surface: + return "card_surface_mismatch" + return None + + +def _revalidation_result(card: dict, snapshot: dict, *, checked_on: str) -> dict: + source = card["source"] + result = { + "card_id": card["id"], + "work_ref": source.get("work_ref", ""), + "source_ref": source.get("source_ref", ""), + "expected_source_sha256": source["source_sha256"], + "actual_source_sha256": snapshot.get("actual_sha256"), + "status": snapshot["status"], + "reason": snapshot.get("reason", ""), + "checked_on": checked_on, + } + if snapshot["status"] == "loaded": + if snapshot["actual_sha256"] != source["source_sha256"]: + result["status"] = "stale" + result["reason"] = "source_hash_mismatch" + else: + reason = _card_anchor_reason(card, snapshot["text"]) + if reason: + result["status"] = "card_mismatch" + result["reason"] = reason + else: + result["status"] = "verified" + result["reason"] = "source_hash_and_anchor_match" + return result + + +def revalidate_card(card: dict, *, source_root: Path | None = None, source_path: Path | None = None, + source_text: str | None = None, checked_on: str | None = None) -> dict: + """重新读取来源并返回不可变验证结果;不会自动改写案例卡状态。""" + validate_card(card) + snapshot = _source_snapshot(source_ref=card["source"].get("source_ref", ""), + source_root=source_root, source_path=source_path, source_text=source_text) + return _revalidation_result(card, snapshot, checked_on=checked_on or date.today().isoformat()) + + +def revalidate_cards(cards: list[dict], *, source_root: Path | None = None, + checked_on: str | None = None) -> list[dict]: + """批量重验证;同一来源只读取一次。""" + checked_on = checked_on or date.today().isoformat() + snapshots: dict[str, dict] = {} + results = [] + for card in cards: + validate_card(card) + source_ref = card["source"].get("source_ref", "") + if source_ref not in snapshots: + snapshots[source_ref] = _source_snapshot(source_ref=source_ref, source_root=source_root) + results.append(_revalidation_result(card, snapshots[source_ref], checked_on=checked_on)) + return results + + +def build_revalidation_report(cards: list[dict], *, source_root: Path | None = None, + checked_on: str | None = None, + source_texts: dict[str, str] | None = None) -> dict: + if source_texts is None: + results = revalidate_cards(cards, source_root=source_root, checked_on=checked_on) + else: + results = [] + for card in cards: + if card["id"] not in source_texts: + raise CaseCardError(f"缺少案例卡 {card['id']} 的反馈正文") + results.append(revalidate_card(card, source_text=source_texts[card["id"]], checked_on=checked_on)) + counts = Counter(result["status"] for result in results) + works: dict[tuple[str, str], dict] = {} + for result in results: + key = (result["work_ref"], result["source_ref"]) + work = works.setdefault(key, { + "work_ref": result["work_ref"], "source_ref": result["source_ref"], + "expected_source_sha256": result["expected_source_sha256"], + "actual_source_sha256": result["actual_source_sha256"], "card_count": 0, + "status_counts": {}, + }) + work["card_count"] += 1 + work["status_counts"][result["status"]] = work["status_counts"].get(result["status"], 0) + 1 + if work["actual_source_sha256"] is None and result["actual_source_sha256"] is not None: + work["actual_source_sha256"] = result["actual_source_sha256"] + return { + "schema_version": REVALIDATION_SCHEMA_VERSION, + "checked_on": checked_on or date.today().isoformat(), + "source_root_ref": source_root.name if source_root else None, + "usable": bool(results) and all(result["status"] == "verified" for result in results), + "totals": {"cards": len(results), **{status: counts.get(status, 0) for status in sorted(REVALIDATION_STATUSES)}}, + "works": list(sorted(works.values(), key=lambda item: (item["work_ref"], item["source_ref"]))), + "cards": results, + } + + +def _verification_results(verification) -> dict[str, dict]: + if not isinstance(verification, dict): + return {} + if verification.get("card_id"): + return {verification["card_id"]: verification} + items = verification.get("cards") + if not isinstance(items, list): + return {} + return {item.get("card_id"): item for item in items if isinstance(item, dict) and item.get("card_id")} + + +def require_verified(card: dict, verification) -> dict: + """升级或消费前的 fail-closed 门:只接受本卡的 verified 回执。""" + validate_card(card) + result = _verification_results(verification).get(card["id"]) + if (not result or result.get("status") != "verified" + or result.get("expected_source_sha256") != card["source"]["source_sha256"] + or result.get("actual_source_sha256") != card["source"]["source_sha256"]): + status = result.get("status") if result else "missing" + raise CaseCardError(f"案例卡 {card['id']} 来源未通过重验证: {status}") + return result + + +def require_verified_cards(cards: list[dict], verification) -> list[dict]: + return [require_verified(card, verification) for card in cards] + + +def _line_col(text: str, offset: int) -> tuple[int, int]: + line_start = text.count("\n", 0, offset) + 1 + last_newline = text.rfind("\n", 0, offset) + return line_start, offset - (last_newline + 1) + + +def _location(text: str, start: int, end: int) -> dict: + line_start, col_start = _line_col(text, start) + line_end, col_end = _line_col(text, end) + return { + "line_start": line_start, + "line_end": line_end, + "char_start": start, + "char_end": end, + "column_start": col_start, + "column_end": col_end, + } + + +def _context(text: str, start: int, end: int) -> str: + """取命中所在行及相邻一行,供有权保存的来源做人工复核。""" + line_start = text.rfind("\n", 0, start) + 1 + line_end = text.find("\n", end) + if line_end < 0: + line_end = len(text) + previous_start = text.rfind("\n", 0, max(0, line_start - 1)) + 1 + next_end = text.find("\n", line_end + 1) + if next_end < 0: + next_end = len(text) + return text[previous_start:next_end].strip("\n") + + +def _evidence_window(text: str, start: int, end: int, max_chars: int = 180) -> tuple[int, int]: + """取命中词所在句的有限窗口,避免样例只有触发词或吞入整章。""" + left = max(text.rfind("。", 0, start), text.rfind("!", 0, start), + text.rfind("?", 0, start), text.rfind("\n", 0, start)) + 1 + right_candidates = [p for p in (text.find("。", end), text.find("!", end), + text.find("?", end), text.find("\n", end)) if p >= 0] + right = min(right_candidates, default=len(text)) + if right_candidates: + right += 1 + if right - left > max_chars: + left = max(0, start - max_chars // 2) + right = min(len(text), max(end, start + max_chars // 2)) + if right - left > max_chars: + right = left + max_chars + return left, right + + +def _require_string(value, field: str, *, allow_empty: bool = False) -> str: + if not isinstance(value, str) or (not allow_empty and not value.strip()): + raise CaseCardError(f"{field} 必须是{'可为空的' if allow_empty else ''}字符串") + return value + + +def validate_card(card: dict) -> dict: + """验证单卡的字段和跨字段不变量。""" + if not isinstance(card, dict): + raise CaseCardError("卡片必须是对象") + for key in ("schema_version", "id", "card_type", "state", "label", "layer", "carrier", "capture_mode", "source", "observation"): + if key not in card: + raise CaseCardError(f"卡片缺少 {key}") + if card["schema_version"] != SCHEMA_VERSION: + raise CaseCardError(f"schema_version 必须为 {SCHEMA_VERSION}") + _require_string(card["id"], "id") + if not card["id"].startswith("case-"): + raise CaseCardError("id 必须以 case- 开头") + if card["card_type"] != CARD_TYPE: + raise CaseCardError("card_type 必须为 ai_flavor_case") + for field, allowed in (("state", STATES), ("label", LABELS), ("layer", LAYERS), ("carrier", CARRIERS), ("capture_mode", CAPTURE_MODES)): + if card[field] not in allowed: + raise CaseCardError(f"{field} 取值非法: {card[field]!r}") + + source = card["source"] + if not isinstance(source, dict): + raise CaseCardError("source 必须是对象") + for key in ("kind", "license", "source_sha256", "excerpt_sha256", "location"): + if key not in source: + raise CaseCardError(f"source 缺少 {key}") + if source["kind"] not in {"existing_work", "creation_feedback", "synthetic", "public_domain"}: + raise CaseCardError(f"source.kind 非法: {source['kind']!r}") + if source["license"] not in LICENSES: + raise CaseCardError(f"source.license 非法: {source['license']!r}") + for key in ("source_sha256", "excerpt_sha256"): + if not re.fullmatch(r"[0-9a-f]{64}", source[key]): + raise CaseCardError(f"source.{key} 必须是 64 位小写 SHA-256") + if source["kind"] == "existing_work" and not source.get("work_ref"): + raise CaseCardError("existing_work 必须有 work_ref") + if card["capture_mode"] == "live_feedback" and source["kind"] != "creation_feedback": + raise CaseCardError("live_feedback 的 source.kind 必须为 creation_feedback") + location = source["location"] + if not isinstance(location, dict): + raise CaseCardError("source.location 必须是对象") + for key in ("line_start", "line_end", "char_start", "char_end"): + if not isinstance(location.get(key), int) or location[key] < 0: + raise CaseCardError(f"source.location.{key} 必须是非负整数") + if location["line_end"] < location["line_start"] or location["char_end"] < location["char_start"]: + raise CaseCardError("source.location 结束位置不能早于开始位置") + + _require_string(card.get("excerpt", ""), "excerpt", allow_empty=True) + _require_string(card.get("context", ""), "context", allow_empty=True) + if source["license"] in NON_REPO_LICENSES: + if card.get("excerpt") or card.get("context"): + raise CaseCardError("未授权/研究限定来源必须 hash-only") + if card["state"] != "shadow": + raise CaseCardError("未授权/研究限定来源只能保持 shadow") + if card["state"] == "canonical": + if source["license"] not in STOREABLE_LICENSES: + raise CaseCardError("canonical 卡必须来自可保存的授权来源") + if not card.get("excerpt"): + raise CaseCardError("canonical 卡必须有可审阅片段") + if card["label"] == "unclassified": + raise CaseCardError("canonical 卡必须有评审标签") + review = card.get("review") + if not isinstance(review, dict) or not review.get("reviewer") or not review.get("note"): + raise CaseCardError("canonical 卡必须有 review.reviewer 和 review.note") + if card.get("excerpt") and source["excerpt_sha256"] != text_sha256(card["excerpt"]): + raise CaseCardError("excerpt_sha256 与 excerpt 不一致") + + observation = card["observation"] + if not isinstance(observation, dict): + raise CaseCardError("observation 必须是对象") + for key in ("pattern_key", "surface", "diagnosis", "function_check", "risk_if_changed", "suggested_action"): + if key not in observation: + raise CaseCardError(f"observation 缺少 {key}") + for key in ("pattern_key", "surface", "diagnosis", "risk_if_changed", "suggested_action"): + _require_string(observation[key], f"observation.{key}") + if not isinstance(observation["function_check"], list) or not observation["function_check"]: + raise CaseCardError("observation.function_check 必须是非空数组") + return card + + +def _card_id(source_sha: str, start: int, end: int, pattern_key: str) -> str: + raw = f"{source_sha}:{start}:{end}:{pattern_key}".encode("utf-8") + return "case-" + _sha256_bytes(raw)[:20] + + +def build_case_card(*, text: str, source_sha256: str, source_kind: str, source_license: str, + source_ref: str, work_ref: str | None, start: int, end: int, + pattern: dict, capture_mode: str = "backfill", feedback: dict | None = None, + excerpt_start: int | None = None, excerpt_end: int | None = None) -> dict: + evidence_start = start if excerpt_start is None else excerpt_start + evidence_end = end if excerpt_end is None else excerpt_end + excerpt = text[evidence_start:evidence_end] + stored = source_license in STOREABLE_LICENSES + source = { + "kind": source_kind, + "license": source_license, + "source_sha256": source_sha256, + "excerpt_sha256": text_sha256(excerpt), + "source_ref": source_ref, + "location": _location(text, evidence_start, evidence_end), + "surface_location": _location(text, start, end), + } + if work_ref: + source["work_ref"] = work_ref + card = { + "schema_version": SCHEMA_VERSION, + "id": _card_id(source_sha256, start, end, pattern["key"]), + "card_type": CARD_TYPE, + "state": "shadow", + "label": "unclassified", + "layer": pattern["layer"], + "carrier": "unknown", + "capture_mode": capture_mode, + "excerpt": excerpt if stored else "", + "context": _context(text, start, end) if stored else "", + "source": source, + "observation": { + "pattern_key": pattern["key"], + "surface": text[start:end], + "diagnosis": pattern["diagnosis"], + "function_check": ["是否承担具体叙事功能", "是否是角色/场内载体的有意声线"], + "risk_if_changed": "未经上下文复核直接删除可能损失人物声音、伏笔或节奏。", + "suggested_action": "保留 shadow,补充上下文后再标注 sf/snf/boundary。", + }, + } + if feedback is not None: + card["feedback"] = copy.deepcopy(feedback) + return validate_card(card) + + +def capture_file(path: Path, *, source_license: str = "research_only", source_kind: str = "existing_work", + work_ref: str | None = None, patterns: Iterable[dict] = PATTERNS, + max_cards: int = 500) -> list[dict]: + if source_license not in LICENSES: + raise CaseCardError(f"不支持的来源许可: {source_license}") + if source_kind not in {"existing_work", "public_domain", "synthetic"}: + raise CaseCardError("文件扫描 source_kind 必须是 existing_work/public_domain/synthetic") + raw = path.read_bytes() + text = raw.decode("utf-8") + source_sha = _sha256_bytes(raw) + cards = [] + seen = set() + matches = [] + for pattern in patterns: + matches.extend((match.start(), pattern["key"], pattern, match) + for match in re.finditer(pattern["pattern"], text, flags=re.MULTILINE)) + for _start, _key, pattern, match in sorted(matches, key=lambda item: (item[0], item[1])): + evidence_start, evidence_end = _evidence_window(text, match.start(), match.end()) + card = build_case_card( + text=text, source_sha256=source_sha, source_kind=source_kind, + source_license=source_license, source_ref=str(path), work_ref=work_ref, + start=match.start(), end=match.end(), pattern=pattern, + excerpt_start=evidence_start, excerpt_end=evidence_end, + ) + if card["id"] not in seen: + cards.append(card) + seen.add(card["id"]) + if len(cards) >= max_cards: + return cards + return cards + + +def capture_feedback(text: str, *, work_ref: str, run_ref: str, issue: str, + source_license: str = "owned", location: dict | None = None) -> dict: + if not text: + raise CaseCardError("反馈正文不能为空") + if not work_ref or not run_ref or not issue: + raise CaseCardError("反馈必须有 work_ref、run_ref 和 issue") + pattern = { + "key": "feedback.manual_observation", + "layer": "unknown", + "diagnosis": "创作反馈待人工归类,不把一次事故直接固化为规则。", + } + card = build_case_card( + text=text, source_sha256=text_sha256(text), source_kind="creation_feedback", + source_license=source_license, source_ref=run_ref, work_ref=work_ref, + start=0, end=len(text), pattern=pattern, capture_mode="live_feedback", + feedback={"run_ref": run_ref, "issue": issue, "candidate_sha256": text_sha256(text)}, + ) + card["source"]["location"] = location or {"line_start": 1, "line_end": text.count("\n") + 1, + "char_start": 0, "char_end": len(text), + "column_start": 0, "column_end": 0} + return validate_card(card) + + +def annotate_card(card: dict, *, label: str, carrier: str = "unknown", reviewer: str = "", note: str = "") -> dict: + validate_card(card) + if card["state"] != "shadow": + raise CaseCardError("只有 shadow 卡可以标注") + if label not in LABELS - {"unclassified"}: + raise CaseCardError(f"标注标签非法: {label}") + out = copy.deepcopy(card) + out["label"] = label + out["carrier"] = carrier + if reviewer or note: + out["review"] = {"reviewer": reviewer, "note": note} + return validate_card(out) + + +def confirm_card(card: dict, *, reviewer: str, note: str, verification=None) -> dict: + validate_card(card) + if card["source"]["license"] in NON_REPO_LICENSES: + raise CaseCardError("hash-only 卡不能在仓库内确认") + require_verified(card, verification) + out = copy.deepcopy(card) + out["state"] = "canonical" + out["review"] = {"reviewer": reviewer, "note": note} + return validate_card(out) + + +def project_sample(card: dict, *, verification=None) -> dict: + validate_card(card) + if card["state"] != "canonical": + raise CaseCardError("只有 canonical 卡可以投影样例") + require_verified(card, verification) + return { + "id": "sample-" + card["id"], + "type": card["label"], + "rules": list(card.get("rule_candidate_ids", [])), + "carrier": card["carrier"], + "source": card["source"]["license"], + "text": card["excerpt"], + "note": card["observation"]["diagnosis"], + "case_card_id": card["id"], + "source_ref": card["source"].get("source_ref", ""), + "source_license": card["source"]["license"], + } + + +def propose_rule(cards: list[dict], *, rule_id: str, name: str, fix_hint: str, verification=None) -> dict: + if not cards: + raise CaseCardError("规则候选至少需要一张案例卡") + for card in cards: + validate_card(card) + # 候选仍可保持 candidate,但其证据必须先证明对应来源没有漂移。 + require_verified_cards(cards, verification) + works = {card["source"].get("work_ref") for card in cards if card["source"].get("work_ref")} + if len(works) < 2: + raise CaseCardError("规则候选至少需要两个不同来源作品") + labels = {card["label"] for card in cards} + if "sf" not in labels or not labels.intersection({"snf", "boundary", "regression"}): + raise CaseCardError("规则候选必须同时有 sf 与 snf/boundary/regression 证据") + return { + "schema_version": "ai-flavor-rule-candidate-v1", + "id": rule_id, + "name": name, + "status": "candidate", + "default_disposition": "candidate", + "fix_hint": fix_hint, + "case_card_ids": [card["id"] for card in cards], + "source_work_refs": sorted(works), + "evidence": "由多来源案例卡归纳;待四类样例、回放和独立评审。", + } + + +def load_bundle(paths: Iterable[Path]) -> list[dict]: + cards = [] + seen = set() + for path in paths: + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + items = data.get("cards", []) if isinstance(data, dict) else data + if not isinstance(items, list): + raise CaseCardError(f"{path}: cards 必须是数组") + for card in items: + validate_card(card) + if card["id"] in seen: + raise CaseCardError(f"案例卡 id 重复: {card['id']}") + seen.add(card["id"]) + cards.append(card) + return cards + + +def load_verification(path: Path) -> dict: + """读取 revalidate 产出的 JSON/YAML 回执,并做最小结构门禁。""" + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + if not isinstance(data, dict) or data.get("schema_version") != REVALIDATION_SCHEMA_VERSION: + raise CaseCardError(f"{path}: 不是 {REVALIDATION_SCHEMA_VERSION} 回执") + if not isinstance(data.get("cards"), list): + raise CaseCardError(f"{path}: 回执缺少 cards 数组") + for result in data["cards"]: + if not isinstance(result, dict) or result.get("status") not in REVALIDATION_STATUSES: + raise CaseCardError(f"{path}: 回执包含非法结果") + return data + + +def build_inventory(root: Path, *, source_license: str = "research_only", + source_kind: str = "existing_work", glob: str = "*.txt", + work_ref_prefix: str = "ref:", source_root_ref: str | None = None, + max_cards: int = 5000, generated_on: str | None = None) -> dict: + """扫描目录并生成可持久化的汇总报告及完整 hash-only 卡清单。 + + 报告保留每张卡的稳定 ID、模式、位置和来源哈希;当来源不是可保存许可时, + ``capture_file`` 已将片段与上下文清空。``source_root_ref`` 只作为脱敏后的 + 相对引用写入报告,绝不把本机绝对路径带入共享资产。 + """ + if max_cards <= 0: + raise CaseCardError("max_cards 必须为正整数") + if source_license not in LICENSES: + raise CaseCardError(f"不支持的来源许可: {source_license}") + if source_kind not in {"existing_work", "public_domain", "synthetic"}: + raise CaseCardError("目录扫描 source_kind 必须是 existing_work/public_domain/synthetic") + root = root.expanduser().resolve() + if not root.is_dir(): + raise CaseCardError(f"扫描根目录不存在或不是目录: {root}") + paths = sorted(path for path in root.rglob(glob) if path.is_file()) + if not paths: + raise CaseCardError(f"扫描根目录没有匹配 {glob!r} 的文件: {root}") + + cards: list[dict] = [] + works: list[dict] = [] + total_patterns: Counter[str] = Counter() + seen_ids: set[str] = set() + prefix = source_root_ref.rstrip("/") if source_root_ref else "" + for path in paths: + relative = path.relative_to(root).as_posix() + work_name = str(Path(relative).with_suffix("")).replace("\\", "/") + work_ref = f"{work_ref_prefix}{work_name}" + raw = path.read_bytes() + source_ref = f"{prefix}/{relative}" if prefix else relative + file_cards = capture_file( + path, + source_license=source_license, + source_kind=source_kind, + work_ref=work_ref, + max_cards=max_cards, + ) + pattern_counts = Counter(card["observation"]["pattern_key"] for card in file_cards) + total_patterns.update(pattern_counts) + for card in file_cards: + # 不改变卡片 ID;只将机器路径替换为报告中的相对来源引用。 + card = copy.deepcopy(card) + card["source"]["source_ref"] = source_ref + validate_card(card) + if card["id"] in seen_ids: + raise CaseCardError(f"目录扫描产生重复案例卡 id: {card['id']}") + seen_ids.add(card["id"]) + cards.append(card) + works.append({ + "work_ref": work_ref, + "source_ref": source_ref, + "source_sha256": _sha256_bytes(raw), + "card_count": len(file_cards), + "pattern_counts": dict(sorted(pattern_counts.items())), + }) + + return { + "schema_version": "ai-flavor-inventory-v1", + "capture_schema_version": SCHEMA_VERSION, + "generated_on": generated_on or date.today().isoformat(), + "source_root_ref": source_root_ref or root.name, + "source_kind": source_kind, + "source_license": source_license, + "max_cards_per_work": max_cards, + "totals": { + "books": len(works), + "cards": len(cards), + "pattern_counts": dict(sorted(total_patterns.items())), + }, + "works": works, + "cards": cards, + } + + +def write_yaml(path: Path, payload) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(yaml.safe_dump(payload, allow_unicode=True, sort_keys=False), encoding="utf-8") + + +def _file_sha256(path: Path) -> str: + return _sha256_bytes(path.read_bytes()) + + +def _inventory_from_cards(cards: list[dict], *, generated_on: str | None = None, + source_root_ref: str | None = None, + source_kind: str = "existing_work", + source_license: str = "research_only") -> dict: + """把单文件/反馈检测结果包装成统一 inventory 载荷,供自动落库。""" + works: dict[tuple[str, str], dict] = {} + patterns = Counter() + for card in cards: + source = card["source"] + key = (source.get("work_ref", ""), source.get("source_ref", "")) + work = works.setdefault(key, { + "work_ref": key[0], "source_ref": key[1], + "source_sha256": source["source_sha256"], "card_count": 0, + "pattern_counts": {}, + }) + pattern_key = card["observation"]["pattern_key"] + work["card_count"] += 1 + work["pattern_counts"][pattern_key] = work["pattern_counts"].get(pattern_key, 0) + 1 + patterns.update([pattern_key]) + return { + "schema_version": "ai-flavor-inventory-v1", + "capture_schema_version": SCHEMA_VERSION, + "generated_on": generated_on or date.today().isoformat(), + "source_root_ref": source_root_ref, + "source_kind": source_kind, + "source_license": source_license, + "max_cards_per_work": len(cards), + "totals": {"books": len(works), "cards": len(cards), + "pattern_counts": dict(sorted(patterns.items()))}, + "works": [dict(item, pattern_counts=dict(sorted(item["pattern_counts"].items()))) + for item in sorted(works.values(), key=lambda value: (value["work_ref"], value["source_ref"]))], + "cards": cards, + } + + +def _persist_detection(*, inventory: dict, verification: dict, inventory_path: Path, + verification_path: Path, offline: bool = False) -> dict: + """检测命令统一落库边界;失败即让 CLI 失败,不报假绿。""" + if offline: + return {"status": "offline", "reason": "显式 --offline,未写 muse-example"} + # 延迟导入,保持纯函数测试不建立数据库连接,同时避免循环导入。 + from persist_cases import persist_generated + + try: + return persist_generated( + inventory=inventory, + verification=verification, + inventory_ref=f"capture://ai-flavor/{inventory_path.name}", + report_ref=f"capture://ai-flavor/{verification_path.name}", + inventory_sha256=_file_sha256(inventory_path), + report_sha256=_file_sha256(verification_path), + ) + except CaseCardError: + raise + except Exception as exc: + raise CaseCardError(f"自动落库失败(事务已回滚): {type(exc).__name__}: {exc}") from exc + + +def _write_revalidation(path: Path, report: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def _load_inventory_file(path: Path) -> dict: + """读取 inventory 载荷,供显式重验证落库使用。""" + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + except (OSError, UnicodeError, yaml.YAMLError) as exc: + raise CaseCardError(f"{path}: inventory 读取失败: {exc}") from exc + if not isinstance(data, dict) or data.get("schema_version") != "ai-flavor-inventory-v1": + raise CaseCardError(f"{path}: 不是 ai-flavor-inventory-v1 清单") + cards = data.get("cards") + if not isinstance(cards, list): + raise CaseCardError(f"{path}: inventory.cards 必须是数组") + for card in cards: + validate_card(card) + return data + + +def _sidecar(path: Path, suffix: str) -> Path: + return path.with_name(f"{path.stem}{suffix}") + + +def _detection_paths(output: Path, *, inventory_command: bool = False) -> tuple[Path, Path]: + """为每次检测生成不可能互相覆盖的 inventory/revalidation 文件名。""" + if inventory_command: + if "inventory" in output.stem: + verification = output.with_name( + f"{output.stem.replace('inventory', 'revalidation', 1)}{output.suffix}" + ) + else: + verification = output.with_name(f"{output.stem}.revalidation{output.suffix}") + if verification == output: + verification = output.with_name(f"{output.stem}.revalidation{output.suffix}") + return output, verification + return output.with_suffix(".inventory.json"), output.with_suffix(".revalidation.json") + + +def _run_detection_persistence(*, cards: list[dict], inventory: dict, + inventory_path: Path, verification_path: Path, + source_root: Path | None = None, + source_texts: dict[str, str] | None = None, + checked_on: str | None = None, offline: bool = False) -> dict: + """所有检测命令共用:生成回执文件后立即写入数据库。""" + inventory_path.parent.mkdir(parents=True, exist_ok=True) + inventory_path.write_text(json.dumps(inventory, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + verification = build_revalidation_report( + cards, source_root=source_root, source_texts=source_texts, checked_on=checked_on, + ) + # 把逻辑回执引用纳入报告哈希:同一输出路径重跑幂等,换一次检测产物则留下新批次; + # 不把本机绝对路径写进正式库。 + verification["report_ref"] = f"capture://ai-flavor/{verification_path.name}" + _write_revalidation(verification_path, verification) + if offline: + return {"status": "offline", "reason": "显式 --offline,未写 muse-example"} + return _persist_detection( + inventory=inventory, verification=verification, + inventory_path=inventory_path, verification_path=verification_path, + ) + + +def _parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="发现并验证 AI 味案例卡") + sub = parser.add_subparsers(dest="command", required=True) + scan = sub.add_parser("scan") + scan.add_argument("path", type=Path) + scan.add_argument("--work-ref") + scan.add_argument("--license", dest="source_license", default="research_only", choices=sorted(LICENSES)) + scan.add_argument("--source-kind", default="existing_work", choices=["existing_work", "public_domain", "synthetic"]) + scan.add_argument("--output", type=Path, required=True) + scan.add_argument("--max-cards", type=int, default=500) + scan.add_argument("--offline", action="store_true", help="只生成文件,不自动写 muse-example") + feedback = sub.add_parser("feedback") + feedback.add_argument("--text-file", type=Path, required=True) + feedback.add_argument("--work-ref", required=True) + feedback.add_argument("--run-ref", required=True) + feedback.add_argument("--issue", required=True) + feedback.add_argument("--license", dest="source_license", default="owned", choices=sorted(LICENSES)) + feedback.add_argument("--output", type=Path, required=True) + feedback.add_argument("--offline", action="store_true", help="只生成文件,不自动写 muse-example") + validate = sub.add_parser("validate") + validate.add_argument("path", type=Path) + revalidate = sub.add_parser("revalidate") + revalidate.add_argument("cards", type=Path, help="案例卡 YAML/JSON 或 inventory 报告") + revalidate.add_argument("--source-root", type=Path, + help="受控来源根目录;不传则只能验证卡片中的绝对 source_ref") + revalidate.add_argument("--output", type=Path, required=True) + revalidate.add_argument("--checked-on") + revalidate.add_argument("--offline", action="store_true", help="只生成回执,不自动写 muse-example") + propose = sub.add_parser("propose-rule") + propose.add_argument("--cards", type=Path, nargs="+", required=True) + propose.add_argument("--rule-id", required=True) + propose.add_argument("--name", required=True) + propose.add_argument("--fix-hint", default="待独立评审决定") + propose.add_argument("--verification", type=Path, required=True, + help="revalidate 命令生成的来源重验证回执") + propose.add_argument("--output", type=Path, required=True) + inventory = sub.add_parser("inventory") + inventory.add_argument("root", type=Path) + inventory.add_argument("--glob", default="*.txt") + inventory.add_argument("--source-root-ref") + inventory.add_argument("--work-ref-prefix", default="ref:") + inventory.add_argument("--license", dest="source_license", default="research_only", choices=sorted(LICENSES)) + inventory.add_argument("--source-kind", default="existing_work", choices=["existing_work", "public_domain", "synthetic"]) + inventory.add_argument("--output", type=Path, required=True) + inventory.add_argument("--max-cards", type=int, default=5000) + inventory.add_argument("--generated-on") + inventory.add_argument("--offline", action="store_true", help="只生成文件,不自动写 muse-example") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = _parser().parse_args(argv) + try: + if args.command == "scan": + cards = capture_file(args.path, source_license=args.source_license, source_kind=args.source_kind, + work_ref=args.work_ref, max_cards=args.max_cards) + # 单文件扫描也遵守脱敏来源合同;重验证仍通过受控 source_root 读取真实文件。 + cards = [copy.deepcopy(card) for card in cards] + for card in cards: + card["source"]["source_ref"] = args.path.name + inventory = _inventory_from_cards(cards, source_root_ref=args.path.parent.name, + source_kind=args.source_kind, source_license=args.source_license) + inventory_path, verification_path = _detection_paths(args.output) + result = _run_detection_persistence( + cards=cards, inventory=inventory, inventory_path=inventory_path, + verification_path=verification_path, source_root=args.path.parent, + checked_on=date.today().isoformat(), offline=args.offline, + ) + # 保留原有 YAML 作为人工复核输入,但它不是正式权威。 + write_yaml(args.output, {"schema_version": SCHEMA_VERSION, "cards": cards}) + print(json.dumps({"cards": len(cards), "output": str(args.output), + "inventory": str(inventory_path), "revalidation": str(verification_path), + "persistence": result}, ensure_ascii=False)) + elif args.command == "feedback": + text = args.text_file.read_text(encoding="utf-8") + card = capture_feedback(text, work_ref=args.work_ref, + run_ref=args.run_ref, issue=args.issue, source_license=args.source_license) + inventory = _inventory_from_cards([card], source_root_ref="live_feedback", + source_kind="creation_feedback", source_license=args.source_license) + inventory_path, verification_path = _detection_paths(args.output) + result = _run_detection_persistence( + cards=[card], inventory=inventory, inventory_path=inventory_path, + verification_path=verification_path, + source_texts={card["id"]: text}, checked_on=date.today().isoformat(), offline=args.offline, + ) + write_yaml(args.output, {"schema_version": SCHEMA_VERSION, "cards": [card]}) + print(json.dumps({"cards": 1, "output": str(args.output), + "inventory": str(inventory_path), "revalidation": str(verification_path), + "persistence": result}, ensure_ascii=False)) + elif args.command == "validate": + cards = load_bundle([args.path]) + print(json.dumps({"valid": True, "cards": len(cards)}, ensure_ascii=False)) + elif args.command == "revalidate": + cards = load_bundle([args.cards]) + report = build_revalidation_report( + cards, + source_root=args.source_root, + checked_on=args.checked_on, + ) + report["report_ref"] = f"capture://ai-flavor/{args.output.name}" + _write_revalidation(args.output, report) + try: + inventory = _load_inventory_file(args.cards) + except CaseCardError: + inventory = _inventory_from_cards( + cards, + source_root_ref="manual-revalidate", + source_kind=cards[0]["source"].get("kind", "existing_work") if cards else "existing_work", + source_license=cards[0]["source"].get("license", "research_only") if cards else "research_only", + ) + if not args.offline: + # 手工重验证也是检测运行;默认自动追加批次/逐卡回执,--offline 才不写库。 + inventory_path = args.cards.with_name(f"{args.cards.stem}.inventory.json") + if args.cards.name.endswith(".inventory.json"): + inventory_path = args.cards + # 单卡 YAML 不是正式 inventory;先写 sidecar,保证自动落库有稳定的 + # inventory hash 和可恢复引用,不要求用户再调用导入脚本。 + if inventory_path != args.cards: + inventory_path.parent.mkdir(parents=True, exist_ok=True) + inventory_path.write_text( + json.dumps(inventory, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + verification_path = args.output + persistence = _persist_detection( + inventory=inventory, verification=report, + inventory_path=inventory_path, verification_path=verification_path, + ) + else: + persistence = {"status": "offline", "reason": "显式 --offline,未写 muse-example"} + print(json.dumps({ + "cards": report["totals"]["cards"], + "verified": report["totals"]["verified"], + "stale": report["totals"]["stale"], + "unavailable": report["totals"]["unavailable"], + "card_mismatch": report["totals"]["card_mismatch"], + "usable": report["usable"], + "output": str(args.output), + "persistence": persistence, + }, ensure_ascii=False)) + elif args.command == "propose-rule": + cards = load_bundle(args.cards) + verification = load_verification(args.verification) + rule = propose_rule(cards, rule_id=args.rule_id, name=args.name, fix_hint=args.fix_hint, + verification=verification) + write_yaml(args.output, rule) + print(json.dumps({"status": rule["status"], "cards": len(cards), "output": str(args.output)}, ensure_ascii=False)) + else: + report = build_inventory( + args.root, + source_license=args.source_license, + source_kind=args.source_kind, + glob=args.glob, + work_ref_prefix=args.work_ref_prefix, + source_root_ref=args.source_root_ref, + max_cards=args.max_cards, + generated_on=args.generated_on, + ) + inventory_path, verification_path = _detection_paths(args.output, inventory_command=True) + persistence = _run_detection_persistence( + cards=report["cards"], inventory=report, + inventory_path=args.output, verification_path=verification_path, + source_root=args.root, checked_on=report["generated_on"], offline=args.offline, + ) + print(json.dumps({"books": report["totals"]["books"], "cards": report["totals"]["cards"], + "output": str(args.output), "revalidation": str(verification_path), + "persistence": persistence}, ensure_ascii=False)) + return 0 + except (CaseCardError, OSError, UnicodeError) as exc: + print(f"CASE_CARD_CONTRACT_FAILED: {exc}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.claude/skills/capture-ai-flavor-cases/scripts/persist_cases.py b/.claude/skills/capture-ai-flavor-cases/scripts/persist_cases.py new file mode 100644 index 0000000..6a9666b --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/scripts/persist_cases.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +"""把 AI 味案例卡与来源重验证回执写入 agent-example 的 muse-example。 + +采集脚本保持确定性、可离线回放;本脚本是唯一的持久化边界,复用 +``access-database`` 的连接入口。案例卡做幂等当前投影,重验证批次/回执做 +append-only 账本。研究限定来源只写 hash、位置和观察,不写第三方正文。 +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +from pathlib import Path +from typing import Any + +import yaml + + +SCRIPT_DIR = Path(__file__).resolve().parent +DB_SCRIPTS = SCRIPT_DIR.parents[1] / "access-database" / "scripts" +if str(SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPT_DIR)) +if str(DB_SCRIPTS) not in sys.path: + sys.path.insert(0, str(DB_SCRIPTS)) + +from capture_cases import ( # noqa: E402 + CaseCardError, + REVALIDATION_SCHEMA_VERSION, + SCHEMA_VERSION, + load_verification, + validate_card, +) +from db import connect # noqa: E402 + + +TENANT_ID = 1 +CREATOR = "1" + + +def _logical_ref(path: Path) -> str: + """落库只保留可迁移的证据名,不把本机绝对路径当跨环境引用。""" + return f"capture://ai-flavor/{path.name}" + + +def _sha256_file(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _json(value: Any) -> str: + return json.dumps(value, ensure_ascii=False, separators=(",", ":")) + + +def _load_object(path: Path) -> dict: + try: + value = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + except (OSError, UnicodeError, yaml.YAMLError) as exc: + raise CaseCardError(f"{path}: 读取失败: {exc}") from exc + if not isinstance(value, dict): + raise CaseCardError(f"{path}: 顶层必须是对象") + return value + + +def load_inventory(path: Path) -> dict: + """加载并逐卡验证 inventory;返回可安全写入的报告对象。""" + report = _load_object(path) + if report.get("schema_version") != "ai-flavor-inventory-v1": + raise CaseCardError(f"{path}: 不是 ai-flavor-inventory-v1 清单") + cards = report.get("cards") + if not isinstance(cards, list): + raise CaseCardError(f"{path}: cards 必须是数组") + seen: set[str] = set() + for card in cards: + validate_card(card) + if card["id"] in seen: + raise CaseCardError(f"{path}: 案例卡 id 重复: {card['id']}") + seen.add(card["id"]) + totals = report.get("totals") or {} + if totals.get("cards") != len(cards): + raise CaseCardError(f"{path}: totals.cards 与 cards 数量不一致") + return report + + +def _validate_pair(inventory: dict, verification: dict, *, inventory_ref: str, + report_ref: str, inventory_sha256: str, report_sha256: str) -> dict: + """校验一次检测产出的卡片集合和同运行重验证回执。""" + if inventory.get("schema_version") != "ai-flavor-inventory-v1": + raise CaseCardError("inventory 不是 ai-flavor-inventory-v1 清单") + cards = inventory.get("cards") + if not isinstance(cards, list): + raise CaseCardError("inventory.cards 必须是数组") + for card in cards: + validate_card(card) + verification = verification if isinstance(verification, dict) else {} + if verification.get("schema_version") != REVALIDATION_SCHEMA_VERSION: + raise CaseCardError("重验证版本不支持") + if verification.get("report_ref") and verification["report_ref"] != report_ref: + raise CaseCardError("重验证 report_ref 与导入引用不一致") + results = verification["cards"] + if not isinstance(results, list): + raise CaseCardError("重验证 cards 必须是数组") + card_ids = {card["id"] for card in cards} + result_ids = {item.get("card_id") for item in results} + if card_ids != result_ids: + missing = sorted(card_ids - result_ids) + extra = sorted(result_ids - card_ids) + raise CaseCardError(f"清单/重验证卡片集合不一致: missing={missing[:3]} extra={extra[:3]}") + by_id = {card["id"]: card for card in cards} + for result in results: + card = by_id[result["card_id"]] + source_sha = card["source"]["source_sha256"] + if result.get("expected_source_sha256") != source_sha: + raise CaseCardError(f"{result['card_id']}: expected_source_sha256 与卡片不一致") + if result.get("checked_on") != verification.get("checked_on"): + raise CaseCardError(f"{result['card_id']}: checked_on 与批次不一致") + return { + "inventory": inventory, + "verification": verification, + "inventory_sha256": inventory_sha256, + "report_sha256": report_sha256, + "inventory_ref": inventory_ref, + "report_ref": report_ref, + } + + +def prepare_import(inventory_path: Path, verification_path: Path) -> dict: + """准备恢复/迁移导入;正常检测不需要调用此函数。""" + return _validate_pair( + load_inventory(inventory_path), load_verification(verification_path), + inventory_ref=_logical_ref(inventory_path), report_ref=_logical_ref(verification_path), + inventory_sha256=_sha256_file(inventory_path), report_sha256=_sha256_file(verification_path), + ) + + +def persist_generated(*, inventory: dict, verification: dict, inventory_ref: str, + report_ref: str, inventory_sha256: str, report_sha256: str, + creator: str = CREATOR, tenant_id: int = TENANT_ID) -> dict: + """检测命令的自动写入口;不要求先生成可导入文件。""" + prepared = _validate_pair( + inventory, verification, inventory_ref=inventory_ref, report_ref=report_ref, + inventory_sha256=inventory_sha256, report_sha256=report_sha256, + ) + return persist(prepared, creator=creator, tenant_id=tenant_id) + + +def _card_params(card: dict, prepared: dict, *, creator: str, tenant_id: int) -> tuple: + source = card["source"] + return ( + card["id"], card["schema_version"], card["card_type"], card["state"], + card["label"], card["layer"], card["carrier"], card["capture_mode"], + source["kind"], source["license"], source["source_sha256"], source["excerpt_sha256"], + source.get("source_ref", ""), source.get("work_ref"), _json(source["location"]), + _json(source.get("surface_location")) if source.get("surface_location") else None, + card.get("excerpt", ""), card.get("context", ""), _json(card["observation"]), + _json(card["feedback"]) if card.get("feedback") is not None else None, + _json(card["review"]) if card.get("review") is not None else None, + _json(card.get("rule_candidate_ids", [])), prepared["inventory_ref"], + prepared["inventory_sha256"], creator, creator, tenant_id, + ) + + +def persist(prepared: dict, *, creator: str = CREATOR, tenant_id: int = TENANT_ID) -> dict: + """事务性导入;重复运行不重复插入批次/逐卡回执。""" + inventory = prepared["inventory"] + verification = prepared["verification"] + cards = inventory["cards"] + results = verification["cards"] + batch_sql = ( + "INSERT INTO example_ai_flavor_revalidation_batch " + "(batch_ref,report_sha256,schema_version,checked_on,source_root_ref,usable,totals,works,report_path,creator,tenant_id) " + "VALUES (%s,%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s,%s,%s) " + "ON CONFLICT (tenant_id,report_sha256) DO NOTHING RETURNING id" + ) + card_sql = ( + "INSERT INTO example_ai_flavor_case " + "(card_id,schema_version,card_type,state,label,layer,carrier,capture_mode,source_kind,source_license," + "source_sha256,excerpt_sha256,source_ref,work_ref,source_location,surface_location,excerpt,context,observation," + "feedback,review,rule_candidate_ids,inventory_ref,inventory_sha256,creator,updater,tenant_id) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s,%s,%s::jsonb,%s::jsonb,%s::jsonb,%s::jsonb,%s,%s,%s,%s,%s) " + "ON CONFLICT (tenant_id,card_id) DO UPDATE SET " + "schema_version=EXCLUDED.schema_version,card_type=EXCLUDED.card_type,source_kind=EXCLUDED.source_kind," + "source_license=EXCLUDED.source_license,source_sha256=EXCLUDED.source_sha256,excerpt_sha256=EXCLUDED.excerpt_sha256," + "source_ref=EXCLUDED.source_ref,work_ref=EXCLUDED.work_ref,source_location=EXCLUDED.source_location," + "surface_location=EXCLUDED.surface_location,observation=EXCLUDED.observation,feedback=EXCLUDED.feedback," + "rule_candidate_ids=EXCLUDED.rule_candidate_ids,inventory_ref=EXCLUDED.inventory_ref," + "inventory_sha256=EXCLUDED.inventory_sha256,updater=EXCLUDED.updater,update_time=CURRENT_TIMESTAMP " + "WHERE example_ai_flavor_case.state NOT IN ('canonical','rejected','archived')" + ) + result_sql = ( + "INSERT INTO example_ai_flavor_revalidation " + "(batch_id,card_id,work_ref,source_ref,expected_source_sha256,actual_source_sha256,status,reason,checked_on,creator,tenant_id) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) " + "ON CONFLICT (tenant_id,batch_id,card_id) DO NOTHING" + ) + with connect() as conn: + try: + batch_row = conn.execute( + batch_sql, + (prepared["report_ref"], prepared["report_sha256"], verification["schema_version"], + verification["checked_on"], verification.get("source_root_ref"), verification["usable"], + _json(verification.get("totals", {})), _json(verification.get("works", [])), + prepared["report_ref"], creator, tenant_id), + ).fetchone() + if batch_row: + batch_id = batch_row[0] + else: + batch_id = conn.execute( + "SELECT id FROM example_ai_flavor_revalidation_batch WHERE tenant_id=%s AND report_sha256=%s", + (tenant_id, prepared["report_sha256"]), + ).fetchone()[0] + + for card in cards: + conn.execute(card_sql, _card_params(card, prepared, creator=creator, tenant_id=tenant_id)) + for result in results: + conn.execute(result_sql, ( + batch_id, result["card_id"], result.get("work_ref"), result.get("source_ref", ""), + result["expected_source_sha256"], result.get("actual_source_sha256"), result["status"], + result.get("reason", ""), result["checked_on"], creator, tenant_id, + )) + counts = conn.execute( + "SELECT count(*) FILTER (WHERE deleted=false), " + "count(*) FILTER (WHERE deleted=false AND state='shadow') " + "FROM example_ai_flavor_case WHERE tenant_id=%s", (tenant_id,), + ).fetchone() + receipt_count = conn.execute( + "SELECT count(*) FROM example_ai_flavor_revalidation WHERE tenant_id=%s AND batch_id=%s", + (tenant_id, batch_id), + ).fetchone()[0] + conn.commit() + except Exception: + conn.rollback() + raise + return { + "status": "written", + "batch_id": batch_id, + "inventory_cards": len(cards), + "case_rows": int(counts[0]), + "shadow_rows": int(counts[1]), + "revalidation_rows": int(receipt_count), + "report_sha256": prepared["report_sha256"], + } + + +def _parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="AI 味案例卡与重验证回执落库") + parser.add_argument("inventory", type=Path, help="ai-flavor-inventory-v1 JSON/YAML") + parser.add_argument("verification", type=Path, help="ai-flavor-revalidation-v1 JSON/YAML") + parser.add_argument("--creator", default=CREATOR) + parser.add_argument("--tenant-id", type=int, default=TENANT_ID) + return parser + + +def main(argv: list[str] | None = None) -> int: + args = _parser().parse_args(argv) + prepared = prepare_import(args.inventory, args.verification) + print(json.dumps(persist(prepared, creator=args.creator, tenant_id=args.tenant_id), + ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (CaseCardError, OSError, KeyError, TypeError, IndexError) as exc: + print(f"CASE_CARD_PERSIST_FAILED: {exc}", file=sys.stderr) + raise SystemExit(2) diff --git a/.claude/skills/capture-ai-flavor-cases/scripts/test_capture_cases.py b/.claude/skills/capture-ai-flavor-cases/scripts/test_capture_cases.py new file mode 100644 index 0000000..35a14d6 --- /dev/null +++ b/.claude/skills/capture-ai-flavor-cases/scripts/test_capture_cases.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +"""AI 味案例卡离线合同测试;不连接数据库、不调用模型。""" + +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch +import yaml + +from capture_cases import ( + CaseCardError, + PATTERNS, + annotate_card, + capture_feedback, + capture_file, + build_inventory, + confirm_card, + project_sample, + propose_rule, + build_revalidation_report, + revalidate_card, + text_sha256, + validate_card, + main, +) + + +class CaptureCasesTest(unittest.TestCase): + def test_inventory_is_deterministic_and_keeps_only_hash_for_research_sources(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "b.txt").write_text("值得注意的是。\n", encoding="utf-8") + (root / "a.txt").write_text("嘴角微微上扬。\n", encoding="utf-8") + report = build_inventory(root, source_root_ref="fixture", generated_on="2026-08-13") + self.assertEqual("ai-flavor-inventory-v1", report["schema_version"]) + self.assertEqual({"books": 2, "cards": 2}, + {key: report["totals"][key] for key in ("books", "cards")}) + self.assertEqual(2, len({card["id"] for card in report["cards"]})) + self.assertTrue(all(card["state"] == "shadow" and not card["excerpt"] for card in report["cards"])) + self.assertTrue(all(not card["source"]["source_ref"].startswith("/") for card in report["cards"])) + + def test_backfill_research_only_is_hash_only_and_replayable(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("第一行。值得注意的是,门外下雨了。\n", encoding="utf-8") + cards = capture_file(path, work_ref="work-a") + self.assertEqual(1, len(cards)) + card = cards[0] + self.assertEqual("shadow", card["state"]) + self.assertEqual("", card["excerpt"]) + self.assertEqual("", card["context"]) + self.assertEqual(text_sha256("值得注意的是,门外下雨了。"), card["source"]["excerpt_sha256"]) + self.assertEqual(1, card["source"]["location"]["line_start"]) + validate_card(card) + + def test_owned_backfill_keeps_context_and_location(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "owned.txt" + path.write_text("她抬头。嘴角微微上扬。\n", encoding="utf-8") + cards = capture_file(path, work_ref="work-owned", source_license="owned") + self.assertEqual(1, len(cards)) + self.assertEqual("嘴角微微上扬。", cards[0]["excerpt"]) + self.assertIn("她抬头", cards[0]["context"]) + self.assertEqual(1, cards[0]["source"]["location"]["line_start"]) + + def test_duplicate_scan_pattern_is_deduplicated(self): + pattern = PATTERNS[0] + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "same.txt" + path.write_text("值得注意的是。", encoding="utf-8") + cards = capture_file(path, patterns=[pattern, pattern], source_license="owned", work_ref="work-same") + self.assertEqual(1, len(cards)) + + def test_feedback_is_bound_to_creation_run(self): + card = capture_feedback("候选正文", work_ref="work-12", run_ref="run-7", issue="段尾空泛") + self.assertEqual("live_feedback", card["capture_mode"]) + self.assertEqual("creation_feedback", card["source"]["kind"]) + self.assertEqual("run-7", card["feedback"]["run_ref"]) + self.assertEqual("shadow", card["state"]) + + def test_unlicensed_card_cannot_be_confirmed_or_store_text(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "third-party.txt" + path.write_text("值得注意的是。", encoding="utf-8") + card = capture_file(path, work_ref="work-third")[0] + with self.assertRaises(CaseCardError): + confirm_card(card, reviewer="u", note="不能确认") + broken = dict(card) + broken["excerpt"] = "偷偷保存的原文" + with self.assertRaises(CaseCardError): + validate_card(broken) + + def test_revalidate_unchanged_source_is_verified(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("第一行。值得注意的是,门外下雨了。\n", encoding="utf-8") + card = capture_file(path, work_ref="work-a", source_license="owned")[0] + result = revalidate_card(card, source_path=path, checked_on="2026-08-14") + self.assertEqual("verified", result["status"]) + self.assertEqual(card["source"]["source_sha256"], result["actual_source_sha256"]) + self.assertEqual("2026-08-14", result["checked_on"]) + + def test_revalidate_modified_source_is_stale_and_does_not_mutate_card(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("值得注意的是。\n", encoding="utf-8") + card = capture_file(path, work_ref="work-a", source_license="owned")[0] + original_state = card["state"] + path.write_text("已经改成另一版正文。\n", encoding="utf-8") + result = revalidate_card(card, source_path=path) + self.assertEqual("stale", result["status"]) + self.assertEqual("source_hash_mismatch", result["reason"]) + self.assertEqual("shadow", original_state) + self.assertEqual("shadow", card["state"]) + + def test_revalidate_same_source_but_tampered_anchor_is_card_mismatch(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("值得注意的是。", encoding="utf-8") + card = capture_file(path, work_ref="work-a", source_license="owned")[0] + tampered = dict(card) + tampered["source"] = dict(card["source"]) + tampered["source"]["surface_location"] = dict(card["source"]["surface_location"]) + tampered["source"]["surface_location"]["char_start"] = 1 + result = revalidate_card(tampered, source_path=path) + self.assertEqual("card_mismatch", result["status"]) + self.assertEqual("card_surface_mismatch", result["reason"]) + + def test_revalidate_unavailable_source_is_fail_closed(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("值得注意的是。", encoding="utf-8") + card = capture_file(path, work_ref="work-a")[0] + result = revalidate_card(card, source_path=Path(tmp) / "missing" / "work.txt") + self.assertEqual("unavailable", result["status"]) + self.assertEqual("source_not_found", result["reason"]) + + def test_revalidate_report_deduplicates_source_reads_and_exposes_counts(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "work.txt" + path.write_text("值得注意的是。嘴角微微上扬。", encoding="utf-8") + cards = capture_file(path, work_ref="work-a", source_license="owned") + report = build_revalidation_report(cards, source_root=Path(tmp), checked_on="2026-08-14") + self.assertEqual(2, report["totals"]["cards"]) + self.assertEqual(2, report["totals"]["verified"]) + self.assertTrue(report["usable"]) + + def test_confirmation_requires_verified_receipt(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "owned.txt" + path.write_text("值得注意的是。", encoding="utf-8") + shadow = capture_file(path, work_ref="work-a", source_license="owned")[0] + annotated = annotate_card(shadow, label="sf", carrier="narration") + with self.assertRaises(CaseCardError): + confirm_card(annotated, reviewer="human", note="无功能") + verification = {"cards": [revalidate_card(annotated, source_path=path)]} + canonical = confirm_card(annotated, reviewer="human", note="无功能", verification=verification) + self.assertEqual("canonical", canonical["state"]) + + def test_shadow_needs_explicit_annotation_before_rule_candidate(self): + with tempfile.TemporaryDirectory() as tmp: + a = Path(tmp) / "a.txt" + b = Path(tmp) / "b.txt" + a.write_text("值得注意的是。", encoding="utf-8") + b.write_text("值得注意的是。", encoding="utf-8") + sf = annotate_card(capture_file(a, work_ref="work-a", source_license="owned")[0], label="sf") + snf = annotate_card(capture_file(b, work_ref="work-b", source_license="owned")[0], label="snf") + verification = {"cards": [ + revalidate_card(sf, source_path=a), revalidate_card(snf, source_path=b) + ]} + rule = propose_rule([sf, snf], rule_id="candidate-1", name="元话语", fix_hint="先核功能", + verification=verification) + self.assertEqual("candidate", rule["status"]) + self.assertEqual({"work-a", "work-b"}, set(rule["source_work_refs"])) + + def test_rule_candidate_rejects_single_work_or_one_sided_evidence(self): + with tempfile.TemporaryDirectory() as tmp: + a = Path(tmp) / "a.txt" + a.write_text("值得注意的是。\n值得注意的是。", encoding="utf-8") + cards = capture_file(a, work_ref="work-a", source_license="owned") + sf = annotate_card(cards[0], label="sf") + sf2 = annotate_card(cards[1], label="sf") + with self.assertRaises(CaseCardError): + propose_rule([sf, sf2], rule_id="candidate-one-sided", name="单样本禁令", fix_hint="删除") + + def test_canonical_card_projects_to_sample_only_after_review(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "owned.txt" + path.write_text("值得注意的是。", encoding="utf-8") + shadow = capture_file(path, work_ref="work-a", source_license="owned")[0] + annotated = annotate_card(shadow, label="sf", carrier="narration") + with self.assertRaises(CaseCardError): + project_sample(annotated, verification={}) + verification = {"cards": [revalidate_card(annotated, source_path=path)]} + canonical = confirm_card(annotated, reviewer="human", note="上下文无功能", verification=verification) + sample = project_sample(canonical, verification=verification) + self.assertEqual("sample-" + canonical["id"], sample["id"]) + self.assertEqual(canonical["id"], sample["case_card_id"]) + + def test_shipped_fixtures_pass_the_same_validator(self): + root = Path(__file__).resolve().parents[1] / "references" / "fixtures" + for name in ("backfill-hash-only.yaml", "canonical-samples.yaml"): + data = yaml.safe_load((root / name).read_text(encoding="utf-8")) + for card in data["cards"]: + validate_card(card) + + def test_shipped_rule_seed_is_candidate_only(self): + root = Path(__file__).resolve().parents[1] / "references" / "fixtures" + data = yaml.safe_load((root / "rule-candidates.yaml").read_text(encoding="utf-8")) + self.assertTrue(data["rules"]) + self.assertTrue(all(rule["status"] == "candidate" for rule in data["rules"])) + + def test_shipped_revalidation_report_is_structurally_usable(self): + root = Path(__file__).resolve().parents[1] / "references" / "fixtures" + data = yaml.safe_load((root / "revalidation-2026-08-14.json").read_text(encoding="utf-8")) + self.assertEqual("ai-flavor-revalidation-v1", data["schema_version"]) + self.assertTrue(data["usable"]) + self.assertEqual({"cards": 788, "verified": 788, "stale": 0, "unavailable": 0, "card_mismatch": 0}, data["totals"]) + + def test_revalidate_cli_is_persist_by_default_contract(self): + import capture_cases + parser = capture_cases._parser() + args = parser.parse_args(["revalidate", "cards.yaml", "--output", "receipt.json"]) + self.assertFalse(args.offline) + + def test_inventory_cli_automatically_calls_persistence(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) / "works" + root.mkdir() + (root / "work.txt").write_text("值得注意的是。", encoding="utf-8") + output = Path(tmp) / "inventory.json" + with patch("capture_cases._persist_detection", return_value={"status": "written"}) as persist: + rc = main(["inventory", str(root), "--output", str(output)]) + self.assertEqual(0, rc) + persist.assert_called_once() + + def test_inventory_cli_offline_is_explicit_escape_hatch(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) / "works" + root.mkdir() + (root / "work.txt").write_text("值得注意的是。", encoding="utf-8") + output = Path(tmp) / "inventory.json" + with patch("capture_cases._persist_detection") as persist: + rc = main(["inventory", str(root), "--output", str(output), "--offline"]) + self.assertEqual(0, rc) + persist.assert_not_called() + + def test_scan_cli_automatically_calls_persistence(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + source = root / "work.txt" + source.write_text("值得注意的是。", encoding="utf-8") + output = root / "cards.yaml" + with patch("capture_cases._persist_detection", return_value={"status": "written"}) as persist: + rc = main(["scan", str(source), "--work-ref", "work-a", "--output", str(output)]) + self.assertEqual(0, rc) + persist.assert_called_once() + + def test_feedback_cli_automatically_calls_persistence(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + source = root / "candidate.txt" + source.write_text("候选正文。", encoding="utf-8") + output = root / "feedback.yaml" + with patch("capture_cases._persist_detection", return_value={"status": "written"}) as persist: + rc = main([ + "feedback", "--text-file", str(source), "--work-ref", "work-a", + "--run-ref", "run-a", "--issue", "段尾空泛", "--output", str(output), + ]) + self.assertEqual(0, rc) + persist.assert_called_once() + + def test_revalidate_single_card_writes_inventory_sidecar_before_persistence(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + source = root / "work.txt" + source.write_text("值得注意的是。", encoding="utf-8") + card = capture_file(source, work_ref="work-a", source_license="owned")[0] + cards_path = root / "card.yaml" + cards_path.write_text(yaml.safe_dump({"schema_version": "ai-flavor-case-v1", "cards": [card]}, allow_unicode=True), encoding="utf-8") + receipt_path = root / "receipt.json" + with patch("capture_cases._persist_detection", return_value={"status": "written"}) as persist: + rc = main([ + "revalidate", str(cards_path), "--source-root", str(root), + "--output", str(receipt_path), "--checked-on", "2026-08-14", + ]) + self.assertEqual(0, rc) + persist.assert_called_once() + self.assertTrue((root / "card.inventory.json").is_file()) + + def test_revalidate_single_card_offline_does_not_write_database(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + source = root / "work.txt" + source.write_text("值得注意的是。", encoding="utf-8") + card = capture_file(source, work_ref="work-a", source_license="owned")[0] + cards_path = root / "card.yaml" + cards_path.write_text(yaml.safe_dump({"schema_version": "ai-flavor-case-v1", "cards": [card]}, allow_unicode=True), encoding="utf-8") + receipt_path = root / "receipt.json" + with patch("capture_cases._persist_detection") as persist: + rc = main([ + "revalidate", str(cards_path), "--source-root", str(root), + "--output", str(receipt_path), "--offline", + ]) + self.assertEqual(0, rc) + persist.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/detect/SKILL.md b/.claude/skills/check-content-consistency/SKILL.md similarity index 81% rename from .claude/skills/detect/SKILL.md rename to .claude/skills/check-content-consistency/SKILL.md index c17656f..fb507b4 100644 --- a/.claude/skills/detect/SKILL.md +++ b/.claude/skills/check-content-consistency/SKILL.md @@ -1,6 +1,6 @@ --- -name: detect -description: 检测的功能合同(scenario: validation/consistency_check/fine_outline_replay,检测槽位)。检查清单由 schema 字段自动生成——凡 aiContext 含 detection 的字段即检查项。 +name: check-content-consistency +description: 检查正文或细纲候选的结构、事实、角色状态、能力代价、伏笔和证据缺口。候选进入用户决策或独立评分前使用;只产检测报告,不修改候选。 disable-model-invocation: true --- @@ -12,7 +12,7 @@ disable-model-invocation: true 1. 先运行 `scripts/check_writer_candidate.py`;它只做确定性机械硬门,不调用模型。 2. 机械硬门阻塞时直接返回结构化失败码,保留审查轨迹,不调用语义 detector。 -3. 机械硬门通过后,编排层构造 `semantic-detector-input-v3`,再以 fresh 无会话调用执行一个候选的语义核查。 +3. 机械硬门通过后,编排层构造 `semantic-detector-input-v3`(生产构造器:`scripts/run_writer_semantic_detector.py` 的 `build_semantic_input_v3`,从 WriterContext + 候选投影并闭集校验,sourceRef 清洗成合同形状),再以 fresh 无会话调用执行一个候选的语义核查。 4. 模型只返回 `semantic-detection-draft-v3`;adapter 负责绑定输入、候选、上下文、模型回执、字符 offset、状态和报告 hash,形成 `SemanticDetection v3`。异常、非法结构或绑定不一致全部失败关闭。 当前机械硬门覆盖:WriterContext v1、CandidateEnvelope v2、上下文与候选 hash、篇幅、细纲事件/角色/伏笔/章末钩子锚点。事实断言、角色知情范围、能力代价、语义冲突、新设定识别和证据缺口属于 semantic detector,不得塞回 writer 输出或机械门。 @@ -44,11 +44,13 @@ schema 给字段加上 detection 用途,检查项自动+1,本 skill 与 detector `claims` 明确候选中的事实主张与覆盖状态;`findings` 记录可定位问题;verdict 完整覆盖输入登记的 assertion/constraint ID。只有 `evidenceGaps` 或 `unknown` 可以触发编排器补证;纯机械失败或明确语义失败直接拒绝,不以同一输入重试。补证后必须冻结新的上下文快照和新的 WriterCreativeInput,再启动 fresh writer 调用。 +正文回放检测每臂最多调用三次。模型已返回但结构/引文不合约时,可用剩余额度回喂上一稿纠错;带可信成本回执的瞬时 `SEMANTIC_DETECTOR_API_ERROR` 最多重发同一输入一次,并与结构纠错共用三次总额度。认证、预算、本地合同、成本未知或连续第二次 API 错误均失败关闭,不得重试绕过。 + 最终 SemanticDetection v3 由 adapter 增加运行身份、候选/上下文 hash、字符 offset、模型回执、状态和报告 hash。模型不得输出这些可信字段。 ## 细纲回放分支(`fine_outline_replay`) -何时用:`fine-outline` 候选进入独立评分前。检测器只看冻结到 `as_of` 的规划上下文和候选,不看目标章标准事实,不把评测答案倒灌回规划侧。 +何时用:`plan-chapter` 产出的 `fine_outline` 候选进入独立评分前。检测器只看冻结到 `as_of` 的规划上下文和候选,不看目标章标准事实,不把评测答案倒灌回规划侧。 检查对象和证据格式: diff --git a/.claude/skills/detect/scripts/check_writer_candidate.py b/.claude/skills/check-content-consistency/scripts/check_writer_candidate.py similarity index 99% rename from .claude/skills/detect/scripts/check_writer_candidate.py rename to .claude/skills/check-content-consistency/scripts/check_writer_candidate.py index 7850264..86a0fc8 100644 --- a/.claude/skills/detect/scripts/check_writer_candidate.py +++ b/.claude/skills/check-content-consistency/scripts/check_writer_candidate.py @@ -9,7 +9,7 @@ import sys from typing import Any, Mapping, Sequence SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "read-context" / "scripts" +READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" if str(READ_CONTEXT_DIR) not in sys.path: sys.path.insert(0, str(READ_CONTEXT_DIR)) diff --git a/.claude/skills/detect/scripts/run_writer_semantic_detector.py b/.claude/skills/check-content-consistency/scripts/run_writer_semantic_detector.py similarity index 56% rename from .claude/skills/detect/scripts/run_writer_semantic_detector.py rename to .claude/skills/check-content-consistency/scripts/run_writer_semantic_detector.py index b089e9a..95b9bcf 100644 --- a/.claude/skills/detect/scripts/run_writer_semantic_detector.py +++ b/.claude/skills/check-content-consistency/scripts/run_writer_semantic_detector.py @@ -3,6 +3,7 @@ from __future__ import annotations +import copy import hashlib import json import pathlib @@ -11,9 +12,9 @@ import sys from typing import Any, Mapping, Protocol, Sequence, runtime_checkable SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -if str(RUNTIME_DIR) not in sys.path: - sys.path.insert(0, str(RUNTIME_DIR)) +EXECUTION_DIR = SCRIPT_DIR.parents[1] / "execute-claude-task" / "scripts" +if str(EXECUTION_DIR) not in sys.path: + sys.path.insert(0, str(EXECUTION_DIR)) try: from claude_runtime import ExecutionProfile, contains_path_traversal, run_claude, sha256_json # type: ignore[import-not-found] # noqa: E402 @@ -54,6 +55,27 @@ REPORT_FIELDS = frozenset({ "hardConstraintVerdicts", "newSettingCandidates", "evidenceGaps", "status", "reportSha256", }) +SAFE_DIAGNOSTIC_VERSION = "semantic-diagnostic-v1" +SAFE_DIAGNOSTIC_MAX_COUNT = 10_000 +SAFE_DIAGNOSTIC_SECTIONS = frozenset({ + "input", "model_output", "claims", "findings", "assertion_verdicts", + "hard_constraint_verdicts", "new_setting_candidates", "evidence_gaps", + "report", "runtime", +}) +SAFE_DIAGNOSTIC_REASON_CODES = frozenset({ + "ARRAY_REQUIRED", "CONTRACT_INVALID", "DUPLICATE_ID", "ENUM_INVALID", + "EVIDENCE_ID_INVALID", "EVIDENCE_REQUIRED", "FIELD_SET_INVALID", + "GAP_REASON_FORBIDDEN", "GAP_REASON_REQUIRED", "HASH_INVALID", + "ID_ORDER_MISMATCH", "INTEGER_RANGE_INVALID", "MODEL_VERSION_INVALID", + "NON_EMPTY_STRING_REQUIRED", "OBJECT_REQUIRED", "QUOTE_NOT_FOUND", + "REFERENCE_LEAKAGE", "RUNTIME_FAILED", "SEMANTIC_BLOCKED", +}) +SAFE_PRIMARY_CODE_PATTERN = re.compile(r"^SEMANTIC_[A-Z0-9_]{1,95}$") +SAFE_BLOCKING_COUNT_FIELDS = ( + "highFindings", "failedAssertions", "failedHardConstraints", + "conflictingClaims", "evidenceGaps", "unknownAssertions", + "unknownHardConstraints", "unknownClaims", +) @runtime_checkable @@ -61,12 +83,67 @@ class ModelRunner(Protocol): def run(self, *, adapter_role: str, model_input: Mapping[str, Any], output_schema: Mapping[str, Any]) -> Mapping[str, Any]: ... +def _diagnostic_section(path: str) -> str: + """把 adapter 自己生成的字段路径投影成固定区段,不保留索引或原始路径。""" + + prefixes = ( + ("$.claims", "claims"), + ("$.findings", "findings"), + ("$.assertionVerdicts", "assertion_verdicts"), + ("$.hardConstraintVerdicts", "hard_constraint_verdicts"), + ("$.newSettingCandidates", "new_setting_candidates"), + ("$.evidenceGaps", "evidence_gaps"), + ) + for prefix, section in prefixes: + if path.startswith(prefix): + return section + return "model_output" + + +def _diagnostic_section_for_error(path: str, code: str) -> str: + _reason, default_section = _default_diagnostic(code) + if default_section in {"input", "report", "runtime"}: + return default_section + return _diagnostic_section(path) + + +def _default_diagnostic(code: str) -> tuple[str, str]: + if code in {"SEMANTIC_DETECTOR_RUNTIME_FAILED", "SEMANTIC_DETECTOR_RUNNER_INVALID"}: + return "RUNTIME_FAILED", "runtime" + if code == "SEMANTIC_DETECTOR_LEAKAGE_DETECTED": + return "REFERENCE_LEAKAGE", "input" + if code == "SEMANTIC_DETECTOR_QUOTE_NOT_FOUND": + return "QUOTE_NOT_FOUND", "model_output" + if "INPUT" in code or "CANDIDATE_HASH" in code: + return "CONTRACT_INVALID", "input" + if "REPORT" in code or "STATUS" in code or "OFFSET" in code: + return "CONTRACT_INVALID", "report" + return "CONTRACT_INVALID", "model_output" + + +def _safe_primary_code(value: Any) -> str: + if isinstance(value, str) and SAFE_PRIMARY_CODE_PATTERN.fullmatch(value): + return value + return "SEMANTIC_DETECTOR_INVALID" + + class SemanticDetectorContractError(ValueError): - def __init__(self, code: str, message: str, *, causes: Sequence[str] = ()) -> None: + def __init__( + self, + code: str, + message: str, + *, + causes: Sequence[str] = (), + reason_code: str | None = None, + section: str | None = None, + ) -> None: super().__init__(message) self.code = code self.causes = tuple(cause for cause in causes if cause != code) self.acceptance_eligible = False + default_reason, default_section = _default_diagnostic(code) + self.reason_code = reason_code if reason_code in SAFE_DIAGNOSTIC_REASON_CODES else default_reason + self.section = section if section in SAFE_DIAGNOSTIC_SECTIONS else default_section def _canonical_json(value: Any) -> str: @@ -79,36 +156,54 @@ def canonical_sha256(value: Any) -> str: def _object(value: Any, path: str, required: frozenset[str], optional: frozenset[str] = frozenset(), *, code: str) -> Mapping[str, Any]: if not isinstance(value, Mapping): - raise SemanticDetectorContractError(code, f"{path} 必须是对象") + raise SemanticDetectorContractError( + code, f"{path} 必须是对象", reason_code="OBJECT_REQUIRED", + section=_diagnostic_section_for_error(path, code), + ) missing = sorted(required - set(value)) extra = sorted(set(value) - required - optional) if missing or extra: - raise SemanticDetectorContractError(code, f"{path} 字段非法,missing={missing}, extra={extra}") + raise SemanticDetectorContractError( + code, f"{path} 字段非法,missing={missing}, extra={extra}", + reason_code="FIELD_SET_INVALID", section=_diagnostic_section_for_error(path, code), + ) return value def _array(value: Any, path: str, *, code: str) -> list[Any]: if not isinstance(value, list): - raise SemanticDetectorContractError(code, f"{path} 必须是数组") + raise SemanticDetectorContractError( + code, f"{path} 必须是数组", reason_code="ARRAY_REQUIRED", + section=_diagnostic_section_for_error(path, code), + ) return value def _string(value: Any, path: str, *, code: str) -> str: if not isinstance(value, str) or not value.strip(): - raise SemanticDetectorContractError(code, f"{path} 必须是非空字符串") + raise SemanticDetectorContractError( + code, f"{path} 必须是非空字符串", reason_code="NON_EMPTY_STRING_REQUIRED", + section=_diagnostic_section_for_error(path, code), + ) return value def _integer(value: Any, path: str, *, minimum: int, code: str) -> int: if isinstance(value, bool) or not isinstance(value, int) or value < minimum: - raise SemanticDetectorContractError(code, f"{path} 必须是大于等于 {minimum} 的整数") + raise SemanticDetectorContractError( + code, f"{path} 必须是大于等于 {minimum} 的整数", + reason_code="INTEGER_RANGE_INVALID", section=_diagnostic_section_for_error(path, code), + ) return value def _hash(value: Any, path: str, *, code: str) -> str: text = _string(value, path, code=code) if not HASH_PATTERN.fullmatch(text): - raise SemanticDetectorContractError(code, f"{path} 必须是规范 SHA-256") + raise SemanticDetectorContractError( + code, f"{path} 必须是规范 SHA-256", reason_code="HASH_INVALID", + section=_diagnostic_section_for_error(path, code), + ) return text @@ -118,7 +213,12 @@ def _safe_reference(value: Any, path: str, *, code: str) -> str: # 子串匹配会把省略号 `...`(含子串 `..`)误判为路径穿越,冤杀含省略号的合法引用; # 精确判定只拦 a/../b 这类真实穿越,臂名与 raw 路径仍由其余条件保留拦截。 if text in REAL_ARM_NAMES or text.startswith(("/", "file:")) or contains_path_traversal(text) or RAW_PATH_PATTERN.search(text): - raise SemanticDetectorContractError("SEMANTIC_DETECTOR_LEAKAGE_DETECTED", f"{path} 含真实臂名或 raw 路径") + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_LEAKAGE_DETECTED", + f"{path} 含真实臂名或 raw 路径", + reason_code="REFERENCE_LEAKAGE", + section="input", + ) return text @@ -241,9 +341,13 @@ def validate_semantic_detector_input(value: Any) -> dict[str, Any]: return normalized -def build_semantic_model_input(value: Any) -> dict[str, Any]: +def build_semantic_model_input(value: Any, correction: Mapping[str, Any] | None = None) -> dict[str, Any]: + # WHY: correction 只在「模型输入层」可选透传,绝不进入 detector_input—— + # validate_semantic_detector_input 对 detector_input 执行固定 INPUT_FIELDS 闭集校验, + # 多塞 correction 会被当成非法 extra 字段拒掉。把 correction 放在这里拼进模型输入, + # 既不破坏正常输入的闭集校验,又能把「上一轮不合格产出 + 出错原因」喂回模型做自我纠错。 payload = validate_semantic_detector_input(value) - return { + model_input: dict[str, Any] = { "candidateBody": payload["candidateBody"], "fineOutline": payload["fineOutline"], "hardConstraints": payload["hardConstraints"], @@ -263,6 +367,9 @@ def build_semantic_model_input(value: Any) -> dict[str, Any]: ], "asOf": payload["asOf"], } + if correction is not None: + model_input["correction"] = dict(correction) + return model_input def _quote_location(body: str, quote: Any, path: str) -> tuple[str, int, int]: @@ -270,7 +377,10 @@ def _quote_location(body: str, quote: Any, path: str) -> tuple[str, int, int]: # 0 次 = 引文根本不在候选里 = 编造证据,必须失败关闭(防编造核心不动)。 if text not in body: raise SemanticDetectorContractError( - "SEMANTIC_DETECTOR_QUOTE_NOT_FOUND", f"{path} 引文未在候选正文中出现(不得编造证据)" + "SEMANTIC_DETECTOR_QUOTE_NOT_FOUND", + f"{path} 引文未在候选正文中出现(不得编造证据)", + reason_code="QUOTE_NOT_FOUND", + section=_diagnostic_section(path), ) # ≥1 次:由确定性代码绑定到首次出现,不再因多处出现而失败关闭—— # 大模型无法可靠数出一句话在长文里出现几次,精确唯一计数不是可信门槛。 @@ -281,7 +391,12 @@ def _quote_location(body: str, quote: Any, path: str) -> tuple[str, int, int]: def _evidence_ids(value: Any, path: str, allowed: set[str]) -> list[str]: ids = [_string(item, f"{path}[{index}]", code="SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID") for index, item in enumerate(_array(value, path, code="SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID"))] if len(ids) != len(set(ids)) or any(item not in allowed for item in ids): - raise SemanticDetectorContractError("SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID", f"{path} 含重复或越界证据 ID") + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID", + f"{path} 含重复或越界证据 ID", + reason_code="EVIDENCE_ID_INVALID", + section=_diagnostic_section(path), + ) return ids @@ -289,7 +404,10 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, code = "SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID" draft = _object(value, "$", frozenset({"schemaVersion", "claims", "findings", "assertionVerdicts", "hardConstraintVerdicts", "newSettingCandidates", "evidenceGaps"}), code=code) if draft["schemaVersion"] != MODEL_OUTPUT_VERSION: - raise SemanticDetectorContractError(code, "模型输出版本非法") + raise SemanticDetectorContractError( + code, "模型输出版本非法", reason_code="MODEL_VERSION_INVALID", + section="model_output", + ) body = payload["candidateBody"] allowed_evidence = set(payload["_evidenceIds"]) @@ -307,11 +425,15 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, claim_id = _string(item["claimId"], f"$.claims[{index}].claimId", code=code) claim_ids.append(claim_id) if item["coverageState"] not in coverage_states: - raise SemanticDetectorContractError(code, f"$.claims[{index}].coverageState 非法") + raise SemanticDetectorContractError( + code, f"$.claims[{index}].coverageState 非法", + reason_code="ENUM_INVALID", section="claims", + ) if item["coverageState"] == "unknown" and "gapReason" not in item: - raise SemanticDetectorContractError(code, f"$.claims[{index}] unknown 必须携带 gapReason") - if item["coverageState"] != "unknown" and "gapReason" in item: - raise SemanticDetectorContractError(code, f"$.claims[{index}] 非 unknown 不得携带 gapReason") + raise SemanticDetectorContractError( + code, f"$.claims[{index}] unknown 必须携带 gapReason", + reason_code="GAP_REASON_REQUIRED", section="claims", + ) quote, start, end = _quote_location(body, item["candidateQuote"], f"$.claims[{index}].candidateQuote") bound = { "claimId": claim_id, @@ -324,11 +446,15 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, "coverageState": item["coverageState"], "evidenceIds": _evidence_ids(item["evidenceIds"], f"$.claims[{index}].evidenceIds", allowed_evidence), } - if "gapReason" in item: + # WHY: 模型可能在已判定的 claim 上残留解释性 gapReason;它不改变闭集状态, + # 绑定时确定性丢弃,避免把无害冗余升级为整份报告失败。unknown 仍须在上方校验非空原因。 + if item["coverageState"] == "unknown": bound["gapReason"] = _string(item["gapReason"], f"$.claims[{index}].gapReason", code=code) claims.append(bound) if len(claim_ids) != len(set(claim_ids)): - raise SemanticDetectorContractError(code, "claimId 不得重复") + raise SemanticDetectorContractError( + code, "claimId 不得重复", reason_code="DUPLICATE_ID", section="claims" + ) findings: list[dict[str, Any]] = [] finding_ids: list[str] = [] @@ -337,7 +463,10 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, finding_id = _string(item["findingId"], f"$.findings[{index}].findingId", code=code) finding_ids.append(finding_id) if item["severity"] not in SEVERITIES or item["category"] not in FINDING_CATEGORIES: - raise SemanticDetectorContractError(code, f"$.findings[{index}] 枚举非法") + raise SemanticDetectorContractError( + code, f"$.findings[{index}] 枚举非法", + reason_code="ENUM_INVALID", section="findings", + ) quote, start, end = _quote_location(body, item["candidateQuote"], f"$.findings[{index}].candidateQuote") findings.append({ "findingId": finding_id, "severity": item["severity"], "category": item["category"], @@ -347,7 +476,9 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, "message": _string(item["message"], f"$.findings[{index}].message", code=code), }) if len(finding_ids) != len(set(finding_ids)): - raise SemanticDetectorContractError(code, "findingId 不得重复") + raise SemanticDetectorContractError( + code, "findingId 不得重复", reason_code="DUPLICATE_ID", section="findings" + ) def verdicts(field: str, id_field: str, expected_ids: list[str]) -> list[dict[str, Any]]: result: list[dict[str, Any]] = [] @@ -357,23 +488,39 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, stable_id = _string(item[id_field], f"$.{field}[{index}].{id_field}", code=code) actual_ids.append(stable_id) if item["verdict"] not in VERDICTS: - raise SemanticDetectorContractError(code, f"$.{field}[{index}].verdict 非法") + raise SemanticDetectorContractError( + code, f"$.{field}[{index}].verdict 非法", + reason_code="ENUM_INVALID", section=_diagnostic_section(f"$.{field}"), + ) if item["verdict"] == "unknown" and "gapReason" not in item: - raise SemanticDetectorContractError(code, f"$.{field}[{index}] unknown 必须携带 gapReason") - if item["verdict"] != "unknown" and "gapReason" in item: - raise SemanticDetectorContractError(code, f"$.{field}[{index}] 非 unknown 不得携带 gapReason") + raise SemanticDetectorContractError( + code, f"$.{field}[{index}] unknown 必须携带 gapReason", + reason_code="GAP_REASON_REQUIRED", section=_diagnostic_section(f"$.{field}"), + ) quote, start, end = _quote_location(body, item["candidateQuote"], f"$.{field}[{index}].candidateQuote") bound = { id_field: stable_id, "candidateSha256": payload["candidateSha256"], "verdict": item["verdict"], "candidateQuote": quote, "startCodePoint": start, "endCodePoint": end, "evidenceIds": _evidence_ids(item["evidenceIds"], f"$.{field}[{index}].evidenceIds", allowed_evidence), } - if "gapReason" in item: + # WHY: pass/fail 已是终态,额外 gapReason 不参与可信绑定;统一丢弃可消除模型格式噪声, + # 但 unknown 的非空原因仍由上方强制校验,其他字段和证据约束保持失败关闭。 + if item["verdict"] == "unknown": bound["gapReason"] = _string(item["gapReason"], f"$.{field}[{index}].gapReason", code=code) result.append(bound) - if actual_ids != expected_ids: - raise SemanticDetectorContractError(code, f"$.{field} ID 集或顺序不一致") - return result + # WHY: ID 集完整且无重复时,条目顺序不影响语义;由适配器按冻结输入顺序重排, + # 避免模型把同一组裁决按字典序返回而被误判为缺失。集合不完整、重复或越界仍失败关闭。 + if ( + len(actual_ids) != len(expected_ids) + or len(actual_ids) != len(set(actual_ids)) + or set(actual_ids) != set(expected_ids) + ): + raise SemanticDetectorContractError( + code, f"$.{field} ID 集或顺序不一致", + reason_code="ID_ORDER_MISMATCH", section=_diagnostic_section(f"$.{field}"), + ) + by_id = {item[id_field]: item for item in result} + return [by_id[item] for item in expected_ids] assertion_verdicts = verdicts("assertionVerdicts", "assertionId", list(payload["_expectedAssertionIds"])) constraint_verdicts = verdicts("hardConstraintVerdicts", "constraintId", list(payload["_expectedConstraintIds"])) @@ -392,7 +539,10 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, "startCodePoint": start, "endCodePoint": end, }) if len(setting_ids) != len(set(setting_ids)): - raise SemanticDetectorContractError(code, "settingId 不得重复") + raise SemanticDetectorContractError( + code, "settingId 不得重复", reason_code="DUPLICATE_ID", + section="new_setting_candidates", + ) gaps: list[dict[str, Any]] = [] gap_ids: list[str] = [] @@ -401,7 +551,10 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, gap_id = _string(item["gapId"], f"$.evidenceGaps[{index}].gapId", code=code) gap_ids.append(gap_id) if item["priority"] not in PRIORITIES: - raise SemanticDetectorContractError(code, f"$.evidenceGaps[{index}].priority 非法") + raise SemanticDetectorContractError( + code, f"$.evidenceGaps[{index}].priority 非法", + reason_code="ENUM_INVALID", section="evidence_gaps", + ) quote, start, end = _quote_location(body, item["candidateQuote"], f"$.evidenceGaps[{index}].candidateQuote") gaps.append({ "gapId": gap_id, "query": _string(item["query"], f"$.evidenceGaps[{index}].query", code=code), @@ -410,7 +563,9 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, "candidateQuote": quote, "startCodePoint": start, "endCodePoint": end, }) if len(gap_ids) != len(set(gap_ids)): - raise SemanticDetectorContractError(code, "gapId 不得重复") + raise SemanticDetectorContractError( + code, "gapId 不得重复", reason_code="DUPLICATE_ID", section="evidence_gaps" + ) return { "claims": claims, "findings": findings, "assertionVerdicts": assertion_verdicts, "hardConstraintVerdicts": constraint_verdicts, "newSettingCandidates": settings, @@ -424,7 +579,8 @@ def build_semantic_detection(value: Any, detector_input: Mapping[str, Any], *, m content = _validate_model_output(value, payload) has_unknown = any(item["verdict"] == "unknown" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in content[field]) or any(item["coverageState"] == "unknown" for item in content["claims"]) has_failure = any(item["severity"] == "high" for item in content["findings"]) or any(item["verdict"] == "fail" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in content[field]) or any(item["coverageState"] == "conflict" for item in content["claims"]) - status = "needs_evidence" if content["evidenceGaps"] or has_unknown else ("failed" if has_failure else "passed") + # 高危优先:有高危发现/失败裁决/冲突即 failed(不被证据缺口掩盖);无高危仅有证据缺口/未知才是 needs_evidence。 + status = "failed" if has_failure else ("needs_evidence" if content["evidenceGaps"] or has_unknown else "passed") report = { "schemaVersion": REPORT_VERSION, "runId": payload["runId"], "sampleId": payload["sampleId"], "opaqueArmId": payload["opaqueArmId"], @@ -484,7 +640,7 @@ def validate_semantic_detector_report(value: Any, detector_input: Mapping[str, A values = [item[id_field] for item in report[field]] if len(values) != len(set(values)): raise SemanticDetectorContractError(code, f"$.{field}.{id_field} 不得重复") - expected_status = "needs_evidence" if report["evidenceGaps"] or any(item.get("verdict") == "unknown" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in report[field]) or any(item.get("coverageState") == "unknown" for item in report["claims"]) else ("failed" if any(item.get("severity") == "high" for item in report["findings"]) or any(item.get("verdict") == "fail" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in report[field]) or any(item.get("coverageState") == "conflict" for item in report["claims"]) else "passed") + expected_status = "failed" if any(item.get("severity") == "high" for item in report["findings"]) or any(item.get("verdict") == "fail" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in report[field]) or any(item.get("coverageState") == "conflict" for item in report["claims"]) else ("needs_evidence" if report["evidenceGaps"] or any(item.get("verdict") == "unknown" for field in ("assertionVerdicts", "hardConstraintVerdicts") for item in report[field]) or any(item.get("coverageState") == "unknown" for item in report["claims"]) else "passed") if report["status"] != expected_status: raise SemanticDetectorContractError("SEMANTIC_DETECTOR_STATUS_MISMATCH", "报告状态与语义内容不一致") supplied_hash = _hash(report["reportSha256"], "$.reportSha256", code=code) @@ -503,6 +659,106 @@ def calculate_semantic_metrics(report: Mapping[str, Any], detector_input: Mappin } +def _bounded_count(value: int) -> int: + return min(max(int(value), 0), SAFE_DIAGNOSTIC_MAX_COUNT) + + +def _empty_blocking_counts() -> dict[str, int]: + return {field: 0 for field in SAFE_BLOCKING_COUNT_FIELDS} + + +def _contract_safe_diagnostic( + error: SemanticDetectorContractError, *, attempt_count: int, correction_count: int | None = None +) -> dict[str, Any]: + attempts = _bounded_count(attempt_count) + corrections = _bounded_count( + max(attempts - 1, 0) if correction_count is None else correction_count + ) + return { + "schemaVersion": SAFE_DIAGNOSTIC_VERSION, + "outcome": "invalid", + "primaryCode": _safe_primary_code(error.code), + "reasonCode": error.reason_code, + "section": error.section, + "attemptCount": attempts, + "correctionCount": min(corrections, max(attempts - 1, 0)), + "blockingCounts": _empty_blocking_counts(), + } + + +def build_safe_semantic_diagnostic(value: Mapping[str, Any]) -> dict[str, Any]: + """从完整检测结果蒸馏固定闭集摘要;不复制任何模型文本、ID、引用或字段路径。""" + + if value.get("ok") is not True or not isinstance(value.get("report"), Mapping): + candidate = value.get("safeDiagnostic") + if not isinstance(candidate, Mapping): + candidate = {} + reason = candidate.get("reasonCode") + section = candidate.get("section") + attempts = candidate.get("attemptCount") + corrections = candidate.get("correctionCount") + safe_attempts = _bounded_count( + attempts if isinstance(attempts, int) and not isinstance(attempts, bool) else 0 + ) + safe_corrections = _bounded_count( + corrections if isinstance(corrections, int) and not isinstance(corrections, bool) else 0 + ) + return { + "schemaVersion": SAFE_DIAGNOSTIC_VERSION, + "outcome": "invalid", + "primaryCode": _safe_primary_code(candidate.get("primaryCode") or value.get("primaryCode")), + "reasonCode": reason if reason in SAFE_DIAGNOSTIC_REASON_CODES else "CONTRACT_INVALID", + "section": section if section in SAFE_DIAGNOSTIC_SECTIONS else "model_output", + "attemptCount": safe_attempts, + "correctionCount": min(safe_corrections, max(safe_attempts - 1, 0)), + "blockingCounts": _empty_blocking_counts(), + } + + report = value["report"] + status = str(report.get("status") or "") + if status not in {"failed", "needs_evidence"}: + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_STATUS_MISMATCH", + "安全诊断只接受阻断态报告", + section="report", + ) + findings = report.get("findings") if isinstance(report.get("findings"), list) else [] + assertions = report.get("assertionVerdicts") if isinstance(report.get("assertionVerdicts"), list) else [] + constraints = report.get("hardConstraintVerdicts") if isinstance(report.get("hardConstraintVerdicts"), list) else [] + claims = report.get("claims") if isinstance(report.get("claims"), list) else [] + gaps = report.get("evidenceGaps") if isinstance(report.get("evidenceGaps"), list) else [] + counts = { + "highFindings": _bounded_count(sum(isinstance(item, Mapping) and item.get("severity") == "high" for item in findings)), + "failedAssertions": _bounded_count(sum(isinstance(item, Mapping) and item.get("verdict") == "fail" for item in assertions)), + "failedHardConstraints": _bounded_count(sum(isinstance(item, Mapping) and item.get("verdict") == "fail" for item in constraints)), + "conflictingClaims": _bounded_count(sum(isinstance(item, Mapping) and item.get("coverageState") == "conflict" for item in claims)), + "evidenceGaps": _bounded_count(len(gaps)), + "unknownAssertions": _bounded_count(sum(isinstance(item, Mapping) and item.get("verdict") == "unknown" for item in assertions)), + "unknownHardConstraints": _bounded_count(sum(isinstance(item, Mapping) and item.get("verdict") == "unknown" for item in constraints)), + "unknownClaims": _bounded_count(sum(isinstance(item, Mapping) and item.get("coverageState") == "unknown" for item in claims)), + } + attempts = value.get("attemptCount") + corrections = value.get("correctionCount") + safe_attempts = _bounded_count( + attempts if isinstance(attempts, int) and not isinstance(attempts, bool) else 1 + ) + safe_corrections = _bounded_count( + corrections + if isinstance(corrections, int) and not isinstance(corrections, bool) + else max(safe_attempts - 1, 0) + ) + return { + "schemaVersion": SAFE_DIAGNOSTIC_VERSION, + "outcome": status, + "primaryCode": "SEMANTIC_EVIDENCE_REQUIRED" if status == "needs_evidence" else "SEMANTIC_CHECK_FAILED", + "reasonCode": "EVIDENCE_REQUIRED" if status == "needs_evidence" else "SEMANTIC_BLOCKED", + "section": "report", + "attemptCount": safe_attempts, + "correctionCount": min(safe_corrections, max(safe_attempts - 1, 0)), + "blockingCounts": counts, + } + + class ClaudeRuntimeModelRunner: def __init__(self, profile: ExecutionProfile, *, runtime_callable: Any = None) -> None: self.profile = profile @@ -532,29 +788,114 @@ def _invoke_model_runner(model_runner: ModelRunner, model_input: Mapping[str, An return result["structuredOutput"], result["modelReceiptSha256"] -def _failure(error: SemanticDetectorContractError) -> dict[str, Any]: - return {"ok": False, "acceptanceEligible": False, "status": "failed", "primaryCode": error.code, "causes": list(error.causes), "message": str(error)} +def _failure( + error: SemanticDetectorContractError, *, attempt_count: int, correction_count: int +) -> dict[str, Any]: + return { + "ok": False, + "acceptanceEligible": False, + "status": "failed", + "primaryCode": error.code, + "causes": list(error.causes), + "message": str(error), + "safeDiagnostic": _contract_safe_diagnostic( + error, + attempt_count=attempt_count, + correction_count=correction_count, + ), + } -def run_writer_semantic_detector(detector_input: Mapping[str, Any], *, model_runner: ModelRunner) -> dict[str, Any]: +def run_writer_semantic_detector( + detector_input: Mapping[str, Any], + *, + model_runner: ModelRunner, + max_corrections: int = 2, + max_runtime_retries: int = 1, +) -> dict[str, Any]: + # WHY: 检测模型(opus high)最常见的不合格是引文校验挂——它引了一句正文里没有的话。 + # 这类错误自我纠错最对症:把上一轮原始产出和出错原因回喂给模型,让它换一句正文里真实存在的原话。 + # 盲重试(不带上一轮产出)不一定收敛,所以纠错反馈里携带 previousDraft + error。 + # max_corrections 给出硬上界(默认 2 轮纠错 = 总共最多 3 次调用),防止无限循环; + # 每次调用都走同一个 model_runner(生产中是 _BudgetedModelRunner),各自过预算账本。 + attempt_count = 0 + correction_count = 0 + runtime_retry_count = 0 try: + if ( + isinstance(max_runtime_retries, bool) + or not isinstance(max_runtime_retries, int) + or not 0 <= max_runtime_retries <= 1 + ): + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_RUNNER_INVALID", + "max_runtime_retries 必须是 0 或 1", + ) normalized = validate_semantic_detector_input(detector_input) public_input = {key: value for key, value in normalized.items() if not key.startswith("_")} - model_input = build_semantic_model_input(public_input) - try: - draft, receipt_hash = _invoke_model_runner(model_runner, model_input) - except SemanticDetectorContractError: - raise - except Exception as exc: - runtime_code = getattr(exc, "primary_code", None) or getattr(exc, "code", None) - if isinstance(runtime_code, str) and runtime_code.startswith("SEMANTIC_DETECTOR_"): - raise SemanticDetectorContractError(runtime_code, "模型运行底座失败", causes=getattr(exc, "causes", ())) from exc - raise SemanticDetectorContractError("SEMANTIC_DETECTOR_RUNTIME_FAILED", f"模型运行失败: {type(exc).__name__}") from exc - report = build_semantic_detection(draft, public_input, model_receipt_sha256=receipt_hash) - report = validate_semantic_detector_report(report, public_input, model_receipt_sha256=receipt_hash) - return {"ok": True, "acceptanceEligible": False, "status": report["status"], "report": report, "metrics": calculate_semantic_metrics(report, public_input)} + base_model_input = build_semantic_model_input(public_input) + correction: dict[str, Any] | None = None + for attempt in range(max_corrections + 1): + attempt_count = attempt + 1 + # 首轮不带 correction;纠错轮在原始模型输入基础上追加 correction 字段透传给模型。 + model_input = base_model_input if correction is None else {**base_model_input, "correction": correction} + draft: Any = None + try: + try: + draft, receipt_hash = _invoke_model_runner(model_runner, model_input) + except SemanticDetectorContractError: + raise + except Exception as exc: + runtime_code = getattr(exc, "primary_code", None) or getattr(exc, "code", None) + if isinstance(runtime_code, str) and runtime_code.startswith("SEMANTIC_DETECTOR_"): + raise SemanticDetectorContractError(runtime_code, "模型运行底座失败", causes=getattr(exc, "causes", ())) from exc + raise SemanticDetectorContractError("SEMANTIC_DETECTOR_RUNTIME_FAILED", f"模型运行失败: {type(exc).__name__}") from exc + report = build_semantic_detection(draft, public_input, model_receipt_sha256=receipt_hash) + report = validate_semantic_detector_report(report, public_input, model_receipt_sha256=receipt_hash) + return { + "ok": True, + "acceptanceEligible": False, + "status": report["status"], + "report": report, + "metrics": calculate_semantic_metrics(report, public_input), + "attemptCount": attempt_count, + "correctionCount": correction_count, + } + except SemanticDetectorContractError as error: + # 纠错只对有「模型原始产出」的不合格有意义(build/validate 抛错时 draft 已存在)。 + # runner 底座失败没有 draft(draft 仍为 None),纠错帮不上忙,直接失败关闭; + # 已用尽纠错轮次时也直接抛出最后一轮错误,返回 ok=False。 + if draft is None: + # WHY: 带可信失败回执的瞬时 API 错误可以重新发送同一输入一次;它占用 + # 现有三次总调用额度,不携带伪造 correction,也不重试本地合同/认证错误。 + if ( + error.code == "SEMANTIC_DETECTOR_API_ERROR" + and runtime_retry_count < max_runtime_retries + and attempt < max_corrections + ): + runtime_retry_count += 1 + correction = None + continue + raise + if attempt >= max_corrections: + raise + correction = {"previousDraft": draft, "error": str(error)} + if error.reason_code == "ID_ORDER_MISMATCH": + # WHY: 纠错轮明确给出冻结输入要求的完整 ID 集;适配器仍严格校验 + # 缺失、重复、越界和绑定,提示只帮助模型修正格式,不放宽覆盖门禁。 + correction["expectedVerdictIds"] = { + "assertionVerdicts": list(normalized["_expectedAssertionIds"]), + "hardConstraintVerdicts": list(normalized["_expectedConstraintIds"]), + } + correction_count += 1 + # 循环必然在 return 或 raise 处退出,此处不可达。 + raise SemanticDetectorContractError("SEMANTIC_DETECTOR_RUNTIME_FAILED", "纠错环意外退出") except SemanticDetectorContractError as error: - return _failure(error) + return _failure( + error, + attempt_count=attempt_count, + correction_count=correction_count, + ) def _closed(properties: Mapping[str, Any], required: Sequence[str], optional: Sequence[str] = ()) -> dict[str, Any]: @@ -603,11 +944,126 @@ SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA = _closed( ) +# 检测输入 sourceRef 闭集:与 _source_ref 合同逐字段对齐。writer 上下文的证据 sourceRef +# 允许携带 sourceType 等多余字段,投影给 detector 时必须清洗成闭集形状(上下文本身不动)。 +_INPUT_SOURCE_REF_ALLOWED = frozenset( + {"sourceId", "sourceVersion", "chapter", "blockId", "startCodePoint", "endCodePoint", "contentSha256"} +) + + +def _clean_input_source_ref(ref: Any) -> Any: + """把单个 sourceRef 深拷贝并清洗成检测输入闭集形状。""" + + if not isinstance(ref, Mapping): + return copy.deepcopy(ref) + return {key: copy.deepcopy(ref[key]) for key in ref if key in _INPUT_SOURCE_REF_ALLOWED} + + +def _clean_input_evidence(evidence: Any) -> Any: + """深拷贝证据列表,仅清洗每条证据的 sourceRef 子对象。""" + + if not isinstance(evidence, list): + return copy.deepcopy(evidence) + cleaned: list[Any] = [] + for item in evidence: + if not isinstance(item, Mapping): + cleaned.append(copy.deepcopy(item)) + continue + new_item = {key: copy.deepcopy(value) for key, value in item.items() if key != "sourceRef"} + if "sourceRef" in item: + new_item["sourceRef"] = _clean_input_source_ref(item["sourceRef"]) + cleaned.append(new_item) + return cleaned + + +def _project_outline_for_input(writer_context: Mapping[str, Any]) -> tuple[dict[str, Any], list[dict[str, str]]]: + """把写手细纲投影成 detector 输入的稳定 ID 合同(constraint-N / declared-fact-N)。""" + + outline = writer_context.get("fineOutline") + if not isinstance(outline, Mapping): + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_INPUT_SCHEMA_INVALID", "writerContext.fineOutline 必须是对象" + ) + raw_constraints = outline.get("hardConstraints") or [] + if not isinstance(raw_constraints, list): + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_INPUT_SCHEMA_INVALID", "fineOutline.hardConstraints 必须是数组" + ) + constraints = [ + {"constraintId": f"constraint-{index + 1}", "text": str(text)} + for index, text in enumerate(raw_constraints) + ] + declared: list[dict[str, Any]] = [] + raw_declared = outline.get("declaredNewFacts") or [] + if not isinstance(raw_declared, list): + raise SemanticDetectorContractError( + "SEMANTIC_DETECTOR_INPUT_SCHEMA_INVALID", "fineOutline.declaredNewFacts 必须是数组" + ) + for index, value in enumerate(raw_declared): + if isinstance(value, Mapping): + declared.append({ + "factId": str(value.get("factId") or f"declared-fact-{index + 1}"), + "text": str(value.get("text") or ""), + "sourceRef": copy.deepcopy(value.get("sourceRef") or outline["sourceRef"]), + }) + else: + declared.append({ + "factId": f"declared-fact-{index + 1}", + "text": str(value), + "sourceRef": copy.deepcopy(outline["sourceRef"]), + }) + projected = { + "sourceRef": copy.deepcopy(outline["sourceRef"]), + "hardConstraints": constraints, + "adjustableBeats": [str(item) for item in (outline.get("adjustableBeats") or [])], + "declaredNewFacts": declared, + } + return projected, constraints + + +def build_semantic_input_v3( + *, + run_id: str, + sample_id: str, + opaque_arm_id: str, + writer_context: Mapping[str, Any], + candidate: Mapping[str, Any], +) -> dict[str, Any]: + """把 WriterContext + 候选投影成严格 semantic-detector-input-v3(生产/回放共用)。 + + 只依赖 writer_context 与 candidate 两个输入,不读库不读文件;投影后立即由 + validate_semantic_detector_input 做闭集校验,合同漂移在此失败关闭。 + """ + + fine_outline, constraints = _project_outline_for_input(writer_context) + payload = { + "schemaVersion": INPUT_VERSION, + "runId": run_id, + "sampleId": sample_id, + "opaqueArmId": opaque_arm_id, + "candidateVersion": candidate["candidateVersion"], + "candidateSha256": candidate["candidateSha256"], + "candidateBody": candidate["candidateBody"], + "contextSnapshotSha256": writer_context["contextSnapshot"]["contextSha256"], + "fineOutline": fine_outline, + "hardConstraints": constraints, + "factEvidence": _clean_input_evidence(writer_context.get("factEvidence", [])), + "proseEvidence": _clean_input_evidence(writer_context.get("proseEvidence", [])), + "asOf": writer_context["asOf"], + "authorizationSnapshotId": writer_context["authorizationSnapshot"]["snapshotId"], + } + payload["inputSha256"] = canonical_sha256(payload) + # 先做闭集校验失败关闭,再返回不含内部 "_" 派生键的干净输入(供 run/detector 复用)。 + validate_semantic_detector_input(payload) + return json.loads(_canonical_json(payload)) + + __all__ = [ "ModelRunner", "ClaudeRuntimeModelRunner", "SemanticDetectorContractError", "INPUT_VERSION", "MODEL_OUTPUT_VERSION", "REPORT_VERSION", "SEVERITIES", "FINDING_CATEGORIES", "SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA", "canonical_sha256", "validate_semantic_detector_input", "build_semantic_model_input", "build_semantic_detection", "validate_semantic_detector_report", - "calculate_semantic_metrics", "run_writer_semantic_detector", + "calculate_semantic_metrics", "build_safe_semantic_diagnostic", + "run_writer_semantic_detector", "build_semantic_input_v3", ] diff --git a/.claude/skills/check-content-consistency/scripts/test_build_semantic_input.py b/.claude/skills/check-content-consistency/scripts/test_build_semantic_input.py new file mode 100644 index 0000000..c29038e --- /dev/null +++ b/.claude/skills/check-content-consistency/scripts/test_build_semantic_input.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""build_semantic_input_v3 生产投影的离线测试。 + +验证:WriterContext + 候选能被投影成通过闭集校验的 semantic-detector-input-v3; +sourceRef 多余字段被清洗;身份字段严格绑定;哈希自洽。 + +跑法:.venv/bin/python .claude/skills/check-content-consistency/scripts/test_build_semantic_input.py +""" + +from __future__ import annotations + +import copy +import pathlib +import sys +import unittest + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, + SKILLS_DIR / "check-content-consistency" / "scripts", + SKILLS_DIR / "write-next-chapter" / "scripts", + SKILLS_DIR / "assemble-context" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from run_writer_semantic_detector import ( # noqa: E402 + build_semantic_input_v3, validate_semantic_detector_input, +) +from test_check_writer_candidate import _valid_pair # noqa: E402 + + +class BuildSemanticInputV3Test(unittest.TestCase): + def test_projects_valid_input_that_passes_closed_validation(self) -> None: + context, candidate = _valid_pair() + payload = build_semantic_input_v3( + run_id=context["runId"], sample_id="writer-ch2", opaque_arm_id="production", + writer_context=context, candidate=candidate) + # 返回值本身必须能再过一遍闭集校验(幂等自洽) + validated = validate_semantic_detector_input(payload) + self.assertEqual(validated["runId"], context["runId"]) + self.assertEqual(validated["candidateSha256"], candidate["candidateSha256"]) + self.assertEqual(validated["candidateVersion"], candidate["candidateVersion"]) + self.assertEqual( + validated["contextSnapshotSha256"], context["contextSnapshot"]["contextSha256"]) + self.assertEqual(validated["asOf"], context["asOf"]) + self.assertNotIn("_expectedAssertionIds", payload) + + def test_cleans_source_ref_extra_fields(self) -> None: + context, candidate = _valid_pair() + # _bound_context 的 factEvidence.sourceRef 带 sourceType 多余字段,必须被清洗掉 + payload = build_semantic_input_v3( + run_id=context["runId"], sample_id="writer-ch2", opaque_arm_id="production", + writer_context=context, candidate=candidate) + for item in payload["factEvidence"]: + self.assertNotIn("sourceType", item["sourceRef"]) + # 原上下文不被改动 + self.assertIn("sourceType", context["factEvidence"][0]["sourceRef"]) + + def test_outline_constraints_get_stable_ids(self) -> None: + context, candidate = _valid_pair() + payload = build_semantic_input_v3( + run_id=context["runId"], sample_id="writer-ch2", opaque_arm_id="production", + writer_context=context, candidate=candidate) + ids = [item["constraintId"] for item in payload["hardConstraints"]] + self.assertEqual(ids, [f"constraint-{i + 1}" for i in range(len(ids))]) + self.assertEqual( + [item["constraintId"] for item in payload["fineOutline"]["hardConstraints"]], ids) + + def test_candidate_body_hash_mismatch_fails_closed(self) -> None: + context, candidate = _valid_pair() + bad = copy.deepcopy(candidate) + bad["candidateSha256"] = "sha256:" + "f" * 64 + with self.assertRaises(Exception): + build_semantic_input_v3( + run_id=context["runId"], sample_id="writer-ch2", opaque_arm_id="production", + writer_context=context, candidate=bad) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/detect/scripts/test_check_writer_candidate.py b/.claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py similarity index 97% rename from .claude/skills/detect/scripts/test_check_writer_candidate.py rename to .claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py index b21153e..ffb13b8 100644 --- a/.claude/skills/detect/scripts/test_check_writer_candidate.py +++ b/.claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py @@ -11,8 +11,8 @@ import unittest SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -CONTINUATION_DIR = SKILLS_DIR / "continuation" / "scripts" -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" +CONTINUATION_DIR = SKILLS_DIR / "write-next-chapter" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" for path in (SCRIPT_DIR, CONTINUATION_DIR, READ_CONTEXT_DIR): sys.path.insert(0, str(path)) diff --git a/.claude/skills/detect/scripts/test_run_writer_semantic_detector.py b/.claude/skills/check-content-consistency/scripts/test_run_writer_semantic_detector.py similarity index 50% rename from .claude/skills/detect/scripts/test_run_writer_semantic_detector.py rename to .claude/skills/check-content-consistency/scripts/test_run_writer_semantic_detector.py index 1dd857c..9f45b2d 100644 --- a/.claude/skills/detect/scripts/test_run_writer_semantic_detector.py +++ b/.claude/skills/check-content-consistency/scripts/test_run_writer_semantic_detector.py @@ -8,7 +8,7 @@ import hashlib import pathlib import sys import unittest -from typing import Any, Mapping +from typing import Any, Mapping, Sequence sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) @@ -16,6 +16,7 @@ from run_writer_semantic_detector import ( # noqa: E402 SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA, SemanticDetectorContractError, _quote_location, + build_safe_semantic_diagnostic, canonical_sha256, calculate_semantic_metrics, run_writer_semantic_detector, @@ -143,6 +144,198 @@ class FakeRunner: return {"structuredOutput": copy.deepcopy(self.output), "modelReceiptSha256": self.receipt_hash} +class SequenceFakeRunner: + """按调用次序依次返回预设产出,用于驱动自我纠错环。""" + + def __init__(self, outputs: Sequence[Any], receipt_hash: str = "sha256:" + "3" * 64) -> None: + self.outputs = [ + output if isinstance(output, BaseException) else copy.deepcopy(dict(output)) + for output in outputs + ] + self.receipt_hash = receipt_hash + self.calls: list[dict[str, Any]] = [] + + def run(self, *, adapter_role: str, model_input: Mapping[str, Any], output_schema: Mapping[str, Any]) -> Mapping[str, Any]: + self.calls.append({"role": adapter_role, "input": copy.deepcopy(dict(model_input)), "schema": output_schema}) + # 超出预设数量后一直复用最后一份产出,便于断言纠错轮次上界。 + output = self.outputs[min(len(self.calls) - 1, len(self.outputs) - 1)] + if isinstance(output, BaseException): + raise output + return {"structuredOutput": copy.deepcopy(output), "modelReceiptSha256": self.receipt_hash} + + +class SemanticDetectorCorrectionTest(unittest.TestCase): + """检测自我纠错环:首轮引文不合格 → 回喂上一轮产出+原因 → 纠错后合格。""" + + def test_quote_not_found_then_corrected_returns_ok_after_two_calls(self) -> None: + # 首轮引了一句正文里没有的话(QUOTE_NOT_FOUND),纠错后换成正文里真实存在的原话。 + bad = semantic_draft() + bad["assertionVerdicts"][0]["candidateQuote"] = "正文里根本没有的引文" + good = semantic_draft() + runner = SequenceFakeRunner([bad, good]) + + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertTrue(result["ok"]) + self.assertEqual(result["status"], "passed") + # 模型被调了 2 次:首轮不合格 + 一轮纠错。 + self.assertEqual(len(runner.calls), 2) + # 首轮输入不带 correction。 + self.assertNotIn("correction", runner.calls[0]["input"]) + # 第二轮输入携带 correction:previousDraft 是首轮原始产出,error 说明引文不在正文里。 + correction = runner.calls[1]["input"]["correction"] + self.assertEqual(correction["previousDraft"], bad) + self.assertIn("引文未在候选正文中出现", correction["error"]) + # 纠错不放宽校验:第二轮合格产出仍被完整绑定(offset 由 adapter 计算)。 + verdict = result["report"]["assertionVerdicts"][0] + self.assertEqual(verdict["candidateQuote"], "林澈守住城门") + self.assertEqual(verdict["startCodePoint"], 0) + + def test_id_set_mismatch_correction_receives_expected_verdict_ids(self) -> None: + """ID 集错误时把冻结顺序显式回喂,但仍由适配器执行完整覆盖校验。""" + + bad = semantic_draft() + bad["assertionVerdicts"][0]["assertionId"] = "wrong-id" + runner = SequenceFakeRunner([bad, semantic_draft()]) + + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertTrue(result["ok"], result) + self.assertEqual( + runner.calls[1]["input"]["correction"]["expectedVerdictIds"], + {"assertionVerdicts": ["evidence-1"], "hardConstraintVerdicts": ["constraint-1"]}, + ) + + def test_transient_api_error_retries_same_input_without_fake_correction(self) -> None: + """可信 API 瞬时错误占用现有槽位,重发原输入后可恢复。""" + + api_error = SemanticDetectorContractError( + "SEMANTIC_DETECTOR_API_ERROR", "瞬时 API 错误" + ) + runner = SequenceFakeRunner([api_error, semantic_draft()]) + + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertTrue(result["ok"], result) + self.assertEqual(result["attemptCount"], 2) + self.assertEqual(result["correctionCount"], 0) + self.assertEqual(len(runner.calls), 2) + self.assertNotIn("correction", runner.calls[0]["input"]) + self.assertNotIn("correction", runner.calls[1]["input"]) + + def test_second_transient_api_error_fails_without_third_call(self) -> None: + """API 瞬时错误最多重试一次,不能吃掉所有格式纠错槽位后继续盲重试。""" + + first = SemanticDetectorContractError( + "SEMANTIC_DETECTOR_API_ERROR", "第一次瞬时 API 错误" + ) + second = SemanticDetectorContractError( + "SEMANTIC_DETECTOR_API_ERROR", "第二次瞬时 API 错误" + ) + runner = SequenceFakeRunner([first, second, semantic_draft()]) + + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "SEMANTIC_DETECTOR_API_ERROR") + self.assertEqual(result["safeDiagnostic"]["attemptCount"], 2) + self.assertEqual(result["safeDiagnostic"]["correctionCount"], 0) + self.assertEqual(len(runner.calls), 2) + + def test_all_rounds_invalid_exhausts_corrections_and_fails_closed(self) -> None: + # 三轮都引错(max_corrections=2 → 总共最多 3 次调用),最终返回最后一轮的失败。 + bad = semantic_draft() + bad["assertionVerdicts"][0]["candidateQuote"] = "始终不在正文里的引文" + runner = SequenceFakeRunner([bad]) + + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "SEMANTIC_DETECTOR_QUOTE_NOT_FOUND") + self.assertEqual(len(runner.calls), 3) + # 第二、三轮都带纠错反馈,首轮不带。 + self.assertNotIn("correction", runner.calls[0]["input"]) + self.assertIn("correction", runner.calls[1]["input"]) + self.assertIn("correction", runner.calls[2]["input"]) + self.assertEqual( + result["safeDiagnostic"], + { + "schemaVersion": "semantic-diagnostic-v1", + "outcome": "invalid", + "primaryCode": "SEMANTIC_DETECTOR_QUOTE_NOT_FOUND", + "reasonCode": "QUOTE_NOT_FOUND", + "section": "assertion_verdicts", + "attemptCount": 3, + "correctionCount": 2, + "blockingCounts": { + "highFindings": 0, + "failedAssertions": 0, + "failedHardConstraints": 0, + "conflictingClaims": 0, + "evidenceGaps": 0, + "unknownAssertions": 0, + "unknownHardConstraints": 0, + "unknownClaims": 0, + }, + }, + ) + + def test_hostile_extra_key_never_enters_safe_diagnostic(self) -> None: + hostile = semantic_draft() + hostile["raw-/private/tmp-候选正文"] = "不应出现在安全摘要" + + result = run_writer_semantic_detector( + semantic_input(), model_runner=FakeRunner(hostile), max_corrections=0 + ) + + self.assertFalse(result["ok"]) + diagnostic = result["safeDiagnostic"] + self.assertEqual(diagnostic["reasonCode"], "FIELD_SET_INVALID") + self.assertEqual(diagnostic["section"], "model_output") + serialized = str(diagnostic) + for forbidden in ("raw-/private/tmp-候选正文", "不应出现在安全摘要", "missing", "extra"): + self.assertNotIn(forbidden, serialized) + + def test_safe_diagnostic_rejects_untrusted_code_and_unbounded_counts(self) -> None: + diagnostic = build_safe_semantic_diagnostic( + { + "ok": False, + "primaryCode": "SEMANTIC_BAD\n/private/tmp/raw", + "safeDiagnostic": { + "primaryCode": "SEMANTIC_BAD\n/private/tmp/raw", + "reasonCode": "不可信原因", + "section": "$.candidateBody", + "attemptCount": 100_000_000, + "correctionCount": 100_000_000, + }, + } + ) + + self.assertEqual(diagnostic["primaryCode"], "SEMANTIC_DETECTOR_INVALID") + self.assertEqual(diagnostic["reasonCode"], "CONTRACT_INVALID") + self.assertEqual(diagnostic["section"], "model_output") + self.assertEqual(diagnostic["attemptCount"], 10_000) + self.assertEqual(diagnostic["correctionCount"], 9_999) + self.assertNotIn("/private/tmp", str(diagnostic)) + + def test_runner_failure_without_draft_is_not_corrected(self) -> None: + # runner 底座失败没有模型原始产出,纠错帮不上忙:只调一次即失败关闭。 + class BrokenRunner: + def __init__(self) -> None: + self.calls = 0 + + def run(self, *, adapter_role: str, model_input: Mapping[str, Any], output_schema: Mapping[str, Any]) -> Mapping[str, Any]: + self.calls += 1 + return {"structuredOutput": None} # 缺 modelReceiptSha256 → _invoke_model_runner 抛错 + + runner = BrokenRunner() + result = run_writer_semantic_detector(semantic_input(), model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "SEMANTIC_DETECTOR_RECEIPT_BINDING_MISMATCH") + self.assertEqual(runner.calls, 1) + + class SemanticDetectorV3Test(unittest.TestCase): def test_model_schema_rejects_hash_offset_and_runtime_identity(self) -> None: schema = SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA @@ -180,6 +373,38 @@ class SemanticDetectorV3Test(unittest.TestCase): for forbidden in ("runId", "sampleId", "opaqueArmId", "candidateSha256", "contextSnapshotSha256", "authorizationSnapshotId", "inputSha256"): self.assertNotIn(forbidden, serialized) + def test_verdict_id_set_is_normalized_to_input_order(self) -> None: + """同一完整 ID 集乱序时确定性重排,缺失/重复仍由合同拒绝。""" + + payload = semantic_input() + payload["factEvidence"].append({ + "evidenceId": "evidence-2", + "fact": "旧徽章在城门", + "sourceType": "historical_prose", + "sourceRef": {"sourceId": "prose:487", "sourceVersion": "v1", "chapter": 487}, + "contentSha256": "sha256:" + "3" * 64, + "riskLevel": "low", + }) + payload["inputSha256"] = canonical_sha256({ + key: value for key, value in payload.items() if key != "inputSha256" + }) + draft = semantic_draft() + draft["assertionVerdicts"].append({ + "assertionId": "evidence-2", + "verdict": "pass", + "candidateQuote": "林澈守住城门", + "evidenceIds": ["evidence-2"], + }) + draft["assertionVerdicts"].reverse() + + result = run_writer_semantic_detector(payload, model_runner=FakeRunner(draft)) + + self.assertTrue(result["ok"], result) + self.assertEqual( + [item["assertionId"] for item in result["report"]["assertionVerdicts"]], + ["evidence-1", "evidence-2"], + ) + def test_duplicate_quote_now_binds_first_occurrence(self) -> None: # 引文在候选中出现多次不再失败关闭:绑定到首次出现("。" 在正文里出现两次)。 duplicate = semantic_draft() @@ -214,6 +439,46 @@ class SemanticDetectorV3Test(unittest.TestCase): self.assertEqual(result["report"]["status"], "failed") self.assertEqual(result["report"]["findings"][0]["severity"], "high") self.assertEqual(result["metrics"]["highSeverityCount"], 1) + diagnostic = build_safe_semantic_diagnostic(result) + self.assertEqual(diagnostic["outcome"], "failed") + self.assertEqual(diagnostic["reasonCode"], "SEMANTIC_BLOCKED") + self.assertEqual(diagnostic["blockingCounts"]["highFindings"], 1) + self.assertEqual(diagnostic["blockingCounts"]["failedHardConstraints"], 0) + serialized = str(diagnostic) + for forbidden in ("candidateQuote", "message", "旧徽章", "constraint-1"): + self.assertNotIn(forbidden, serialized) + + def test_corrected_blocking_report_preserves_attempt_count(self) -> None: + invalid = semantic_draft() + invalid["assertionVerdicts"][0]["candidateQuote"] = "正文里不存在的引文" + result = run_writer_semantic_detector( + semantic_input(), + model_runner=SequenceFakeRunner([invalid, semantic_draft(failed=True)]), + ) + + diagnostic = build_safe_semantic_diagnostic(result) + + self.assertEqual(result["attemptCount"], 2) + self.assertEqual(diagnostic["outcome"], "failed") + self.assertEqual(diagnostic["attemptCount"], 2) + self.assertEqual(diagnostic["correctionCount"], 1) + + def test_needs_evidence_safe_diagnostic_contains_counts_only(self) -> None: + result = run_writer_semantic_detector( + semantic_input(), model_runner=FakeRunner(semantic_draft(gap=True)) + ) + + diagnostic = build_safe_semantic_diagnostic(result) + + self.assertEqual(diagnostic["outcome"], "needs_evidence") + self.assertEqual(diagnostic["primaryCode"], "SEMANTIC_EVIDENCE_REQUIRED") + self.assertEqual(diagnostic["blockingCounts"]["evidenceGaps"], 1) + self.assertEqual(diagnostic["blockingCounts"]["unknownAssertions"], 1) + serialized = str(diagnostic) + for forbidden in ( + "candidateQuote", "旧徽章来源", "候选出现未覆盖物品", "gap-1" + ): + self.assertNotIn(forbidden, serialized) def test_ellipsis_reference_not_flagged_but_real_traversal_blocked(self) -> None: # 省略号 `...` 含子串 `..`,旧的 `".." in text` 会误判为路径穿越;精确判定必须放行。 @@ -238,6 +503,66 @@ class SemanticDetectorV3Test(unittest.TestCase): self.assertFalse(result["ok"]) self.assertEqual(result["primaryCode"], "SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID") + def test_non_unknown_claim_gap_reason_is_deterministically_discarded(self) -> None: + # WHY: supported/conflict/declared_new 已有闭集结论,模型残留的解释不应阻断整份报告, + # 也不得进入可信报告参与状态或哈希计算。 + for coverage_state in ("supported", "conflict", "declared_new"): + with self.subTest(coverage_state=coverage_state): + draft = semantic_draft() + draft["claims"][0]["coverageState"] = coverage_state + draft["claims"][0]["gapReason"] = "模型残留的冗余解释" + + result = run_writer_semantic_detector( + semantic_input(), model_runner=FakeRunner(draft), max_corrections=0 + ) + + self.assertTrue(result["ok"]) + bound_claim = result["report"]["claims"][0] + self.assertEqual(bound_claim["coverageState"], coverage_state) + self.assertNotIn("gapReason", bound_claim) + + def test_non_unknown_verdict_gap_reason_is_deterministically_discarded(self) -> None: + # assertion 与 hard constraint 共用同一绑定器;分别覆盖 pass/fail,确保两类列表都收敛。 + cases = ( + ("assertionVerdicts", "pass"), + ("assertionVerdicts", "fail"), + ("hardConstraintVerdicts", "pass"), + ("hardConstraintVerdicts", "fail"), + ) + for field, verdict in cases: + with self.subTest(field=field, verdict=verdict): + draft = semantic_draft() + draft[field][0]["verdict"] = verdict + draft[field][0]["gapReason"] = "模型残留的冗余解释" + + result = run_writer_semantic_detector( + semantic_input(), model_runner=FakeRunner(draft), max_corrections=0 + ) + + self.assertTrue(result["ok"]) + bound_verdict = result["report"][field][0] + self.assertEqual(bound_verdict["verdict"], verdict) + self.assertNotIn("gapReason", bound_verdict) + + def test_unknown_claim_and_hard_constraint_without_gap_reason_fail_closed(self) -> None: + # unknown 的解释不是冗余字段:缺失时仍须失败关闭,防止“未知”成为无理由逃生口。 + cases = ( + ("claims", "coverageState"), + ("hardConstraintVerdicts", "verdict"), + ) + for field, state_field in cases: + with self.subTest(field=field): + draft = semantic_draft() + draft[field][0][state_field] = "unknown" + + result = run_writer_semantic_detector( + semantic_input(), model_runner=FakeRunner(draft), max_corrections=0 + ) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "SEMANTIC_DETECTOR_MODEL_OUTPUT_INVALID") + self.assertEqual(result["safeDiagnostic"]["reasonCode"], "GAP_REASON_REQUIRED") + def test_old_v2_and_v1_reports_fail_closed(self) -> None: payload = semantic_input() for version in ("semantic-detector-report-v2", "semantic-detector-report-v1"): diff --git a/.claude/skills/clean/SKILL.md b/.claude/skills/clean-book-text/SKILL.md similarity index 53% rename from .claude/skills/clean/SKILL.md rename to .claude/skills/clean-book-text/SKILL.md index 1ddfe23..723a7ee 100644 --- a/.claude/skills/clean/SKILL.md +++ b/.claude/skills/clean-book-text/SKILL.md @@ -1,29 +1,29 @@ --- -name: clean -description: LLM 辅助正文清洗——MiniMax-M3 按窗口(约10万字)只输出待删垃圾段原文与理由,代码做精确匹配删除并全程留审计(example_clean_log)。LLM 不改写正文,删不删由守卫规则最终裁决。 +name: clean-book-text +description: 识别并删除参考书或旧稿中的广告、水印、作者拉票和乱码噪声,同时保留逐段审计。静态导入后仍有语义垃圾时使用;模型只提候选,确定性守卫决定是否删除,绝不改写正文。 --- -# clean —— LLM 检测 + 代码执行的正文清洗 +# 清洗书稿正文 -分工(创始人方案 2026-07-13):**规则层**已在 import 解决结构性垃圾(水印正则/重贴章题/目录页/分页尾巴/残破实体);本 skill 处理**语义垃圾**——变体书站广告、作者拉票/PS 段、微信导流、乱码水印等正则打不全的散落噪声。LLM 只当探测器(输出待删片段逐字原文),删除由脚本执行:零改写、可审计、可回放。探测模型=New-API `MiniMax-M3`(经 llm skill 的受治理入口 `chat_governed`;模型降级与额度治理走全局 `BUDGET_CHAIN` 与 5h 额度窗,本 skill 不自写降级链。全链耗尽时该窗记 skipped、不清洗、不返回假成功——网文正文会触发上游敏感词拦截,由治理链自动换模型救回)。 +分工(创始人方案 2026-07-13):**规则层**已在 `import-book` 解决结构性垃圾(水印正则/重贴章题/目录页/分页尾巴/残破实体);本 Skill 处理**语义垃圾**——变体书站广告、作者拉票/PS 段、微信导流、乱码水印等正则打不全的散落噪声。LLM 只当探测器(输出待删片段逐字原文),删除由脚本执行:零改写、可审计、可回放。探测模型=New-API `MiniMax-M3`(经 `call-content-model` 的受治理入口 `chat_governed`;模型降级与额度治理走全局 `BUDGET_CHAIN` 与 5h 额度窗,本 Skill 不自写降级链。全链耗尽时该窗记 skipped、不清洗、不返回假成功——网文正文会触发上游敏感词拦截,由治理链自动换模型救回)。 ## 流程 ```bash # 放量驱动(每书 prep→detect→apply 全链;断点续跑/幂等防重删;可多进程分书并行) -.venv/bin/python .claude/skills/clean/scripts/clean_batch.py --work-id 7 --work-id 10 --batch <批次号> +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_batch.py --work-id 7 --work-id 10 --batch <批次号> # 单步(调试/演示用) -.venv/bin/python .claude/skills/clean/scripts/clean_prep.py --work-id 7 [--from 1 --to 50] # 切窗 -.venv/bin/python .claude/skills/clean/scripts/clean_detect.py --work-id 7 [--win 1] # M3 探测 -.venv/bin/python .claude/skills/clean/scripts/clean_apply.py --work-id 7 --batch X --file … [--dry-run] [--report-md docs/清洗-N-书名.md] +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_prep.py --work-id 7 [--from 1 --to 50] # 切窗 +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_detect.py --work-id 7 [--win 1] # M3 探测 +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_apply.py --work-id 7 --batch X --file … [--dry-run] [--report-md docs/清洗-N-书名.md] # 收尾收割:高频水印全书规则扫净(LLM 每窗只报样例,重复水印靠种子收割) -.venv/bin/python .claude/skills/clean/scripts/clean_sweep.py --work-id 7 --batch X # 审计自动种子(重复≥3次且≥20字) -.venv/bin/python .claude/skills/clean/scripts/clean_sweep.py --work-id 4 --batch X --seed "http://m." # 人工确认的碎水印 +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_sweep.py --work-id 7 --batch X # 审计自动种子(重复≥3次且≥20字) +.venv/bin/python .claude/skills/clean-book-text/scripts/clean_sweep.py --work-id 4 --batch X --seed "http://m." # 人工确认的碎水印 # 审查:审计对账 -.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT batch, count(*), sum(length(removed_text)) FROM example_clean_log WHERE work_id=7 GROUP BY batch" +.venv/bin/python .claude/skills/access-database/scripts/db.py query "SELECT batch, count(*), sum(length(removed_text)) FROM example_clean_log WHERE work_id=7 GROUP BY batch" ``` ⚠️ prep 会覆盖 /tmp/muse-clean// 的窗与 manifest——换范围重切前先归档旧产物目录。 diff --git a/.claude/skills/clean/scripts/clean_apply.py b/.claude/skills/clean-book-text/scripts/clean_apply.py similarity index 99% rename from .claude/skills/clean/scripts/clean_apply.py rename to .claude/skills/clean-book-text/scripts/clean_apply.py index 53770f8..0cbc8d5 100644 --- a/.claude/skills/clean/scripts/clean_apply.py +++ b/.claude/skills/clean-book-text/scripts/clean_apply.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""clean skill:精确匹配删除执行器(LLM 建议 ≠ 必删,守卫规则最终裁决)+ 审计入库。 +"""clean-book-text Skill:精确匹配删除执行器(LLM 建议 ≠ 必删,守卫规则最终裁决)+ 审计入库。 输入 deletions JSON:[{chapter_order|chapter, exact, reason}](键名 ASCII,兼容中文键)。 diff --git a/.claude/skills/clean/scripts/clean_batch.py b/.claude/skills/clean-book-text/scripts/clean_batch.py similarity index 96% rename from .claude/skills/clean/scripts/clean_batch.py rename to .claude/skills/clean-book-text/scripts/clean_batch.py index ab7f0a2..ed4d19a 100644 --- a/.claude/skills/clean/scripts/clean_batch.py +++ b/.claude/skills/clean-book-text/scripts/clean_batch.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""clean skill:放量驱动——每书串行跑 prep→detect→apply 全链,可多进程分书并行。 +"""clean-book-text Skill:放量驱动——每书串行跑 prep→detect→apply 全链,可多进程分书并行。 断点续跑设计: - prep 仅在该书 manifest 缺失时执行(防覆盖已切窗); diff --git a/.claude/skills/clean/scripts/clean_detect.py b/.claude/skills/clean-book-text/scripts/clean_detect.py similarity index 94% rename from .claude/skills/clean/scripts/clean_detect.py rename to .claude/skills/clean-book-text/scripts/clean_detect.py index 768ef7a..8bcf094 100644 --- a/.claude/skills/clean/scripts/clean_detect.py +++ b/.claude/skills/clean-book-text/scripts/clean_detect.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""clean skill:LLM 探测执行器——每窗一次受治理调用(chat_governed),产出 deletions JSON。 +"""clean-book-text Skill:LLM 探测执行器——每窗一次受治理调用,产出 deletions JSON。 读 clean_prep 产出的 manifest.json,逐窗经 llm.chat_governed 探测垃圾段(只报逐字原文,不改写), 写 /tmp/muse-clean//deletions-NNN.json。断点续跑:已有产物的窗自动跳过。 @@ -13,8 +13,8 @@ import time import click -# 统一走 llm skill 受治理入口(额度窗/全局降级链/熔断 + trust_env/重试/剥离/JSON 容错都在那边) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +# 统一走 call-content-model 受治理入口(额度窗/全局降级链/熔断 + trust_env/重试/剥离/JSON 容错都在那边) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from llm import chat_governed, extract_json # noqa: E402 OUT = pathlib.Path("/tmp/muse-clean") @@ -65,7 +65,7 @@ def main(work_id, wins, model, force): # 降级与额度治理已上收 llm.chat_governed(全局 BUDGET_CHAIN + 5h 额度窗):撞内容安全/ # 模型不可用由它沿全局链自动换模型并按窗预算/调用数治理;本脚本不自写降级链。 # 单窗失败不中断整书——全链耗尽或输出无法解析时失败关闭:该窗不清洗、不返回假成功。 - content, usage, used_model = chat_governed(prompt, model=model) + content, usage, used_model = chat_governed(prompt, model=model, caller="clean") if used_model is None: # 治理链全部耗尽(多为上游敏感词拦截):写空产物占位(含跳过原因), # apply 端窗产物齐备可继续,审计可追——绝不拿空内容当成功 diff --git a/.claude/skills/clean/scripts/clean_prep.py b/.claude/skills/clean-book-text/scripts/clean_prep.py similarity index 95% rename from .claude/skills/clean/scripts/clean_prep.py rename to .claude/skills/clean-book-text/scripts/clean_prep.py index 8a999c0..11861ef 100644 --- a/.claude/skills/clean/scripts/clean_prep.py +++ b/.claude/skills/clean-book-text/scripts/clean_prep.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""clean skill:按章对齐切窗(LLM 探测输入面)。窗文件含章题标记行,便于 LLM 报 chapter_order。""" +"""clean-book-text Skill:按章对齐切窗。窗文件含章题标记行,便于 LLM 报 chapter_order。""" import json import pathlib import re diff --git a/.claude/skills/clean/scripts/clean_sweep.py b/.claude/skills/clean-book-text/scripts/clean_sweep.py similarity index 98% rename from .claude/skills/clean/scripts/clean_sweep.py rename to .claude/skills/clean-book-text/scripts/clean_sweep.py index 256ff50..9554357 100644 --- a/.claude/skills/clean/scripts/clean_sweep.py +++ b/.claude/skills/clean-book-text/scripts/clean_sweep.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""clean skill:高频水印全书规则收割(LLM 探测样例 → 代码全书扫净)。 +"""clean-book-text Skill:高频水印全书规则收割(LLM 探测样例 → 代码全书扫净)。 M3 按窗探测对「每章重复的固定水印」只会报样例章(窗内看到≠逐章报全),残留由本脚本收割: - 种子=example_clean_log 中该书重复删除段(相同 removed_text 出现 ≥min-occur 次、长度 ≥min-len)—— diff --git a/.claude/skills/clean/scripts/test_clean_detect_offline.py b/.claude/skills/clean-book-text/scripts/test_clean_detect_offline.py similarity index 100% rename from .claude/skills/clean/scripts/test_clean_detect_offline.py rename to .claude/skills/clean-book-text/scripts/test_clean_detect_offline.py diff --git a/.claude/skills/confirm/SKILL.md b/.claude/skills/decide-candidate/SKILL.md similarity index 57% rename from .claude/skills/confirm/SKILL.md rename to .claude/skills/decide-candidate/SKILL.md index cc1678b..b0c0475 100644 --- a/.claude/skills/confirm/SKILL.md +++ b/.claude/skills/decide-candidate/SKILL.md @@ -1,6 +1,6 @@ --- -name: confirm -description: 确认=把创作产出从待审(Shadow)转为正式事实(Canonical)。正文候选经 scripts/write_canonical.py 单事务写库(正文块+来源归因+决策归档+翻候选态);知识卡走 draft→entity 双轨。全仓唯一的确认通道,仅由用户指令触发。 +name: decide-candidate +description: 根据用户明确指令接受、合并或丢弃 Shadow 候选,并以受控事务更新 Canonical、归因和决策记录。用户已经对正文、知识卡或规划作出决定时使用;不得由 Agent 自行触发。 --- # 确认 / 丢弃(Shadow→Canonical 的唯一入口) @@ -14,24 +14,26 @@ description: 确认=把创作产出从待审(Shadow)转为正式事实(Canonical 两步,顺序不可颠倒: 1. **先过接受前置检查(纯函数,不写库)**:`scripts/check_writer_acceptance.py` - - `check_shadow_ready` 校验生产模式、`acceptanceEligible=true`、候选正文 hash、冻结上下文 ID/hash、`writer-production-v1` 策略、来源状态、候选有效期,以及 detector 最终报告对当前 `attempt/candidateVersion/candidateSha256` 的绑定。 + - `check_shadow_ready` 校验生产模式、`acceptanceEligible=true`、候选正文 hash、冻结上下文 ID/hash、`writer-production-v1` 策略、来源状态、候选有效期,以及 detector 最终报告(`writer-pipeline-result-v1`,要求最终轨迹同时通过机械门与语义审查)对当前 `attempt/candidateVersion/candidateSha256` 的绑定。 + - 实时状态由 `scripts/acceptance_state.py` 从库重读(冻结行、授权快照、已确认细纲、Canonical revision、接受窗口),编排层不得用内存旧快照冒充。 - `accept/merge` 必须实时匹配 `expectedRevision`;冲突返回 `REVISION_CONFLICT`,不得覆盖 Canonical。 - `acceptanceEligible=false` 的诊断/评测候选在第一道门硬拒绝,不能靠改参数、重跑 fake 或用户确认混进接受链。 - - detector 真实模型尚未实现;fake 通过只用于接口测试,不能作为真实正文接受依据。 + - 生产链的 detector 终态来自 `run_writer_pipeline` 的真实机械门 + 语义 detector 双报告;LLM 自然语言 PASS 不是裁决依据,只有结构化报告过检才可接受。 -2. **前置通过后,经写入层落库(单事务)**:`scripts/write_canonical.py`(复用 db skill 的 DSN) +2. **前置通过后,经写入层落库(单事务)**:`scripts/write_canonical.py`(复用 `access-database` 的 DSN) - 接受: ```bash - .venv/bin/python .claude/skills/confirm/scripts/write_canonical.py accept \ - --expected-revision --rationale "为什么接受" --basis-ref "大纲@日期" --command-id <幂等ID> + .venv/bin/python .claude/skills/decide-candidate/scripts/write_canonical.py accept \ + --expected-revision --rationale "为什么接受" --basis-ref "大纲@日期" --command-id <幂等ID> \ + [--approved-deltas <已批准增量JSON数组>] # 先试跑(完整走一遍事务再回滚,校验不落库):加 --dry-run ``` - 丢弃: ```bash - .venv/bin/python .claude/skills/confirm/scripts/write_canonical.py discard --rationale "为什么丢弃" + .venv/bin/python .claude/skills/decide-candidate/scripts/write_canonical.py discard --rationale "为什么丢弃" ``` - - 写入层按落库设计 §2.9 单事务执行:写正文块(`content_text`,revision+1,CAS 乐观锁)→ 写来源归因(`muse_content_block_source_attribution`,来源权威落块,架构-02 §3)→ 写命令幂等审计 → 写决策归档(`example_user_decision`)→ 翻候选 `state='accepted'`。**任一失败整体回滚,绝不留无来源指针的正式正文。** - - DB 级兜底硬校验(不靠调用方自觉):`run_type` 非 production 拒绝接受(05 §8.4)、`state` 非 passed 拒绝、revision 冲突拒绝。 + - 写入层按落库设计 §2.9 单事务执行:写正文块(`content_text`,revision+1,CAS 乐观锁)→ 写来源归因(`muse_content_block_source_attribution`,来源权威落块,架构-02 §3)→ 合并已批准事实增量(`fact_delta.py`,进 `example_fact_ledger` 正典账本)→ 登记投影(`projection_registry.py`,旧 revision 投影翻 stale、新 revision 登记 pending)→ 写命令幂等审计 → 写决策归档(`example_user_decision`)→ 翻候选 `state='accepted'`。**任一失败整体回滚,绝不留无来源指针的正式正文,也绝不产生正文已提交而事实半合并。** + - DB 级兜底硬校验(不靠调用方自觉):`run_type` 非 production 拒绝接受(05 §8.4)、`state` 非 passed 拒绝、`semantic_status` 非 passed 拒绝(先审后入)、revision 冲突拒绝。 ## merge(修改后合并) @@ -40,7 +42,7 @@ description: 确认=把创作产出从待审(Shadow)转为正式事实(Canonical ## 知识卡 / 规划:各自的确认轨 -- **知识卡**:确认 = `muse_knowledge_draft` 翻 `confirmed` + 落 `muse_knowledge_entity(active)`(关系卡落 `muse_knowledge_relation`),并在同一事务内确保作品↔知识库绑定、迁移实体向量 owner。使用 `.venv/bin/python .claude/skills/confirm/scripts/confirm_knowledge.py --draft-id --dry-run` 试跑;实际确认只能在用户明确确认后执行。批量实体/关系必须显式给 `--all-entities ` 或 `--all-relations `。**采纳正文 ≠ 确认知识**,抽取产出的卡变更要单独确认;有冲突的卡先裁决再确认。 +- **知识卡**:确认 = `muse_knowledge_draft` 翻 `confirmed` + 落 `muse_knowledge_entity(active)`(关系卡落 `muse_knowledge_relation`),并在同一事务内确保作品↔知识库绑定、迁移实体向量 owner。使用 `.venv/bin/python .claude/skills/decide-candidate/scripts/confirm_knowledge.py --draft-id --dry-run` 试跑;实际确认只能在用户明确确认后执行。批量实体/关系必须显式给 `--all-entities ` 或 `--all-relations `。**采纳正文 ≠ 确认知识**,抽取产出的卡变更要单独确认;有冲突的卡先裁决再确认。 - **规划**(大纲/细纲/设定):规划表(100)落库前,暂以 git 提交确认——只 `git add` 用户点名的创作文件,**严禁混入框架文件(agents/skills/meta)**;提交信息 `作品(书名): 确认 设定包/大纲vN | 来源: planner`。规划表建成后改为库内 shadow→confirmed。 ## 红线 diff --git a/.claude/skills/decide-candidate/scripts/acceptance_state.py b/.claude/skills/decide-candidate/scripts/acceptance_state.py new file mode 100644 index 0000000..202dc11 --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/acceptance_state.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""接受前置检查的实时状态重读(生产模式)。 + +check_writer_acceptance 是无副作用纯函数,不碰库;本模块负责在**接受时刻**从 muse-example +重读冻结行、Canonical 正文 revision、已确认细纲状态与授权快照,组装严格 live_state。 +编排层不得用内存里的旧快照冒充实时状态——这里读到什么,preflight 就拿什么比对。 +""" + +from __future__ import annotations + +import json +import pathlib +import sys +from datetime import datetime, timedelta, timezone +from typing import Any, Mapping + +DB_DIR = pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts" +if str(DB_DIR) not in sys.path: + sys.path.insert(0, str(DB_DIR)) + +from db import connect # noqa: E402 + +PRODUCTION_POLICY = "writer-production-v1" +# 候选接受窗口:生成后 24 小时内必须完成接受,超期 preflight 报 CANDIDATE_EXPIRED。 +ACCEPTANCE_WINDOW = timedelta(hours=24) + + +class LiveStateError(RuntimeError): + """实时状态不可读或形状非法——失败关闭,不得带病进入 preflight。""" + + +def _bare_sha(value: str) -> str: + value = str(value or "") + return value[len("sha256:"):] if value.startswith("sha256:") else value + + +def _parse_tz(value: Any) -> datetime | None: + """解析带时区 ISO 时间;非法返回 None(由调用方决定是否失败关闭)。""" + + if not isinstance(value, str) or not value: + return None + try: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=timezone.utc) + return parsed + + +def _iso_z(value: datetime) -> str: + return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def build_live_acceptance_state( + context: Mapping[str, Any], *, now: datetime | None = None +) -> dict[str, Any]: + """按 check_writer_acceptance 的 live_state 闭集字段重读实时状态。""" + + if not isinstance(context, Mapping): + raise LiveStateError("context 必须是对象") + context_sha = _bare_sha(context.get("contextSnapshot", {}).get("contextSha256")) + if len(context_sha) != 64: + raise LiveStateError("context 缺 contextSnapshot.contextSha256") + work_id = context.get("workId") + target_chapter = context.get("targetChapter") + if not isinstance(work_id, int) or not isinstance(target_chapter, int): + raise LiveStateError("context 缺 workId/targetChapter") + checked_at = now or datetime.now(timezone.utc) + + with connect(readonly=True) as conn: + freeze = conn.execute( + "SELECT manifest_sha256, context_sha256, authorization_snapshot, create_time " + "FROM example_context_freeze WHERE context_sha256=%s ORDER BY id DESC LIMIT 1", + (context_sha,), + ).fetchone() + if not freeze: + raise LiveStateError("CONTEXT_NOT_FROZEN:冻结上下文未落库,接受前置检查拒绝继续") + manifest_sha, freeze_context_sha, auth_payload, freeze_time = freeze + + revision_row = conn.execute( + "SELECT COALESCE(MAX(b.revision),0) FROM muse_content_block b " + "JOIN muse_content_chapter c ON b.chapter_id=c.id AND c.deleted=false " + "WHERE c.work_id=%s AND c.order_no=%s AND b.deleted=false", + (work_id, target_chapter), + ).fetchone() + canonical_revision = int(revision_row[0]) + + outline_row = conn.execute( + "SELECT state FROM example_planning_section WHERE work_id=%s " + "AND section_type='fine_outline' AND target_chapter=%s AND deleted=false " + "ORDER BY version DESC LIMIT 1", + (work_id, target_chapter), + ).fetchone() + source_status = "active" if outline_row and outline_row[0] == "confirmed" else "stale" + + # 授权快照以冻结行为准重读:快照 ID 必须与候选绑定一致,且核验时间不晚于本次检查。 + auth = auth_payload if isinstance(auth_payload, Mapping) else {} + if isinstance(auth_payload, str): + try: + auth = json.loads(auth_payload) + except ValueError: + auth = {} + context_snapshot = context.get("authorizationSnapshot", {}) + context_snapshot_id = context_snapshot.get("snapshotId") if isinstance(context_snapshot, Mapping) else None + authorization_valid = bool( + auth.get("snapshotId") + and context_snapshot_id + and auth.get("snapshotId") == context_snapshot_id + ) + if authorization_valid: + verified_at = _parse_tz(auth.get("verifiedAt")) + authorization_valid = verified_at is not None and verified_at <= checked_at + + # WriterContext 合同里 generatedAt 住在 contextSnapshot 内,顶层没有该字段 + generated_at = _parse_tz(context.get("contextSnapshot", {}).get("generatedAt")) + if generated_at is None and freeze_time is not None: + generated_at = freeze_time.replace(tzinfo=timezone.utc) + if generated_at is None: + raise LiveStateError("无法确定候选生成时间,接受窗口不可计算") + expires_at = generated_at + ACCEPTANCE_WINDOW + + return { + "qualityPolicyVersion": PRODUCTION_POLICY, + "contextSnapshotId": "sha256:" + str(manifest_sha), + "contextSnapshotSha256": "sha256:" + str(freeze_context_sha), + "authorizationSnapshotId": str(auth.get("snapshotId") or ""), + "authorizationValid": authorization_valid, + "sourceStatus": source_status, + "candidateExpiresAt": _iso_z(expires_at), + "checkedAt": _iso_z(checked_at), + "canonicalRevision": canonical_revision, + } + + +__all__ = ["ACCEPTANCE_WINDOW", "LiveStateError", "PRODUCTION_POLICY", "build_live_acceptance_state"] diff --git a/.claude/skills/confirm/scripts/check_writer_acceptance.py b/.claude/skills/decide-candidate/scripts/check_writer_acceptance.py similarity index 99% rename from .claude/skills/confirm/scripts/check_writer_acceptance.py rename to .claude/skills/decide-candidate/scripts/check_writer_acceptance.py index 09bdff8..30711c7 100644 --- a/.claude/skills/confirm/scripts/check_writer_acceptance.py +++ b/.claude/skills/decide-candidate/scripts/check_writer_acceptance.py @@ -9,7 +9,7 @@ from datetime import datetime from typing import Any, Mapping SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "read-context" / "scripts" +READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" if str(READ_CONTEXT_DIR) not in sys.path: sys.path.insert(0, str(READ_CONTEXT_DIR)) diff --git a/.claude/skills/confirm/scripts/confirm_knowledge.py b/.claude/skills/decide-candidate/scripts/confirm_knowledge.py similarity index 99% rename from .claude/skills/confirm/scripts/confirm_knowledge.py rename to .claude/skills/decide-candidate/scripts/confirm_knowledge.py index ede788d..e54b138 100644 --- a/.claude/skills/confirm/scripts/confirm_knowledge.py +++ b/.claude/skills/decide-candidate/scripts/confirm_knowledge.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """知识草稿确认:把待审实体/关系写入作品正式知识面。 -正文候选和知识卡共用 confirm skill 的主权边界,但知识表不是正文表: +正文候选和知识卡共用 decide-candidate 的主权边界,但知识表不是正文表: 实体写入 ``muse_knowledge_entity``,关系写入 ``muse_knowledge_relation``, 草稿状态、知识库绑定和向量 owner 在同一事务内完成。默认命令只确认点名 草稿;批量确认必须显式给出 ``--all-entities`` 或 ``--all-relations``。 @@ -13,7 +13,7 @@ import sys from copy import deepcopy -DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/decide-candidate/scripts/fact_delta.py b/.claude/skills/decide-candidate/scripts/fact_delta.py new file mode 100644 index 0000000..d3b074d --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/fact_delta.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +"""结构化事实增量(DeltaProposal + reducer)—— 模型只提变更,事实由代码合并。 + +合同要点(先审后入的实体面): +- 模型/抽取只能提出**类型化增量提案**(六种闭集类型),不能重写整份状态; +- 每条提案必须带正文证据引文,reducer 确定性校验引文出现在候选正文中(不得编造证据); +- 提案默认 `proposed`,**抽取结果绝不自动升格**;只有显式批准的增量才随正文在同一事务 + 进 example_fact_ledger(ChapterCommit 的一部分),并绑正文块 revision 与 command_id; +- 账本 append-only:正文被替换后按 source_block_revision 判 stale,旧事实不冒充当前状态。 + +本模块的 validate/apply 是纯逻辑(apply 使用调用方传入的连接与事务,不自行提交); +propose_fact_deltas 是抽取侧登记提案的独立入口(自持短事务)。 +""" + +from __future__ import annotations + +import json +import pathlib +import sys +from typing import Any, Mapping, Sequence + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" +DB_DIR = SKILLS_DIR / "access-database" / "scripts" +for path in (READ_CONTEXT_DIR, DB_DIR): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from writer_contract import normalize_text # noqa: E402 + +DELTA_TYPES = frozenset({ + "character_location_changed", + "character_knowledge_added", + "relationship_changed", + "hook_advanced", + "timeline_event_added", + "setting_added", +}) +HOOK_ACTIONS = frozenset({"planted", "advanced", "resolved", "deferred"}) +_PROPOSAL_FIELDS = frozenset({"deltaId", "deltaType", "payload", "evidenceQuote"}) +CREATOR = "extractor" + + +class FactDeltaError(RuntimeError): + """增量提案非法或无法合并——失败关闭,不静默丢弃也不带病入库。""" + + def __init__(self, code: str, message: str) -> None: + super().__init__(message) + self.code = code + + +def _require_str(value: Any, path: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", f"{path} 必须是非空字符串") + return value + + +def _validate_payload(delta_type: str, payload: Any) -> dict[str, Any]: + """逐型校验 payload 闭集并返回归一化副本。""" + + if not isinstance(payload, Mapping): + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "payload 必须是对象") + payload = dict(payload) + + if delta_type == "character_location_changed": + if set(payload) - {"characterName", "fromLocation", "toLocation"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "位置增量含未知字段") + _require_str(payload.get("characterName"), "payload.characterName") + _require_str(payload.get("toLocation"), "payload.toLocation") + if payload.get("fromLocation") is not None: + _require_str(payload["fromLocation"], "payload.fromLocation") + elif delta_type == "character_knowledge_added": + if set(payload) != {"characterName", "knowledge"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "知情增量字段非法") + _require_str(payload.get("characterName"), "payload.characterName") + _require_str(payload.get("knowledge"), "payload.knowledge") + elif delta_type == "relationship_changed": + if set(payload) != {"fromName", "toName", "relation"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "关系增量字段非法") + _require_str(payload.get("fromName"), "payload.fromName") + _require_str(payload.get("toName"), "payload.toName") + _require_str(payload.get("relation"), "payload.relation") + elif delta_type == "hook_advanced": + if set(payload) - {"hookId", "action", "note", "dueWindow"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "伏笔增量含未知字段") + _require_str(payload.get("hookId"), "payload.hookId") + if payload.get("action") not in HOOK_ACTIONS: + raise FactDeltaError( + "FACT_DELTA_PAYLOAD_INVALID", + f"payload.action 必须属于 {sorted(HOOK_ACTIONS)}") + if payload.get("note") is not None: + _require_str(payload["note"], "payload.note") + due = payload.get("dueWindow") + if due is not None: + if (not isinstance(due, Mapping) or set(due) != {"fromChapter", "toChapter"} + or isinstance(due.get("fromChapter"), bool) + or not isinstance(due.get("fromChapter"), int) + or isinstance(due.get("toChapter"), bool) + or not isinstance(due.get("toChapter"), int) + or due["fromChapter"] < 1 or due["toChapter"] < due["fromChapter"]): + raise FactDeltaError( + "FACT_DELTA_PAYLOAD_INVALID", "payload.dueWindow 必须是合法章区间") + payload["dueWindow"] = dict(due) + elif delta_type == "timeline_event_added": + if set(payload) != {"event"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "时间线增量字段非法") + _require_str(payload.get("event"), "payload.event") + else: # setting_added(DELTA_TYPES 闭集已在外层校验) + if set(payload) != {"factType", "text"}: + raise FactDeltaError("FACT_DELTA_PAYLOAD_INVALID", "设定增量字段非法") + _require_str(payload.get("factType"), "payload.factType") + _require_str(payload.get("text"), "payload.text") + return payload + + +def _derive_subject_key(delta_type: str, payload: Mapping[str, Any], target_chapter: int) -> str: + """派生观察索引键:人物类=人名,关系=双向对,伏笔=hookId,其余按章定位。""" + + if delta_type in ("character_location_changed", "character_knowledge_added"): + return str(payload["characterName"]) + if delta_type == "relationship_changed": + return f"{payload['fromName']}→{payload['toName']}" + if delta_type == "hook_advanced": + return str(payload["hookId"]) + if delta_type == "setting_added": + return str(payload["factType"]) + return f"event@ch{target_chapter}" + + +def validate_delta_proposal( + proposal: Any, *, candidate_body: str, target_chapter: int +) -> dict[str, Any]: + """校验单条增量提案:字段闭集、类型闭集、payload 合同、证据引文真实存在。""" + + if not isinstance(proposal, Mapping): + raise FactDeltaError("FACT_DELTA_INVALID", "增量提案必须是对象") + if set(proposal) != _PROPOSAL_FIELDS: + missing = sorted(_PROPOSAL_FIELDS - set(proposal)) + extra = sorted(set(proposal) - _PROPOSAL_FIELDS) + raise FactDeltaError( + "FACT_DELTA_INVALID", f"增量提案字段非法 missing={missing} extra={extra}") + delta_id = _require_str(proposal.get("deltaId"), "deltaId") + if len(delta_id) > 64: + raise FactDeltaError("FACT_DELTA_INVALID", "deltaId 超长") + delta_type = proposal.get("deltaType") + if delta_type not in DELTA_TYPES: + raise FactDeltaError("FACT_DELTA_TYPE_INVALID", f"deltaType 非法: {delta_type!r}") + if not isinstance(target_chapter, int) or isinstance(target_chapter, bool) or target_chapter < 1: + raise FactDeltaError("FACT_DELTA_INVALID", "target_chapter 非法") + payload = _validate_payload(delta_type, proposal.get("payload")) + quote = proposal.get("evidenceQuote") + if not isinstance(quote, str) or not quote.strip(): + raise FactDeltaError("FACT_DELTA_EVIDENCE_REQUIRED", "增量提案必须携带正文证据引文") + body = normalize_text(candidate_body) + normalized_quote = normalize_text(quote) + if normalized_quote not in body: + # 与语义 detector 同规则:0 次命中 = 编造证据,失败关闭 + raise FactDeltaError( + "FACT_DELTA_QUOTE_NOT_FOUND", "证据引文未在候选正文中出现(不得编造证据)") + return { + "deltaId": delta_id, + "deltaType": delta_type, + "payload": payload, + "evidenceQuote": normalized_quote, + "subjectKey": _derive_subject_key(delta_type, payload, target_chapter), + } + + +def validate_delta_batch( + proposals: Sequence[Any], *, candidate_body: str, target_chapter: int +) -> list[dict[str, Any]]: + """批量校验并拒绝重复 deltaId。""" + + if not isinstance(proposals, Sequence) or isinstance(proposals, (str, bytes)): + raise FactDeltaError("FACT_DELTA_INVALID", "增量提案必须是数组") + normalized = [ + validate_delta_proposal(item, candidate_body=candidate_body, + target_chapter=target_chapter) + for item in proposals + ] + ids = [item["deltaId"] for item in normalized] + if len(ids) != len(set(ids)): + raise FactDeltaError("FACT_DELTA_DUPLICATE_ID", "同批 deltaId 不得重复") + return normalized + + +def apply_accepted_deltas( + conn, + *, + work_id: int, + target_chapter: int, + run_id: str | None, + candidate_sha256_bare: str, + candidate_body: str, + deltas: Sequence[Mapping[str, Any]], + block_revision: int, + command_id: str | None, + decided_by: str, + rationale: str | None = None, +) -> list[int]: + """把**已批准**的增量随正文同一事务落提案表(accepted)与正典账本。 + + 使用调用方的连接与事务,不自行 commit:任一增量非法则抛错,由外层整体回滚, + 绝不会出现"正文已提交但事实半合并"。 + + 两条入口都合到这里:抽取侧已用 propose_fact_deltas 登记过同 deltaId 的 proposed 行时, + 走条件 UPDATE 翻态(propose→approve 正道,不撞唯一键);未登记过的直接 INSERT 为 accepted。 + 已被裁决过的行(accepted/rejected/superseded)不得再次接受,失败关闭给稳定错误码。 + """ + + normalized = validate_delta_batch( + deltas, candidate_body=candidate_body, target_chapter=target_chapter) + delta_ids: list[int] = [] + for item in normalized: + payload_json = json.dumps(item["payload"], ensure_ascii=False) + existing = conn.execute( + "SELECT id, status FROM example_fact_delta WHERE tenant_id=0 AND candidate_sha256=%s " + "AND delta_id=%s AND deleted=false", + (candidate_sha256_bare, item["deltaId"]), + ).fetchone() + if existing is not None: + if existing[1] != "proposed": + raise FactDeltaError( + "FACT_DELTA_ALREADY_DECIDED", + f"增量 {item['deltaId']} 已是 {existing[1]},不得再次接受") + delta_row_id = conn.execute( + "UPDATE example_fact_delta SET status='accepted', decided_by=%s, " + "decision_rationale=%s, source_revision=%s, updater=%s " + "WHERE id=%s AND status='proposed' RETURNING id", + (decided_by, rationale, block_revision, CREATOR, existing[0]), + ).fetchone() + if delta_row_id is None: + raise FactDeltaError( + "FACT_DELTA_ALREADY_DECIDED", + f"增量 {item['deltaId']} 翻态竞争失败(状态已被并发裁决)") + delta_row_id = delta_row_id[0] + else: + delta_row_id = conn.execute( + "INSERT INTO example_fact_delta(work_id, target_chapter, run_id, candidate_sha256, " + "delta_id, delta_type, subject_key, payload, evidence_quote, status, decided_by, " + "decision_rationale, source_revision, creator) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s,'accepted',%s,%s,%s,%s) RETURNING id", + (work_id, target_chapter, run_id, candidate_sha256_bare, item["deltaId"], + item["deltaType"], item["subjectKey"], payload_json, + item["evidenceQuote"], decided_by, rationale, block_revision, CREATOR), + ).fetchone()[0] + conn.execute( + "INSERT INTO example_fact_ledger(work_id, target_chapter, delta_ref, delta_type, " + "subject_key, payload, source_candidate_sha256, source_block_revision, command_id, creator) " + "VALUES (%s,%s,%s,%s,%s,%s::jsonb,%s,%s,%s,%s)", + (work_id, target_chapter, delta_row_id, item["deltaType"], item["subjectKey"], + payload_json, candidate_sha256_bare, + block_revision, command_id, CREATOR), + ) + delta_ids.append(delta_row_id) + return delta_ids + + +def propose_fact_deltas( + *, + work_id: int, + target_chapter: int, + run_id: str | None, + candidate_sha256_bare: str, + candidate_body: str, + deltas: Sequence[Mapping[str, Any]], + creator: str = CREATOR, + dry_run: bool = False, +) -> dict[str, Any]: + """抽取侧登记增量**提案**(status=proposed);升格必须另行显式批准。""" + + from db import connect # 延迟导入:纯校验路径(测试)不需要库 + + normalized = validate_delta_batch( + deltas, candidate_body=candidate_body, target_chapter=target_chapter) + inserted: list[int] = [] + with connect() as conn: + try: + for item in normalized: + row = conn.execute( + "INSERT INTO example_fact_delta(work_id, target_chapter, run_id, " + "candidate_sha256, delta_id, delta_type, subject_key, payload, evidence_quote, " + "status, creator) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s,'proposed',%s) " + "ON CONFLICT (tenant_id, candidate_sha256, delta_id) DO NOTHING RETURNING id", + (work_id, target_chapter, run_id, candidate_sha256_bare, item["deltaId"], + item["deltaType"], item["subjectKey"], + json.dumps(item["payload"], ensure_ascii=False), + item["evidenceQuote"], creator), + ).fetchone() + if row is None: + raise FactDeltaError( + "FACT_DELTA_DUPLICATE_ID", + f"该候选已登记过 deltaId={item['deltaId']}(幂等拒绝,不得覆盖)") + inserted.append(row[0]) + if dry_run: + conn.rollback() + return {"status": "dry_run_ok", "proposed_ids": inserted, + "note": "试跑已回滚,未落库"} + conn.commit() + except Exception: + conn.rollback() + raise + return {"status": "proposed", "proposed_ids": inserted} + + +__all__ = [ + "DELTA_TYPES", "HOOK_ACTIONS", "FactDeltaError", + "validate_delta_proposal", "validate_delta_batch", + "apply_accepted_deltas", "propose_fact_deltas", +] + + +if __name__ == "__main__": + import argparse + ap = argparse.ArgumentParser(description="事实增量提案登记(抽取侧入口)") + ap.add_argument("payload_json", nargs="?", default="-", + help="提案 JSON 文件路径(默认 stdin),含 workId/targetChapter/runId/" + "candidateSha256/candidateBody/deltas") + ap.add_argument("--dry-run", action="store_true") + args = ap.parse_args() + raw = sys.stdin.read() if args.payload_json == "-" else pathlib.Path( + args.payload_json).read_text(encoding="utf-8") + spec = json.loads(raw) + try: + out = propose_fact_deltas( + work_id=int(spec["workId"]), target_chapter=int(spec["targetChapter"]), + run_id=spec.get("runId"), + candidate_sha256_bare=str(spec["candidateSha256"]).removeprefix("sha256:"), + candidate_body=str(spec["candidateBody"]), deltas=list(spec["deltas"]), + dry_run=args.dry_run) + print(json.dumps(out, ensure_ascii=False)) + except FactDeltaError as exc: + print(f"[拒绝] {exc.code}: {exc}", file=sys.stderr) + sys.exit(1) diff --git a/.claude/skills/decide-candidate/scripts/projection_registry.py b/.claude/skills/decide-candidate/scripts/projection_registry.py new file mode 100644 index 0000000..a62fe5b --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/projection_registry.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Canonical 投影的登记、失效与恢复(08 数据权威)。 + +摘要、handoff、embedding、章后抽取、看板缓存都是正文的幂等投影:派生数据坏了可重建, +绝不能反向成为事实源。本模块把每个投影绑定到 source_revision + source_text_hash: + +- register_pending_projections:正文提交同事务登记 pending 投影(幂等键防重放重复登记); +- mark_stale_before_revision:正文换新 revision 时,同事务把旧 revision 的投影翻 stale; +- finish_projection / retry_projection:投影 worker 报告完成/失败与显式重试(attempt+1); +- refresh_staleness:恢复巡检——按当前正文 revision/哈希对账,漂移的投影标 stale。 + +投影失败记 failed 并留原因,绝不显示 completed;stale/failed 不得直接洗白成 completed(DB 触发器兜底)。 +""" + +from __future__ import annotations + +import hashlib +import pathlib +import sys +from typing import Any, Iterable + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +DB_DIR = SCRIPT_DIR.parents[1] / "access-database" / "scripts" +if str(DB_DIR) not in sys.path: + sys.path.insert(0, str(DB_DIR)) + +from db import connect # noqa: E402 + +PROJECTION_KINDS = frozenset({"summary", "handoff", "embedding", "extraction", "dashboard"}) +CREATOR = "confirm" + + +class ProjectionError(RuntimeError): + """投影登记/状态流转非法——失败关闭。""" + + +def _bare_sha(value: str) -> str: + value = str(value or "") + return value[len("sha256:"):] if value.startswith("sha256:") else value + + +def idempotency_key(work_id: int, target_chapter: int | None, kind: str, source_revision: int) -> str: + """同一提交重放不重复登记的幂等键:work:chapter:kind:revN。""" + + chapter_part = f"ch{target_chapter}" if target_chapter is not None else "book" + return f"{work_id}:{chapter_part}:{kind}:rev{source_revision}" + + +def register_pending_projections( + conn, + *, + work_id: int, + target_chapter: int | None, + kinds: Iterable[str], + source_revision: int, + source_text_hash_bare: str, + candidate_sha256_bare: str | None = None, + creator: str = CREATOR, +) -> list[int]: + """在调用方事务内登记 pending 投影;重放同一幂等键回读已有行,不重复登记。""" + + kinds = list(dict.fromkeys(kinds)) # 去重保序 + unknown = sorted(set(kinds) - PROJECTION_KINDS) + if unknown: + raise ProjectionError(f"投影类型非法: {unknown}") + if not kinds: + return [] + ids: list[int] = [] + for kind in kinds: + key = idempotency_key(work_id, target_chapter, kind, source_revision) + row = conn.execute( + "INSERT INTO example_projection_run(work_id, target_chapter, kind, source_revision, " + "source_text_hash, candidate_sha256, status, attempt, idempotency_key, creator) " + "VALUES (%s,%s,%s,%s,%s,%s,'pending',1,%s,%s) " + "ON CONFLICT (tenant_id, idempotency_key) DO NOTHING RETURNING id", + (work_id, target_chapter, kind, source_revision, source_text_hash_bare, + candidate_sha256_bare, key, creator), + ).fetchone() + if row is None: + row = conn.execute( + "SELECT id FROM example_projection_run WHERE tenant_id=0 AND idempotency_key=%s", + (key,), + ).fetchone() + if row is None: + raise ProjectionError(f"投影幂等回读失败: {key}") + ids.append(row[0]) + return ids + + +def mark_stale_before_revision( + conn, *, work_id: int, target_chapter: int | None, new_revision: int, creator: str = CREATOR +) -> int: + """同事务失效:新 revision 提交后,旧 revision 的活动投影一律 stale。""" + + cur = conn.execute( + "UPDATE example_projection_run SET status='stale', updater=%s " + "WHERE tenant_id=0 AND work_id=%s AND target_chapter IS NOT DISTINCT FROM %s " + "AND source_revision < %s AND status IN ('pending','completed','failed') AND deleted=false", + (creator, work_id, target_chapter, new_revision), + ) + return cur.rowcount + + +def finish_projection(projection_id: int, status: str, *, detail: Any = None, + creator: str = "projection") -> dict[str, Any]: + """投影 worker 报告 completed/failed;仅 pending 可报告,竞争或状态不对即失败关闭。""" + + if status not in ("completed", "failed"): + raise ProjectionError("finish_projection 只接受 completed/failed") + import json + detail_json = json.dumps(detail, ensure_ascii=False) if detail is not None else None + with connect() as conn: + try: + cur = conn.execute( + "UPDATE example_projection_run SET status=%s, detail=%s::jsonb, updater=%s " + "WHERE id=%s AND status='pending' AND deleted=false", + (status, detail_json, creator, projection_id), + ) + if cur.rowcount != 1: + raise ProjectionError(f"投影 {projection_id} 不在 pending 状态,拒绝报告 {status}") + conn.commit() + except Exception: + conn.rollback() + raise + return {"status": status, "projection_id": projection_id} + + +def retry_projection(projection_id: int, *, creator: str = "projection") -> dict[str, Any]: + """显式重试 failed/stale 投影:回 pending 且 attempt+1;completed 不得重试。""" + + with connect() as conn: + try: + row = conn.execute( + "UPDATE example_projection_run SET status='pending', attempt=attempt+1, " + "detail=NULL, updater=%s WHERE id=%s AND status IN ('failed','stale') " + "AND deleted=false RETURNING attempt", + (creator, projection_id), + ).fetchone() + if row is None: + raise ProjectionError(f"投影 {projection_id} 不在 failed/stale 状态,拒绝重试") + conn.commit() + except Exception: + conn.rollback() + raise + return {"status": "pending", "projection_id": projection_id, "attempt": row[0]} + + +def refresh_staleness(*, work_id: int, creator: str = "projection") -> list[int]: + """恢复巡检:按当前正文块 revision/哈希对账,把漂移的投影标 stale,返回受影响 id。""" + + stale_ids: list[int] = [] + with connect() as conn: + try: + current = {} + rows = conn.execute( + "SELECT c.order_no, b.revision, b.content_text FROM muse_content_block b " + "JOIN muse_content_chapter c ON b.chapter_id=c.id AND c.deleted=false " + "WHERE c.work_id=%s AND b.deleted=false", + (work_id,), + ).fetchall() + for order_no, revision, text in rows: + if order_no not in current or revision > current[order_no][0]: + current[order_no] = (revision, hashlib.sha256( + (text or "").encode("utf-8")).hexdigest()) + projections = conn.execute( + "SELECT id, target_chapter, source_revision, source_text_hash " + "FROM example_projection_run WHERE tenant_id=0 AND work_id=%s " + "AND status IN ('pending','completed','failed') AND deleted=false", + (work_id,), + ).fetchall() + for proj_id, chapter, source_revision, source_hash in projections: + latest = current.get(chapter) + drifted = ( + latest is None + or source_revision < latest[0] + or source_hash != latest[1] + ) + if drifted: + conn.execute( + "UPDATE example_projection_run SET status='stale', updater=%s WHERE id=%s", + (creator, proj_id), + ) + stale_ids.append(proj_id) + conn.commit() + except Exception: + conn.rollback() + raise + return stale_ids + + +__all__ = [ + "PROJECTION_KINDS", "ProjectionError", "idempotency_key", + "register_pending_projections", "mark_stale_before_revision", + "finish_projection", "retry_projection", "refresh_staleness", +] diff --git a/.claude/skills/confirm/scripts/reconcile_work_metrics.py b/.claude/skills/decide-candidate/scripts/reconcile_work_metrics.py similarity index 96% rename from .claude/skills/confirm/scripts/reconcile_work_metrics.py rename to .claude/skills/decide-candidate/scripts/reconcile_work_metrics.py index 1c651f3..6f2095d 100644 --- a/.claude/skills/confirm/scripts/reconcile_work_metrics.py +++ b/.claude/skills/decide-candidate/scripts/reconcile_work_metrics.py @@ -10,7 +10,7 @@ import pathlib import sys -DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/confirm/scripts/test_confirm_knowledge_offline.py b/.claude/skills/decide-candidate/scripts/test_confirm_knowledge_offline.py similarity index 100% rename from .claude/skills/confirm/scripts/test_confirm_knowledge_offline.py rename to .claude/skills/decide-candidate/scripts/test_confirm_knowledge_offline.py diff --git a/.claude/skills/decide-candidate/scripts/test_fact_delta.py b/.claude/skills/decide-candidate/scripts/test_fact_delta.py new file mode 100644 index 0000000..849abb2 --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/test_fact_delta.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""fact_delta reducer 的离线测试。 + +模型只能提类型化增量,证据引文必须真实出现在候选正文中(不得编造证据); +字段闭集、类型闭集、payload 合同、重复 ID 一律失败关闭。 + +跑法:.venv/bin/python .claude/skills/decide-candidate/scripts/test_fact_delta.py +""" + +from __future__ import annotations + +import copy +import pathlib +import sys +import unittest + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "assemble-context" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from fact_delta import ( # noqa: E402 + DELTA_TYPES, FactDeltaError, validate_delta_batch, validate_delta_proposal, +) + +BODY = "林深把黑纹缠上手臂,异种核心在胸腔里低鸣。何岚站在舱门口没有说话。" + + +def _proposal(delta_type="character_location_changed", *, delta_id="delta-1", + payload=None, quote="异种核心在胸腔里低鸣"): + if payload is None: + payload = {"characterName": "林深", "toLocation": "舱室底层"} + return {"deltaId": delta_id, "deltaType": delta_type, + "payload": payload, "evidenceQuote": quote} + + +class ValidateDeltaProposalTest(unittest.TestCase): + def test_each_delta_type_has_working_contract(self) -> None: + cases = { + "character_location_changed": + ({"characterName": "林深", "toLocation": "舱室底层"}, "异种核心在胸腔里低鸣"), + "character_knowledge_added": + ({"characterName": "何岚", "knowledge": "林深体内有异种核心"}, "何岚站在舱门口没有说话"), + "relationship_changed": + ({"fromName": "林深", "toName": "何岚", "relation": "互相提防"}, "何岚站在舱门口没有说话"), + "hook_advanced": + ({"hookId": "hook-voice", "action": "advanced", + "dueWindow": {"fromChapter": 5, "toChapter": 8}}, "异种核心在胸腔里低鸣"), + "timeline_event_added": + ({"event": "隔离舱首次审讯结束"}, "何岚站在舱门口没有说话"), + "setting_added": + ({"factType": "污染规则", "text": "黑纹扩散不可逆"}, "林深把黑纹缠上手臂"), + } + self.assertEqual(set(cases), DELTA_TYPES) + for delta_type, (payload, quote) in cases.items(): + with self.subTest(delta_type=delta_type): + got = validate_delta_proposal( + _proposal(delta_type, payload=payload, quote=quote), + candidate_body=BODY, target_chapter=2) + self.assertEqual(got["deltaType"], delta_type) + self.assertTrue(got["subjectKey"]) + + def test_subject_key_derivation(self) -> None: + got = validate_delta_proposal( + _proposal("relationship_changed", + payload={"fromName": "林深", "toName": "何岚", "relation": "同盟"}, + quote="何岚站在舱门口没有说话"), + candidate_body=BODY, target_chapter=2) + self.assertEqual(got["subjectKey"], "林深→何岚") + hook = validate_delta_proposal( + _proposal("hook_advanced", payload={"hookId": "hook-voice", "action": "planted"}, + quote="异种核心在胸腔里低鸣"), + candidate_body=BODY, target_chapter=2) + self.assertEqual(hook["subjectKey"], "hook-voice") + + def test_quote_not_found_fails_closed(self) -> None: + with self.assertRaises(FactDeltaError) as caught: + validate_delta_proposal(_proposal(quote="正文里不存在的句子"), + candidate_body=BODY, target_chapter=2) + self.assertEqual(caught.exception.code, "FACT_DELTA_QUOTE_NOT_FOUND") + + def test_closed_field_set_and_type_enum(self) -> None: + extra = _proposal() + extra["extraField"] = 1 + with self.assertRaises(FactDeltaError) as caught: + validate_delta_proposal(extra, candidate_body=BODY, target_chapter=2) + self.assertEqual(caught.exception.code, "FACT_DELTA_INVALID") + bad_type = _proposal(delta_type="character_resurrected") + with self.assertRaises(FactDeltaError) as caught: + validate_delta_proposal(bad_type, candidate_body=BODY, target_chapter=2) + self.assertEqual(caught.exception.code, "FACT_DELTA_TYPE_INVALID") + + def test_payload_contract_enforced_per_type(self) -> None: + missing_field = _proposal(payload={"characterName": "林深"}) # 缺 toLocation + with self.assertRaises(FactDeltaError): + validate_delta_proposal(missing_field, candidate_body=BODY, target_chapter=2) + bad_hook_action = _proposal( + "hook_advanced", payload={"hookId": "h1", "action": "detonated"}, + quote="异种核心在胸腔里低鸣") + with self.assertRaises(FactDeltaError): + validate_delta_proposal(bad_hook_action, candidate_body=BODY, target_chapter=2) + bad_due_window = _proposal( + "hook_advanced", + payload={"hookId": "h1", "action": "planted", + "dueWindow": {"fromChapter": 9, "toChapter": 3}}, + quote="异种核心在胸腔里低鸣") + with self.assertRaises(FactDeltaError): + validate_delta_proposal(bad_due_window, candidate_body=BODY, target_chapter=2) + + def test_batch_rejects_duplicate_ids(self) -> None: + batch = [_proposal(delta_id="delta-1"), + _proposal(delta_id="delta-1", payload={"characterName": "何岚", + "toLocation": "指挥舱"})] + with self.assertRaises(FactDeltaError) as caught: + validate_delta_batch(batch, candidate_body=BODY, target_chapter=2) + self.assertEqual(caught.exception.code, "FACT_DELTA_DUPLICATE_ID") + ok = validate_delta_batch( + [_proposal(delta_id="delta-1"), + _proposal(delta_id="delta-2", payload={"characterName": "何岚", + "toLocation": "指挥舱"})], + candidate_body=BODY, target_chapter=2) + self.assertEqual([item["deltaId"] for item in ok], ["delta-1", "delta-2"]) + + def test_quote_is_normalized_before_matching(self) -> None: + # 正文里的字面转义换行(模型 JSON 双重转义)归一为真换行后再匹配,与正文合同一致 + escaped_body = BODY + "\\n舱灯闪了一下" + got = validate_delta_proposal( + _proposal(payload={"characterName": "林深", "toLocation": "舱室底层"}, + quote="\n舱灯闪了一下"), + candidate_body=escaped_body, target_chapter=2) + self.assertEqual(got["deltaType"], "character_location_changed") + self.assertIn("\n舱灯闪了一下", escaped_body.replace("\\n", "\n")) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/decide-candidate/scripts/test_fact_delta_db.py b/.claude/skills/decide-candidate/scripts/test_fact_delta_db.py new file mode 100644 index 0000000..159151a --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/test_fact_delta_db.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +"""事实增量对 muse-example 真实库的集成测试。 + +覆盖: +- 提案登记幂等:同候选重复 deltaId 拒绝(不得覆盖); +- ChapterCommit 原子性:已批准增量与正文同一事务落账本;任一增量非法则整体回滚, + 正文也不写入; +- 账本绑定:source_block_revision + command_id 与正文提交一致; +- 账本 append-only:UPDATE/DELETE 被触发器拒绝。 + +测试数据 unittest-delta- 前缀隔离;清理时短暂禁用账本防删触发器(try/finally 恢复)。 + +跑法(需 Tailscale 内网可达 muse-example): +.venv/bin/python .claude/skills/decide-candidate/scripts/test_fact_delta_db.py +""" + +from __future__ import annotations + +import hashlib +import pathlib +import sys +import uuid + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from db import connect # noqa: E402 +from fact_delta import FactDeltaError, propose_fact_deltas # noqa: E402 +from write_canonical import ConflictError, accept # noqa: E402 + +SUFFIX = uuid.uuid4().hex[:10] +WORK_TITLE = f"unittest-delta-{SUFFIX}" +BODY = f"林深把黑纹缠上手臂,异种核心在胸腔里低鸣。何岚站在舱门口没有说话。{SUFFIX}" +_command_ids: list[str] = [] +_candidate_ids: list[int] = [] +_delta_ids: list[int] = [] +_work_id: int | None = None + + +def _sha(text: str) -> str: + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +def _cleanup() -> None: + if _work_id is None: + return + with connect() as conn: + try: + conn.execute( + "ALTER TABLE example_fact_ledger DISABLE TRIGGER trg_example_fact_ledger_append_only") + conn.execute( + "ALTER TABLE example_user_decision DISABLE TRIGGER trg_example_user_decision_append_only") + try: + if _candidate_ids: + conn.execute("DELETE FROM example_user_decision WHERE candidate_id = ANY(%s)", + (_candidate_ids,)) + if _command_ids: + conn.execute("DELETE FROM muse_content_command_log WHERE command_id = ANY(%s)", + (_command_ids,)) + conn.execute("DELETE FROM example_fact_ledger WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM example_fact_delta WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_block_source_attribution WHERE work_id=%s", + (_work_id,)) + conn.execute("DELETE FROM muse_content_block WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_chapter WHERE work_id=%s", (_work_id,)) + if _candidate_ids: + conn.execute("DELETE FROM example_candidate WHERE id = ANY(%s)", + (_candidate_ids,)) + conn.execute("DELETE FROM muse_content_work WHERE id=%s", (_work_id,)) + conn.commit() + finally: + conn.execute( + "ALTER TABLE example_fact_ledger ENABLE TRIGGER trg_example_fact_ledger_append_only") + conn.execute( + "ALTER TABLE example_user_decision ENABLE TRIGGER trg_example_user_decision_append_only") + conn.commit() + except Exception: + conn.rollback() + raise + + +def _setup() -> int: + global _work_id + with connect() as conn: + work_id = conn.execute( + "INSERT INTO muse_content_work(owner_user_id, title, status, creator) " + "VALUES (1,%s,'writing','unittest') RETURNING id", (WORK_TITLE,)).fetchone()[0] + conn.execute( + "INSERT INTO muse_content_chapter(work_id, title, order_no, creator) " + "VALUES (%s,'测试章',1,'unittest')", (work_id,)) + conn.commit() + _work_id = work_id + return work_id + + +def _insert_candidate(work_id: int, *, version: str) -> int: + with connect() as conn: + candidate_id = conn.execute( + "INSERT INTO example_candidate(work_id, target_chapter, run_id, attempt, run_type, " + "candidate_version, candidate_sha256, candidate_body, quality_policy_version, mode, " + "source_role, state, acceptance_eligible, semantic_status, semantic_report_sha256, creator) " + "VALUES (%s,1,%s,1,'production',%s,%s,%s,'writer-production-v1','continuation','writer'," + "'passed',TRUE,'passed',%s,'unittest') RETURNING id", + (work_id, f"unittest-delta-run-{SUFFIX}-{version}", version, _sha(BODY), BODY, + _sha("sem:" + BODY))).fetchone()[0] + conn.commit() + _candidate_ids.append(candidate_id) + return candidate_id + + +def _command_id(tag: str) -> str: + cid = f"unittest-delta-{tag}-{SUFFIX}" + _command_ids.append(cid) + return cid + + +def _good_delta(delta_id="delta-1"): + return {"deltaId": delta_id, "deltaType": "hook_advanced", + "payload": {"hookId": "hook-voice", "action": "advanced", + "dueWindow": {"fromChapter": 3, "toChapter": 6}}, + "evidenceQuote": "异种核心在胸腔里低鸣"} + + +def test_propose_is_idempotent_reject(work_id: int) -> None: + candidate_sha = _sha(BODY) + out = propose_fact_deltas( + work_id=work_id, target_chapter=1, run_id=None, + candidate_sha256_bare=candidate_sha, candidate_body=BODY, + deltas=[_good_delta("delta-prop-1"), _good_delta("delta-prop-2")]) + assert out["status"] == "proposed" and len(out["proposed_ids"]) == 2 + _delta_ids.extend(out["proposed_ids"]) + # 同候选重复 deltaId:失败关闭,不得覆盖 + try: + propose_fact_deltas( + work_id=work_id, target_chapter=1, run_id=None, + candidate_sha256_bare=candidate_sha, candidate_body=BODY, + deltas=[_good_delta("delta-prop-1")]) + raise AssertionError("重复 deltaId 必须被拒绝") + except AssertionError: + raise + except FactDeltaError as exc: + assert exc.code == "FACT_DELTA_DUPLICATE_ID" + with connect(readonly=True) as conn: + count = int(conn.execute( + "SELECT COUNT(*) FROM example_fact_delta WHERE candidate_sha256=%s AND delta_id='delta-prop-1'", + (candidate_sha,)).fetchone()[0]) + assert count == 1 + + +def test_commit_applies_approved_deltas_atomically(work_id: int) -> None: + candidate_id = _insert_candidate(work_id, version="1") + command_id = _command_id("commit-1") + result = accept(candidate_id, rationale="unittest", expected_revision=0, + command_id=command_id, + approved_deltas=[_good_delta("delta-acc-1"), _good_delta("delta-acc-2")]) + assert result["status"] == "accepted" and len(result["delta_ids"]) == 2 + _delta_ids.extend(result["delta_ids"]) + with connect(readonly=True) as conn: + ledger_rows = conn.execute( + "SELECT delta_type, subject_key, source_block_revision, command_id, " + "source_candidate_sha256 FROM example_fact_ledger WHERE work_id=%s ORDER BY id", + (work_id,)).fetchall() + delta_rows = conn.execute( + "SELECT status, source_revision, decided_by FROM example_fact_delta WHERE id = ANY(%s)", + (result["delta_ids"],)).fetchall() + assert len(ledger_rows) == 2 + for row in ledger_rows: + assert row[0] == "hook_advanced" + assert row[1] == "hook-voice" + assert row[2] == result["revision"], "账本必须绑同事务的正文 revision" + assert row[3] == command_id, "账本必须与正文提交同一幂等键" + assert row[4] == _sha(BODY) + for row in delta_rows: + assert row == ("accepted", result["revision"], "1") + + +def test_invalid_delta_rolls_back_entire_commit(work_id: int) -> None: + candidate_id = _insert_candidate(work_id, version="2") + command_id = _command_id("commit-bad") + forged = {"deltaId": "delta-forged", "deltaType": "setting_added", + "payload": {"factType": "污染规则", "text": "编造的设定"}, + "evidenceQuote": "这句话根本不在正文里"} + try: + # expected_revision=1:上一测试已把正文块写到 rev1,给对的值才能真正走到增量校验 + accept(candidate_id, rationale="unittest", expected_revision=1, + command_id=command_id, approved_deltas=[_good_delta("delta-ok"), forged]) + raise AssertionError("非法增量必须中止整个提交") + except AssertionError: + raise + except FactDeltaError as exc: + assert exc.code == "FACT_DELTA_QUOTE_NOT_FOUND", exc + with connect(readonly=True) as conn: + blocks = int(conn.execute( + "SELECT COUNT(*) FROM muse_content_block WHERE work_id=%s AND deleted=false", + (work_id,)).fetchone()[0]) + ledger = int(conn.execute( + "SELECT COUNT(*) FROM example_fact_ledger WHERE work_id=%s", (work_id,)).fetchone()[0]) + delta_acc = int(conn.execute( + "SELECT COUNT(*) FROM example_fact_delta WHERE work_id=%s AND status='accepted'", + (work_id,)).fetchone()[0]) + state = conn.execute( + "SELECT state FROM example_candidate WHERE id=%s", (candidate_id,)).fetchone()[0] + # 整体回滚:正文块、账本、accepted 提案全部不得出现(上一条测试已落的 2 行账本不变) + assert blocks == 1, "非法增量不得写入正文" + assert ledger == 2, "非法增量不得半合并进账本" + assert delta_acc == 2, "合法增量也不得单独生效(同事务)" + assert state == "passed" + + +def test_propose_then_approve_no_unique_collision(work_id: int) -> None: + """抽取先登记提案、用户批准后随正文接受:翻态正道,不撞唯一键。""" + + candidate_id = _insert_candidate(work_id, version="3") + candidate_sha = _sha(BODY) + out = propose_fact_deltas( + work_id=work_id, target_chapter=1, run_id=None, + candidate_sha256_bare=candidate_sha, candidate_body=BODY, + deltas=[_good_delta("delta-flow-1")]) + assert out["status"] == "proposed" + _delta_ids.extend(out["proposed_ids"]) + command_id = _command_id("commit-flow") + # 前面的测试把正文块写到 rev1(非法增量整体回滚不改变它),给 rev1 才走到增量合并 + result = accept(candidate_id, rationale="unittest", expected_revision=1, + command_id=command_id, approved_deltas=[_good_delta("delta-flow-1")]) + assert result["status"] == "accepted" and len(result["delta_ids"]) == 1 + assert result["revision"] == 2 + with connect(readonly=True) as conn: + row = conn.execute( + "SELECT status, decided_by, source_revision FROM example_fact_delta WHERE id=%s", + (result["delta_ids"][0],)).fetchone() + # 按 delta_ref 精确查(同 sha 的候选在本文件多个测试里复用过,按 sha 过滤会串) + ledger = conn.execute( + "SELECT delta_ref, source_block_revision, command_id FROM example_fact_ledger " + "WHERE delta_ref=%s", (result["delta_ids"][0],)).fetchall() + assert row == ("accepted", "1", result["revision"]), "提案行应翻 accepted 并绑正文 revision" + assert len(ledger) == 1, "该增量在账本中恰有一行" + assert ledger[0][0] == result["delta_ids"][0] + assert ledger[0][1] == result["revision"], "账本绑同事务正文 revision" + assert ledger[0][2] == command_id, "账本绑同一幂等键" + + # 已裁决的提案不得再次接受(稳定错误码,不是裸 DB 异常); + # expected_revision 给当前 rev2,确保穿过 revision 校验真正走到增量阶段 + candidate_id_2 = _insert_candidate(work_id, version="4") + try: + accept(candidate_id_2, rationale="unittest", expected_revision=2, + command_id=_command_id("commit-flow-2"), + approved_deltas=[_good_delta("delta-flow-1")]) + raise AssertionError("已 accepted 的提案不得再次接受") + except AssertionError: + raise + except FactDeltaError as exc: + assert exc.code == "FACT_DELTA_ALREADY_DECIDED" + except Exception as exc: + raise AssertionError(f"必须是稳定错误码,得到 {type(exc).__name__}: {exc}") + + +def test_ledger_append_only(work_id: int) -> None: + try: + with connect() as conn: + conn.execute("UPDATE example_fact_ledger SET subject_key='hacked' WHERE work_id=%s", + (work_id,)) + conn.commit() + raise AssertionError("账本必须 append-only") + except AssertionError: + raise + except Exception: + pass + try: + with connect() as conn: + conn.execute("DELETE FROM example_fact_ledger WHERE work_id=%s", (work_id,)) + conn.commit() + raise AssertionError("账本必须 append-only") + except AssertionError: + raise + except Exception: + pass + + +def main() -> None: + work_id = _setup() + tests = ( + ("提案登记幂等拒绝", test_propose_is_idempotent_reject), + ("ChapterCommit 原子合并增量", test_commit_applies_approved_deltas_atomically), + ("非法增量整体回滚", test_invalid_delta_rolls_back_entire_commit), + ("提案→批准翻态不撞唯一键", test_propose_then_approve_no_unique_collision), + ("账本 append-only", test_ledger_append_only), + ) + try: + for name, test in tests: + test(work_id) + print(f"PASS: {name}") + print("PASS:事实增量真实库集成测试全部通过") + finally: + _cleanup() + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/decide-candidate/scripts/test_projection_db.py b/.claude/skills/decide-candidate/scripts/test_projection_db.py new file mode 100644 index 0000000..f2cb4cc --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/test_projection_db.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +"""投影登记与恢复对 muse-example 真实库的集成测试。 + +覆盖: +- 提交即登记:accept 同事务登记 pending 投影,绑正文 revision 与文本哈希;重放不重复登记; +- 换版即失效:新 revision 提交后旧投影全部 stale; +- 失败不冒充完成:stale/failed 不得直接置 completed(触发器兜底),failed 显式 retry 才回 pending; +- 恢复巡检:refresh_staleness 按当前正文对账,漂移投影标 stale。 + +测试数据 unittest-proj- 前缀隔离,结束物理清理(本表可变,直接 DELETE)。 + +跑法(需 Tailscale 内网可达 muse-example): +.venv/bin/python .claude/skills/decide-candidate/scripts/test_projection_db.py +""" + +from __future__ import annotations + +import hashlib +import pathlib +import sys +import uuid + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from db import connect # noqa: E402 +from projection_registry import ( # noqa: E402 + ProjectionError, finish_projection, refresh_staleness, retry_projection, +) +from write_canonical import accept # noqa: E402 + +SUFFIX = uuid.uuid4().hex[:10] +WORK_TITLE = f"unittest-proj-{SUFFIX}" +_command_ids: list[str] = [] +_candidate_ids: list[int] = [] +_work_id: int | None = None + + +def _sha(text: str) -> str: + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +def _cleanup() -> None: + if _work_id is None: + return + with connect() as conn: + try: + conn.execute( + "ALTER TABLE example_user_decision DISABLE TRIGGER trg_example_user_decision_append_only") + try: + if _candidate_ids: + conn.execute("DELETE FROM example_user_decision WHERE candidate_id = ANY(%s)", + (_candidate_ids,)) + if _command_ids: + conn.execute("DELETE FROM muse_content_command_log WHERE command_id = ANY(%s)", + (_command_ids,)) + conn.execute("DELETE FROM example_projection_run WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_block_source_attribution WHERE work_id=%s", + (_work_id,)) + conn.execute("DELETE FROM muse_content_block WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_chapter WHERE work_id=%s", (_work_id,)) + if _candidate_ids: + conn.execute("DELETE FROM example_candidate WHERE id = ANY(%s)", + (_candidate_ids,)) + conn.execute("DELETE FROM muse_content_work WHERE id=%s", (_work_id,)) + conn.commit() + finally: + conn.execute( + "ALTER TABLE example_user_decision ENABLE TRIGGER trg_example_user_decision_append_only") + conn.commit() + except Exception: + conn.rollback() + raise + + +def _setup() -> int: + global _work_id + with connect() as conn: + work_id = conn.execute( + "INSERT INTO muse_content_work(owner_user_id, title, status, creator) " + "VALUES (1,%s,'writing','unittest') RETURNING id", (WORK_TITLE,)).fetchone()[0] + conn.execute( + "INSERT INTO muse_content_chapter(work_id, title, order_no, creator) " + "VALUES (%s,'测试章',1,'unittest')", (work_id,)) + conn.commit() + _work_id = work_id + return work_id + + +def _insert_candidate(work_id: int, *, body: str, version: str) -> int: + with connect() as conn: + candidate_id = conn.execute( + "INSERT INTO example_candidate(work_id, target_chapter, run_id, attempt, run_type, " + "candidate_version, candidate_sha256, candidate_body, quality_policy_version, mode, " + "source_role, state, acceptance_eligible, semantic_status, semantic_report_sha256, creator) " + "VALUES (%s,1,%s,1,'production',%s,%s,%s,'writer-production-v1','continuation','writer'," + "'passed',TRUE,'passed',%s,'unittest') RETURNING id", + (work_id, f"unittest-proj-run-{SUFFIX}-{version}", version, _sha(body), body, + _sha("sem:" + body))).fetchone()[0] + conn.commit() + _candidate_ids.append(candidate_id) + return candidate_id + + +def _command_id(tag: str) -> str: + cid = f"unittest-proj-{tag}-{SUFFIX}" + _command_ids.append(cid) + return cid + + +def _projections(work_id: int) -> list[tuple]: + with connect(readonly=True) as conn: + return conn.execute( + "SELECT id, kind, source_revision, source_text_hash, status, attempt " + "FROM example_projection_run WHERE work_id=%s ORDER BY id", (work_id,)).fetchall() + + +def test_commit_registers_and_replay_is_noop(work_id: int) -> None: + body = f"投影测试正文第一版 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="1") + command_id = _command_id("accept-1") + result = accept(candidate_id, rationale="unittest", expected_revision=0, + command_id=command_id, projection_kinds=("extraction", "summary")) + assert result["status"] == "accepted" + rows = _projections(work_id) + assert len(rows) == 2 and result["projection_ids"] == [row[0] for row in rows] + for row in rows: + assert row[2] == result["revision"], "投影必须绑本次正文 revision" + assert row[3] == _sha(body), "投影必须绑本次正文哈希" + assert row[4] == "pending" + + replay = accept(candidate_id, rationale="unittest", expected_revision=1, + command_id=command_id, projection_kinds=("extraction", "summary")) + assert replay["status"] == "already_applied" + assert len(_projections(work_id)) == 2, "重放不得重复登记投影" + + +def test_new_revision_stales_old_projections(work_id: int) -> None: + body = f"投影测试正文第二版 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="2") + result = accept(candidate_id, rationale="unittest", expected_revision=1, + command_id=_command_id("accept-2"), projection_kinds=("extraction",)) + assert result["status"] == "accepted" and result["revision"] == 2 + rows = _projections(work_id) + stale_rows = [row for row in rows if row[4] == "stale"] + pending_rows = [row for row in rows if row[4] == "pending"] + # rev1 的 extraction+summary 全部 stale;rev2 的 extraction 是唯一 pending + assert len(stale_rows) == 2 and all(row[2] == 1 for row in stale_rows) + assert len(pending_rows) == 1 and pending_rows[0][2] == 2 + assert pending_rows[0][1] == "extraction" + + +def test_failed_never_masquerades_completed(work_id: int) -> None: + rows = _projections(work_id) + pending = next(row for row in rows if row[4] == "pending") + # worker 报告失败 + finish_projection(pending[0], "failed", detail={"reason": "extractor_unbuilt"}) + # 失败不得直接洗白成 completed(应用层条件 UPDATE 不命中) + try: + finish_projection(pending[0], "completed") + raise AssertionError("failed 投影不得报告 completed") + except AssertionError: + raise + except ProjectionError: + pass + # 绕过应用层直接 UPDATE 也被触发器拒绝 + try: + with connect() as conn: + conn.execute("UPDATE example_projection_run SET status='completed' WHERE id=%s", + (pending[0],)) + conn.commit() + raise AssertionError("触发器必须拒绝 failed→completed") + except AssertionError: + raise + except Exception: + pass + # 显式 retry 才能回 pending,且 attempt+1 + retried = retry_projection(pending[0]) + assert retried["status"] == "pending" and retried["attempt"] == 2 + ok = finish_projection(pending[0], "completed") + assert ok["status"] == "completed" + # completed 不得重试 + try: + retry_projection(pending[0]) + raise AssertionError("completed 投影不得重试") + except AssertionError: + raise + except ProjectionError: + pass + + +def test_stale_projections_cannot_report_outcomes(work_id: int) -> None: + stale_row = next(row for row in _projections(work_id) if row[4] == "stale") + try: + with connect() as conn: + conn.execute("UPDATE example_projection_run SET status='completed' WHERE id=%s", + (stale_row[0],)) + conn.commit() + raise AssertionError("stale→completed 必须被拒绝") + except AssertionError: + raise + except Exception: + pass + try: + with connect() as conn: + conn.execute("UPDATE example_projection_run SET status='failed' WHERE id=%s", + (stale_row[0],)) + conn.commit() + raise AssertionError("stale→failed 必须被拒绝") + except AssertionError: + raise + except Exception: + pass + # stale 走 retry 恢复:attempt+1 回 pending,随后可以正常完成 + retried = retry_projection(stale_row[0]) + assert retried["status"] == "pending" + finish_projection(stale_row[0], "completed") + + +def test_refresh_staleness_detects_hash_drift(work_id: int) -> None: + # 手工造一条"漂移"投影:revision 与当前正文一致但哈希是旧的(模拟派生后正文被改) + with connect(readonly=True) as conn: + current = conn.execute( + "SELECT COALESCE(MAX(b.revision),0) FROM muse_content_block b " + "JOIN muse_content_chapter c ON b.chapter_id=c.id AND c.deleted=false " + "WHERE c.work_id=%s AND c.order_no=1 AND b.deleted=false", (work_id,)).fetchone()[0] + with connect() as conn: + drifted_id = conn.execute( + "INSERT INTO example_projection_run(work_id, target_chapter, kind, source_revision, " + "source_text_hash, status, idempotency_key, creator) " + "VALUES (%s,1,'dashboard',%s,%s,'completed',%s,'unittest') RETURNING id", + (work_id, current, "f" * 64, f"unittest-drift-{SUFFIX}")).fetchone()[0] + conn.commit() + stale_ids = refresh_staleness(work_id=work_id) + assert drifted_id in stale_ids, "哈希漂移的投影必须被标 stale" + with connect(readonly=True) as conn: + status = conn.execute( + "SELECT status FROM example_projection_run WHERE id=%s", (drifted_id,)).fetchone()[0] + assert status == "stale" + + +def main() -> None: + work_id = _setup() + tests = ( + ("提交即登记且重放幂等", test_commit_registers_and_replay_is_noop), + ("换版即失效旧投影", test_new_revision_stales_old_projections), + ("失败不冒充完成与重试", test_failed_never_masquerades_completed), + ("stale 不得报告结果", test_stale_projections_cannot_report_outcomes), + ("恢复巡检发现哈希漂移", test_refresh_staleness_detects_hash_drift), + ) + try: + for name, test in tests: + test(work_id) + print(f"PASS: {name}") + print("PASS:投影登记与恢复真实库集成测试全部通过") + finally: + _cleanup() + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/decide-candidate/scripts/test_write_canonical_db.py b/.claude/skills/decide-candidate/scripts/test_write_canonical_db.py new file mode 100644 index 0000000..7e0291b --- /dev/null +++ b/.claude/skills/decide-candidate/scripts/test_write_canonical_db.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 +"""write_canonical 接受通道对 muse-example 真实库的故障注入测试。 + +覆盖先审后入主链的机械不变量: +- 接受原子性:事务中断不留半提交(正文/归因/决策要么全有要么全无); +- command_id 重放幂等:同一命令第二次执行不重复写正文、决策; +- revision CAS:旧 expected_revision 拒绝,正文不被旧候选覆盖; +- 先审后入兜底:semantic_status 非 passed 的候选一律不得接受; +- 机械隔离:评测/诊断候选 DB 级拒绝。 + +测试数据用 unittest-commit- 前缀隔离;example_user_decision 是 append-only 表, +清理时短暂禁用其防删触发器(try/finally 保证恢复)。 + +跑法(需 Tailscale 内网可达 muse-example): +.venv/bin/python .claude/skills/decide-candidate/scripts/test_write_canonical_db.py +""" + +from __future__ import annotations + +import hashlib +import pathlib +import sys +import uuid +from typing import Any +from unittest import mock + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from db import connect # noqa: E402 +import write_canonical # noqa: E402 +from write_canonical import ConflictError, accept, discard # noqa: E402 + +SUFFIX = uuid.uuid4().hex[:10] +WORK_TITLE = f"unittest-commit-{SUFFIX}" +_command_ids: list[str] = [] +_candidate_ids: list[int] = [] +_work_id: int | None = None + + +def _sha(text: str) -> str: + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +def _cleanup() -> None: + """物理清理测试行;append-only 决策表短暂禁用防删触发器,finally 恢复。""" + + if _work_id is None: + return + with connect() as conn: + try: + conn.execute( + "ALTER TABLE example_user_decision DISABLE TRIGGER trg_example_user_decision_append_only") + try: + if _candidate_ids: + conn.execute( + "DELETE FROM example_user_decision WHERE candidate_id = ANY(%s)", + (_candidate_ids,)) + if _command_ids: + conn.execute( + "DELETE FROM muse_content_command_log WHERE command_id = ANY(%s)", + (_command_ids,)) + conn.execute( + "DELETE FROM muse_content_block_source_attribution WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_block WHERE work_id=%s", (_work_id,)) + conn.execute("DELETE FROM muse_content_chapter WHERE work_id=%s", (_work_id,)) + if _candidate_ids: + conn.execute( + "DELETE FROM example_candidate WHERE id = ANY(%s)", (_candidate_ids,)) + conn.execute("DELETE FROM muse_content_work WHERE id=%s", (_work_id,)) + conn.commit() + finally: + conn.execute( + "ALTER TABLE example_user_decision ENABLE TRIGGER trg_example_user_decision_append_only") + conn.commit() + except Exception: + conn.rollback() + raise + + +def _setup_work_and_chapter() -> int: + global _work_id + with connect() as conn: + work_id = conn.execute( + "INSERT INTO muse_content_work(owner_user_id, title, status, creator) " + "VALUES (1,%s,'writing','unittest') RETURNING id", (WORK_TITLE,)).fetchone()[0] + conn.execute( + "INSERT INTO muse_content_chapter(work_id, title, order_no, creator) " + "VALUES (%s,'测试章',1,'unittest')", (work_id,)) + conn.commit() + _work_id = work_id + return work_id + + +def _insert_candidate(work_id: int, *, body: str, version: str, run_type: str = "production", + state: str = "passed", semantic_status: str | None = "passed", + semantic_sha: str | None = None) -> int: + """直接造一个 Shadow 候选行(测试夹具,不经 persist_writer_execution)。""" + + sha = _sha(body) + if semantic_status == "passed" and semantic_sha is None: + semantic_sha = _sha("semantic-report:" + sha) + with connect() as conn: + candidate_id = conn.execute( + "INSERT INTO example_candidate(work_id, target_chapter, run_id, attempt, run_type, " + "candidate_version, candidate_sha256, candidate_body, quality_policy_version, mode, " + "source_role, state, acceptance_eligible, semantic_status, semantic_report_sha256, creator) " + "VALUES (%s,1,%s,1,%s,%s,%s,%s,'writer-production-v1','continuation','writer',%s," + "TRUE,%s,%s,'unittest') RETURNING id", + (work_id, f"unittest-commit-run-{SUFFIX}-{version}", run_type, version, sha, body, + state, semantic_status, semantic_sha)).fetchone()[0] + conn.commit() + _candidate_ids.append(candidate_id) + return candidate_id + + +def _command_id(tag: str) -> str: + cid = f"unittest-commit-{tag}-{SUFFIX}" + _command_ids.append(cid) + return cid + + +def _block_revision(work_id: int) -> int: + with connect(readonly=True) as conn: + row = conn.execute( + "SELECT COALESCE(MAX(b.revision),0) FROM muse_content_block b " + "JOIN muse_content_chapter c ON b.chapter_id=c.id AND c.deleted=false " + "WHERE c.work_id=%s AND c.order_no=1 AND b.deleted=false", (work_id,)).fetchone() + return int(row[0]) + + +def _candidate_state(candidate_id: int) -> str: + with connect(readonly=True) as conn: + return conn.execute( + "SELECT state FROM example_candidate WHERE id=%s", (candidate_id,)).fetchone()[0] + + +def _decision_count(candidate_id: int) -> int: + with connect(readonly=True) as conn: + return int(conn.execute( + "SELECT COUNT(*) FROM example_user_decision WHERE candidate_id=%s", + (candidate_id,)).fetchone()[0]) + + +def test_accept_atomic_and_replay_idempotent(work_id: int) -> None: + body = f"测试正文第一版 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="1") + command_id = _command_id("accept-1") + result = accept(candidate_id, rationale="unittest", expected_revision=0, command_id=command_id) + assert result["status"] == "accepted", result + assert result["revision"] == 1 + assert _block_revision(work_id) == 1 + assert _candidate_state(candidate_id) == "accepted" + assert _decision_count(candidate_id) == 1 + + # 同一 command_id 重放:不重复写正文、不新增决策 + replay = accept(candidate_id, rationale="unittest", expected_revision=1, command_id=command_id) + assert replay["status"] == "already_applied", replay + assert _block_revision(work_id) == 1 + assert _decision_count(candidate_id) == 1 + with connect(readonly=True) as conn: + block_rows = int(conn.execute( + "SELECT COUNT(*) FROM muse_content_block WHERE work_id=%s AND deleted=false", + (work_id,)).fetchone()[0]) + attribution_rows = int(conn.execute( + "SELECT COUNT(*) FROM muse_content_block_source_attribution WHERE work_id=%s AND deleted=false", + (work_id,)).fetchone()[0]) + assert block_rows == 1 and attribution_rows == 1, (block_rows, attribution_rows) + + +def test_stale_expected_revision_rejected(work_id: int) -> None: + body = f"测试正文第二版 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="2") + # 当前正文块已在 revision=1;拿旧期望 0 接受必须拒绝,且正文不变 + try: + accept(candidate_id, rationale="unittest", expected_revision=0, + command_id=_command_id("accept-stale")) + raise AssertionError("旧 expected_revision 必须被拒绝") + except AssertionError: + raise + except ConflictError as exc: + assert "REVISION_CONFLICT" in str(exc) + assert _block_revision(work_id) == 1, "拒绝后正文不得变化" + assert _candidate_state(candidate_id) == "passed", "拒绝后候选保持 passed 待人工处理" + + # 正确期望 1 → 接受成 revision 2(旧候选不能覆盖新正文的顺序保证) + ok = accept(candidate_id, rationale="unittest", expected_revision=1, + command_id=_command_id("accept-v2")) + assert ok["status"] == "accepted" and ok["revision"] == 2 + assert _block_revision(work_id) == 2 + + +def test_semantic_backstop(work_id: int) -> None: + # 机械 passed 但语义缺失/未过:DB 兜底拒绝 + no_semantic = _insert_candidate(work_id, body=f"无语义证据 {SUFFIX}", version="3", + semantic_status=None, semantic_sha=None) + failed_semantic = _insert_candidate(work_id, body=f"语义未过 {SUFFIX}", version="4", + semantic_status="failed") + for cid in (no_semantic, failed_semantic): + try: + accept(cid, rationale="unittest", expected_revision=_block_revision(work_id), + command_id=_command_id(f"accept-nosem-{cid}")) + raise AssertionError("未过语义审查的候选不得接受") + except AssertionError: + raise + except ConflictError as exc: + assert "语义审查" in str(exc), str(exc) + assert _candidate_state(no_semantic) == "passed" + assert _candidate_state(failed_semantic) == "passed" + # discard 不受语义兜底限制(丢弃是安全操作) + dropped = discard(failed_semantic, rationale="unittest", + command_id=_command_id("discard-failed-semantic")) + assert dropped["status"] == "discarded" + assert _candidate_state(failed_semantic) == "discarded" + + +def test_non_production_rejected(work_id: int) -> None: + eval_candidate = _insert_candidate(work_id, body=f"评测候选 {SUFFIX}", version="5", + run_type="eval") + try: + accept(eval_candidate, rationale="unittest", expected_revision=_block_revision(work_id), + command_id=_command_id("accept-eval")) + raise AssertionError("评测候选不得接受") + except AssertionError: + raise + except ConflictError as exc: + assert "eval" in str(exc) + assert _candidate_state(eval_candidate) == "passed" + + +def test_interrupted_accept_leaves_no_half_commit(work_id: int) -> None: + """事务后半段注入故障:正文/归因/命令/决策全部回滚,候选保持 passed。""" + + body = f"中断测试正文 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="6") + revision_before = _block_revision(work_id) + + def _explode(*_args, **_kwargs): + raise RuntimeError("injected-fault: 决策归档前崩溃") + + command_id = _command_id("accept-interrupted") + with mock.patch.object(write_canonical, "_refresh_work_metrics", side_effect=_explode): + try: + accept(candidate_id, rationale="unittest", expected_revision=revision_before, + command_id=command_id) + raise AssertionError("注入故障必须中止接受") + except AssertionError: + raise + except RuntimeError as exc: + assert "injected-fault" in str(exc) + + # 半提交检查:正文 revision 不变、无归因新增、命令日志未落(整事务回滚)、决策为 0 + assert _block_revision(work_id) == revision_before, "中断不得改变正文" + assert _decision_count(candidate_id) == 0, "中断不得留下决策" + assert _candidate_state(candidate_id) == "passed", "中断后候选必须保持 passed" + with connect(readonly=True) as conn: + logged = conn.execute( + "SELECT COUNT(*) FROM muse_content_command_log WHERE command_id=%s", + (command_id,)).fetchone()[0] + assert logged == 0, "命令日志随事务回滚,重放仍可执行" + + # 中断后用同一 command_id 重试仍可成功(幂等键未被污染) + retried = accept(candidate_id, rationale="unittest", expected_revision=revision_before, + command_id=command_id) + assert retried["status"] == "accepted" + assert _block_revision(work_id) == revision_before + 1 + + +def test_dry_run_writes_nothing(work_id: int) -> None: + body = f"试跑正文 {SUFFIX}" + candidate_id = _insert_candidate(work_id, body=body, version="7") + revision_before = _block_revision(work_id) + result = accept(candidate_id, rationale="unittest", expected_revision=revision_before, + command_id=_command_id("accept-dry"), dry_run=True) + assert result["status"] == "dry_run_ok" + assert _block_revision(work_id) == revision_before + assert _candidate_state(candidate_id) == "passed" + assert _decision_count(candidate_id) == 0 + + +def main() -> None: + work_id = _setup_work_and_chapter() + tests = ( + ("accept 原子性与重放幂等", test_accept_atomic_and_replay_idempotent), + ("旧 expected_revision 拒绝", test_stale_expected_revision_rejected), + ("语义兜底拒绝", test_semantic_backstop), + ("非生产候选拒绝", test_non_production_rejected), + ("中断不留半提交", test_interrupted_accept_leaves_no_half_commit), + ("dry-run 不落库", test_dry_run_writes_nothing), + ) + try: + for name, test in tests: + test(work_id) + print(f"PASS: {name}") + print("PASS:write_canonical 真实库故障注入测试全部通过") + finally: + _cleanup() + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/confirm/scripts/test_writer_acceptance.py b/.claude/skills/decide-candidate/scripts/test_writer_acceptance.py similarity index 99% rename from .claude/skills/confirm/scripts/test_writer_acceptance.py rename to .claude/skills/decide-candidate/scripts/test_writer_acceptance.py index f3b2e8f..8f9b758 100644 --- a/.claude/skills/confirm/scripts/test_writer_acceptance.py +++ b/.claude/skills/decide-candidate/scripts/test_writer_acceptance.py @@ -16,8 +16,8 @@ import unittest SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -CONTINUATION_DIR = SKILLS_DIR / "continuation" / "scripts" -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" +CONTINUATION_DIR = SKILLS_DIR / "write-next-chapter" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" for path in (SCRIPT_DIR, CONTINUATION_DIR, READ_CONTEXT_DIR): if str(path) not in sys.path: sys.path.insert(0, str(path)) diff --git a/.claude/skills/confirm/scripts/write_canonical.py b/.claude/skills/decide-candidate/scripts/write_canonical.py similarity index 72% rename from .claude/skills/confirm/scripts/write_canonical.py rename to .claude/skills/decide-candidate/scripts/write_canonical.py index e95cb49..2333828 100644 --- a/.claude/skills/confirm/scripts/write_canonical.py +++ b/.claude/skills/decide-candidate/scripts/write_canonical.py @@ -3,10 +3,12 @@ 落库设计 §2.9 的单事务序列(任一失败整体回滚,绝不留"无来源指针的正式正文"): - accept preflight(主会话先调 check_writer_acceptance 纯函数做生产模式全检) - -> 读候选 + 硬校验(run_type=production、state=passed,DB 级兜底) + accept preflight(主会话先调 check_writer_acceptance 纯函数做生产模式全检,实时状态由 acceptance_state 重读) + -> 读候选 + 硬校验(run_type=production、state=passed、semantic_status=passed,DB 级兜底) -> 写 muse_content_block(content_text,revision+1,CAS 乐观锁) [拿到 block_id] -> 写 muse_content_block_source_attribution(block_id, revision, lineage_payload=候选身份;来源权威落块,架构-02 §3) + -> 合并已批准事实增量(fact_delta:校验引文与类型闭集,进 example_fact_ledger,绑 block_revision) + -> 登记投影(projection_registry:旧 revision 投影翻 stale,新 revision 登记 pending) -> 写 muse_content_command_log(command_id 幂等审计) -> 写 example_user_decision(canonical_block_id, command_id, 理据) -> UPDATE example_candidate SET state='accepted' @@ -14,14 +16,19 @@ 只由 confirm 入口调用;主会话与智能体不得自行拼接这些写。 --dry-run 试跑:完整走一遍事务再回滚,校验通过但不落库。 """ +import hashlib import json import sys from pathlib import Path -# 复用 db skill 锁死的 DSN(.claude/skills/db/scripts),不另硬编码连接串 -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +# 复用 access-database Skill 锁死的 DSN,不另硬编码连接串 +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 +from fact_delta import FactDeltaError, apply_accepted_deltas # noqa: E402 +from projection_registry import ( # noqa: E402 + mark_stale_before_revision, register_pending_projections, +) CREATOR = "confirm" @@ -51,8 +58,16 @@ def _refresh_work_metrics(conn, work_id, updater): def accept(candidate_id, decided_by="1", rationale=None, basis_ref=None, expected_revision=None, command_id=None, source_type="ai_candidate", - dry_run=False): - """接受候选为正式正文。单事务;返回 {status, block_id, revision, decision_id, ...}。""" + approved_deltas=None, projection_kinds=None, dry_run=False): + """接受候选为正式正文。单事务;返回 {status, block_id, revision, decision_id, ...}。 + + approved_deltas 非空时,这些**已被用户批准**的结构化事实增量随正文在同一事务进正典账本 + (ChapterCommit);任一增量非法则整体回滚,绝不产生"正文已提交但事实半合并"。 + 抽取出的提案默认不在此列——升格必须先经显式批准(先审后入)。 + + projection_kinds 非空时,同事务把旧 revision 的投影标 stale 并按新 revision 登记 pending + 投影(摘要/抽取/embedding 等派生物只能是正文的幂等投影,坏了可重建)。 + """ with connect() as conn: try: # 0) command_id 幂等:重放同一命令直接返回,不重复写 @@ -63,15 +78,22 @@ def accept(candidate_id, decided_by="1", rationale=None, basis_ref=None, # 1) 读候选 + DB 级硬校验(preflight 的兜底,不靠调用方自觉) row = conn.execute( "SELECT id, work_id, target_chapter, run_id, attempt, run_type, candidate_version, " - "candidate_sha256, candidate_body, mode, source_role, state FROM example_candidate " + "candidate_sha256, candidate_body, mode, source_role, state, " + "semantic_status, semantic_report_sha256 FROM example_candidate " "WHERE id=%s AND deleted=false", (candidate_id,)).fetchone() if not row: raise ConflictError(f"候选 {candidate_id} 不存在") - cid, work_id, chap, run_id, attempt, run_type, cver, csha, body, mode, source_role, state = row + (cid, work_id, chap, run_id, attempt, run_type, cver, csha, body, mode, source_role, + state, semantic_status, semantic_sha) = row if run_type != "production": raise ConflictError(f"run_type={run_type} 为评测/诊断候选,不得接受(05 §8.4)") if state != "passed": raise ConflictError(f"候选 state={state},只有 passed 可接受") + # 先审后入兜底(05 §5):机械门通过(state=passed)之外,还必须有过审的语义审查。 + # 语义状态由 persist_writer_execution 校验真实报告后写入;编排层跳过 detector 时这里失败关闭。 + if semantic_status != "passed" or not semantic_sha: + raise ConflictError( + f"候选 semantic_status={semantic_status},未通过语义审查的候选不得接受(先审后入)") if not body: raise ConflictError("候选正文为空,不能接受") # 2) 定位章 → 现有正文块(一章一块);revision CAS 乐观锁 @@ -116,6 +138,25 @@ def accept(candidate_id, decided_by="1", rationale=None, basis_ref=None, "VALUES (%s,%s,%s,%s,%s,%s,%s::jsonb,%s)", (work_id, block_id, new_rev, source_type, str(cid), int(attempt or 1), json.dumps(lineage, ensure_ascii=False), CREATOR)) + # 3.5) 事实增量:已批准增量随正文同一事务进账本(ChapterCommit 原子性的一部分) + delta_ids = [] + if approved_deltas: + delta_ids = apply_accepted_deltas( + conn, work_id=work_id, target_chapter=chap, run_id=run_id, + candidate_sha256_bare=csha, candidate_body=body, + deltas=list(approved_deltas), block_revision=new_rev, + command_id=command_id, decided_by=decided_by, rationale=rationale) + # 3.6) 投影:旧 revision 的派生物同事务标 stale,新 revision 登记 pending(可重建) + projection_ids = [] + if projection_kinds: + mark_stale_before_revision( + conn, work_id=work_id, target_chapter=chap, new_revision=new_rev, + creator=CREATOR) + projection_ids = register_pending_projections( + conn, work_id=work_id, target_chapter=chap, kinds=list(projection_kinds), + source_revision=new_rev, + source_text_hash_bare=hashlib.sha256(body.encode("utf-8")).hexdigest(), + candidate_sha256_bare=csha, creator=CREATOR) # 4) 命令幂等审计 if command_id: conn.execute( @@ -143,10 +184,12 @@ def accept(candidate_id, decided_by="1", rationale=None, basis_ref=None, conn.rollback() return {"status": "dry_run_ok", "block_id": block_id, "revision": new_rev, "decision_id": dec_id, "word_count": word_count, "metrics": metrics, + "delta_ids": delta_ids, "projection_ids": projection_ids, "note": "试跑已回滚,未落库"} conn.commit() return {"status": "accepted", "block_id": block_id, "revision": new_rev, - "decision_id": dec_id, "word_count": word_count, "metrics": metrics} + "decision_id": dec_id, "word_count": word_count, "metrics": metrics, + "delta_ids": delta_ids, "projection_ids": projection_ids} except Exception: conn.rollback() raise @@ -203,6 +246,10 @@ if __name__ == "__main__": pa.add_argument("--command-id", default=None) pa.add_argument("--source-type", default="ai_candidate", choices=["ai_candidate", "user_merge"]) + pa.add_argument("--approved-deltas", default=None, + help="已批准事实增量的 JSON 文件路径(数组,DeltaProposal 合同)") + pa.add_argument("--projection-kinds", default=None, + help="随接受登记的投影类型,逗号分隔(如 extraction,summary);缺省不登记") pa.add_argument("--dry-run", action="store_true") pd = sub.add_parser("discard", help="丢弃候选") pd.add_argument("candidate_id", type=int) @@ -214,9 +261,20 @@ if __name__ == "__main__": args = ap.parse_args() try: if args.cmd == "accept": + deltas = None + if args.approved_deltas: + deltas = json.loads(Path(args.approved_deltas).read_text(encoding="utf-8")) + if not isinstance(deltas, list): + raise ConflictError("--approved-deltas 必须是 JSON 数组") + projection_kinds = None + if args.projection_kinds: + projection_kinds = [kind.strip() for kind in args.projection_kinds.split(",") + if kind.strip()] result = accept(args.candidate_id, decided_by=args.decided_by, rationale=args.rationale, basis_ref=args.basis_ref, expected_revision=args.expected_revision, - command_id=args.command_id, source_type=args.source_type, dry_run=args.dry_run) + command_id=args.command_id, source_type=args.source_type, + approved_deltas=deltas, projection_kinds=projection_kinds, + dry_run=args.dry_run) else: result = discard(args.candidate_id, decided_by=args.decided_by, rationale=args.rationale, basis_ref=args.basis_ref, command_id=args.command_id, dry_run=args.dry_run) @@ -224,3 +282,6 @@ if __name__ == "__main__": except ConflictError as e: print(f"[拒绝] {e}", file=sys.stderr) sys.exit(1) + except FactDeltaError as e: + print(f"[拒绝] {e.code}: {e}", file=sys.stderr) + sys.exit(1) diff --git a/.claude/skills/parse-book/SKILL.md b/.claude/skills/deconstruct-book/SKILL.md similarity index 79% rename from .claude/skills/parse-book/SKILL.md rename to .claude/skills/deconstruct-book/SKILL.md index ab66149..13bef5c 100644 --- a/.claude/skills/parse-book/SKILL.md +++ b/.claude/skills/deconstruct-book/SKILL.md @@ -1,6 +1,6 @@ --- -name: parse-book -description: 全书解析/拆书的功能合同(scenario: full_parse,分析槽位)。存量作品→规划逆向(大纲/细纲)+实体;参考书再加范式拆取(脱敏)。逐章内环复用 extract-knowledge。作品面升格管线已独立为 upgrade skill。 +name: deconstruct-book +description: 从完整存量作品逆向拆出章节细纲、阶段大纲、实体线索和脱敏写作范式。需要解析用户旧稿或参考书全书时使用;不负责把作品面知识按窗升格,也不确认产物。 disable-model-invocation: true --- @@ -31,7 +31,7 @@ disable-model-invocation: true - **顺序性只来自增量判重**(新实体要对着已积累实体判重合并),细纲逆推本身章间独立——先顺序跑保正确,并行化留作后续优化; - 进度每 10 章报一行(章号/新实体数/累计分型统计)。 -**M3 直调形态(创始人 2026-07-13 拍板,现行)**:循环体不再派 opus/haiku 子代理,改为 `scripts/parse_llm.py` 直调 New-API `MiniMax-M3`(经 llm skill)。 +**M3 直调形态(创始人 2026-07-13 拍板,现行)**:循环体不再派 opus/haiku 子代理,改为 `scripts/parse_llm.py` 直调 New-API `MiniMax-M3`(经 `call-content-model`)。 **出卡权上移窗级(B4-S3 重构,现行)**:章级逐章出卡有三同根病(同功撞车/单章证不成跨章公式/间隔数字伪精确),治法=章级只产「范式候选线索」(并入脚手架 pass,正文只过一遍),出卡在窗级聚类归并——同一手法多章多次出现归并成一张母卡+实例章号,**间隔章数由实例章号差机械计算**(M3 禁自报数字)。跨窗/跨书同手法靠**嵌入判重**(初筛 ≥0.85 → M3 归并终判 merge/keep,拿不准保留)。大纲窗行(example_parse_outline,幂等键=窗起始章)是出卡窗的唯一切分依据。 @@ -39,17 +39,17 @@ disable-model-invocation: true ```bash # ① 章级 pass(细纲+实体+范式候选线索;断点续跑,重跑自动补失败章) -.venv/bin/python .claude/skills/parse-book/scripts/parse_llm.py chapters --work-id 4 --from 1 --to 50 +.venv/bin/python .claude/skills/deconstruct-book/scripts/parse_llm.py chapters --work-id 4 --from 1 --to 50 # ② 窗级大纲聚合(每 5–10 万字:多章细纲+正文→阶段大纲;书末残窗无论大小必成窗) -.venv/bin/python .claude/skills/parse-book/scripts/parse_outline.py window --work-id 4 +.venv/bin/python .claude/skills/deconstruct-book/scripts/parse_outline.py window --work-id 4 # ③ 窗级聚类出卡(窗=②的窗行;线索+细纲+阶段大纲→母卡;守卫+判重在 parse_ingest cards) -.venv/bin/python .claude/skills/parse-book/scripts/parse_llm.py cards --work-id 4 +.venv/bin/python .claude/skills/deconstruct-book/scripts/parse_llm.py cards --work-id 4 # ④ 全书拆完:终检(逐窗细纲对账大纲 + 跨段连贯性纵览) -.venv/bin/python .claude/skills/parse-book/scripts/parse_outline.py check --work-id 4 -# ⑤ 公共卡三角色审核(番茄作家/起点作家/主编,M3 常设步骤;见 review-cards skill) -.venv/bin/python .claude/skills/review-cards/scripts/review_cards.py review --batch <批次> --work-id 4 +.venv/bin/python .claude/skills/deconstruct-book/scripts/parse_outline.py check --work-id 4 +# ⑤ 公共卡三角色审核(番茄作家/起点作家/主编,M3 常设步骤;见 review-knowledge-cards Skill) +.venv/bin/python .claude/skills/review-knowledge-cards/scripts/review_cards.py review --batch <批次> --work-id 4 # 进度 -.venv/bin/python .claude/skills/parse-book/scripts/parse_ingest.py progress +.venv/bin/python .claude/skills/deconstruct-book/scripts/parse_ingest.py progress ``` 审核纪律:常设审核=M3(已用 opus 金标准校准,偏差 0.45 达标);fable/opus 只做起量前校准与起量后一次总审核(门禁与优化,不进流程循环)。 @@ -58,7 +58,7 @@ disable-model-invocation: true **窗行陷阱(放量首日实测)**:`--window` 参数变化后重切,旧窗行会按 from_order 占位,新的大窗被「已有大纲跳过」→ 中间章域永远漏出卡(验收期 1–3 章小窗占住 from_order=1,放量 1–34 章大窗被跳过)。**换窗参数重切前必须先删该书全部窗行**(窗行是可再生中间产物;卡挂「窗起」,cards 重出时按窗软删重出)。 -**作品面升格管线已独立为 upgrade skill**:`upgrade_book` 实体卡按窗抽取(`upgrade.py windows/run/status`)、单卡质量修复、presence 冗余清理、legacy failed 窗恢复、同书 advisory lock 互斥,以及 reset 全量重抽准备与七域 `backup/verify/rehearse/restore` 备份恢复合同,全部见 `.claude/skills/upgrade/SKILL.md`。拆书产出(章级细纲/实体脚手架)是升格的输入;本 skill 不含升格 runbook。 +**作品面知识抽取与维护已拆分**:`extract-work-knowledge` 负责 `windows/run/status` 正常抽取,`maintain-work-extraction` 负责单卡修复、presence 清理、legacy failed 恢复、reset 与七域备份恢复。拆书产出的章级细纲和实体脚手架是正常抽取输入;本 Skill 不含其 runbook。 **只读交叉核对工具(跨拆书与升格两管线)**:`scripts/parse_health.py`(全链终态体检:章级/窗大纲/范式卡/升格四层 + 全局健康红线)与 `scripts/parse_export.py`(终态样张导出:范式五书 + 单书升格实体卡)只读库渲染,不写任何状态,升格层仅做核对与样张。顽固敏感章抢救 `scripts/parse_salvage.py` 是拆书章级的常设工具,留本 skill。 @@ -66,8 +66,8 @@ disable-model-invocation: true ## 步骤(自底向上,与创作期规划的自顶向下互为镜像) -1. 静态分章:import skill(规则,LLM 不参与); -2. **逐章内环——复用 `extract-knowledge` 的字段 checklist**:逆推本章细纲(章目标/关键事件/出场/伏笔动作/钩子)+抽实体增量;上下文=前 N-1 章已积累的细纲与实体(知识库从空增量生长,`作品+元数据+知识库+当前章`公式在此逐章成立); +1. 静态分章:`import-book` Skill(规则,LLM 不参与); +2. **逐章内环——复用 `extract-chapter-knowledge` 的字段 checklist**:逆推本章细纲(章目标/关键事件/出场/伏笔动作/钩子)+抽实体增量;上下文=前 N-1 章已积累的细纲与实体(知识库从空增量生长,`作品+元数据+知识库+当前章`公式在此逐章成立); 3. 自底向上聚合:章细纲→卷粗纲→主线一句话;伏笔跨章连线(哪章埋哪章收)在聚合时补; 4. 2a 补末章 narrative_state;2b 在脚手架上跑范式拆取(判据归型/字段成卡/判重合并例证); 5. 汇报:分章数/细纲覆盖率/实体分型统计/(2b)范式分型统计+设计发现。 @@ -83,4 +83,4 @@ disable-model-invocation: true ## 输出合同 -文件版:2a 落 `works/<书>/`(候选);2b 脚手架落参考书目录、范式卡入 `knowledge/范式/`(草稿)。PG 版(B2):结构化清单经主会话 db skill 写入(draft)+embed 嵌入。均不提交/不自确认。 +文件版:2a 落 `works/<书>/`(候选);2b 脚手架落参考书目录、范式卡入 `knowledge/范式/`(草稿)。PG 版(B2):结构化清单经主会话 `access-database` 写入(draft)+`embed-knowledge` 嵌入。均不提交/不自确认。 diff --git a/.claude/skills/parse-book/scripts/parse_export.py b/.claude/skills/deconstruct-book/scripts/parse_export.py similarity index 98% rename from .claude/skills/parse-book/scripts/parse_export.py rename to .claude/skills/deconstruct-book/scripts/parse_export.py index ed5524b..03231ae 100644 --- a/.claude/skills/parse-book/scripts/parse_export.py +++ b/.claude/skills/deconstruct-book/scripts/parse_export.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""parse-book skill:终态样张导出(零 LLM,纯库读渲染 markdown)。 +"""deconstruct-book Skill:终态样张导出(零 LLM,纯库读渲染 markdown)。 跑序末环「export 样张呈报」的固化实现(批9 收尾落地): - patterns:五书范式卡样张——每书每型抽实例数最多的代表卡(实例多=跨章生长充分, @@ -18,7 +18,7 @@ import sys import click import psycopg -sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) # DSN 的真实来源是同目录的 parse_llm(升格执行器拆分后不再经 upgrade 转导)。 from parse_llm import DSN # noqa: E402 diff --git a/.claude/skills/parse-book/scripts/parse_health.py b/.claude/skills/deconstruct-book/scripts/parse_health.py similarity index 98% rename from .claude/skills/parse-book/scripts/parse_health.py rename to .claude/skills/deconstruct-book/scripts/parse_health.py index 2fb45cd..b8c9ebf 100644 --- a/.claude/skills/parse-book/scripts/parse_health.py +++ b/.claude/skills/deconstruct-book/scripts/parse_health.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""parse-book skill:全链终态体检(零 LLM 机械扫描)。 +"""deconstruct-book Skill:全链终态体检(零 LLM 机械扫描)。 fable5 抽检优化项落地(创始人 2026-07-16):抽检报告中近半发现可机械化—— 固化为体检脚本,每批跑完自动体检,AI 抽检只做机械查不了的判断题。 @@ -13,7 +13,7 @@ import sys import click import psycopg -sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) # DSN/TENANT 的真实来源是同目录的 parse_llm(升格执行器拆分后不再经 upgrade 转导);_is_garbage 是死 import,删。 from parse_llm import DSN, TENANT # noqa: E402 from parse_ingest import IP_LEAK_WORDS # noqa: E402 diff --git a/.claude/skills/parse-book/scripts/parse_ingest.py b/.claude/skills/deconstruct-book/scripts/parse_ingest.py similarity index 99% rename from .claude/skills/parse-book/scripts/parse_ingest.py rename to .claude/skills/deconstruct-book/scripts/parse_ingest.py index 53f0581..23b173f 100644 --- a/.claude/skills/parse-book/scripts/parse_ingest.py +++ b/.claude/skills/deconstruct-book/scripts/parse_ingest.py @@ -20,9 +20,9 @@ import click import psycopg from psycopg.types.json import Jsonb -# 受控点依赖:嵌入走 embed skill 同一实现(同模型同维),归并判定走 llm skill 统一入口 -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed" / "scripts")) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +# 受控点依赖:嵌入走 embed-knowledge,归并判定走 call-content-model +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed-knowledge" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from embed_drafts import _session, build_embed_text, embed_texts # noqa: E402 from llm import chat_governed, extract_json # noqa: E402 # 归并判定走全局额度治理入口 @@ -30,7 +30,7 @@ DSN = ("postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse- "?keepalives=1&keepalives_idle=15&keepalives_interval=5&keepalives_count=3") TENANT, ACTOR = 1, "1" PATTERN_TYPES = {"craft", "combat", "emotion", "scene_pattern", "trope"} # 拍板①:首轮只拆五型 -NGRAM = 15 # 脱敏红线:≥15 连续字与原文重合=违规(parse-book skill) +NGRAM = 15 # 脱敏红线:≥15 连续字与原文重合=违规(deconstruct-book) # 版权 IP 与系统专名词表(复抽实证:机战无限为高达系同人,IP 背景词不在实体名池)。 # 词形边界(opus 终检教训): # - "浮游炮"不入表——已泛化为通用武器品类词(同"光剑"),入表实测误伤 3 卡; @@ -350,7 +350,7 @@ def merge_judge(new_payload, old_payload, model="MiniMax-M3"): f"【卡A(已入库)】\n{json.dumps(old_payload, ensure_ascii=False)}\n\n" f"【卡B(新卡)】\n{json.dumps(new_payload, ensure_ascii=False)}") try: - content, _, used = chat_governed(prompt, model=model) + content, _, used = chat_governed(prompt, model=model, caller="parse-book") # 全局额度链全部拦截(content 为 None):保守判「keep」=不合并——判重失败绝不能误并, # 也不该因此卡住或崩书(错误合并比重复卡更伤,宁重复不误并) if content is None: @@ -484,7 +484,7 @@ def cards(work_id, from_order, file_, model): ON CONFLICT (tenant_id, command_id) WHERE command_id IS NOT NULL DO NOTHING RETURNING id""", (Jsonb(payload), work_id, cid, ACTOR, ACTOR, TENANT)).fetchone() - # 新卡即时嵌入(下一窗/下一书判重立即可见;失败留给 embed skill 批量补) + # 新卡即时嵌入(下一窗/下一书判重立即可见;失败留给 embed-knowledge 批量补) if row and vec is not None: h = hashlib.sha256(f"{embed_text}|{EMBED_MODEL}".encode()).hexdigest() conn.execute( diff --git a/.claude/skills/parse-book/scripts/parse_llm.py b/.claude/skills/deconstruct-book/scripts/parse_llm.py similarity index 98% rename from .claude/skills/parse-book/scripts/parse_llm.py rename to .claude/skills/deconstruct-book/scripts/parse_llm.py index 0be780c..c4158dd 100644 --- a/.claude/skills/parse-book/scripts/parse_llm.py +++ b/.claude/skills/deconstruct-book/scripts/parse_llm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""parse-book skill:M3 直调拆书执行器(B4-S3 重构:出卡权上移窗级)。 +"""deconstruct-book Skill:M3 直调拆书执行器(B4-S3 重构:出卡权上移窗级)。 -创始人拍板(2026-07-13):拆书内容生产 LLM=New-API MiniMax-M3(经 llm skill), +创始人拍板(2026-07-13):拆书内容生产 LLM=New-API MiniMax-M3(经 call-content-model), 不再派 opus/haiku 子代理。B4 fable 审查裁决(2026-07-13):章级逐章出卡有三同根病 (同功 family 撞车/单章证不成跨章公式/间隔数字伪精确),治法=章级只产「范式候选线索」, 出卡在窗级聚类归并(复用大纲窗切分)——同一手法多章多次出现归并为一张母卡+实例章号, @@ -23,9 +23,9 @@ import sys import click import psycopg -# 统一走 llm skill 入口(trust_env/重试/剥离/JSON 容错都在那边) +# 统一走 call-content-model 入口(trust_env/重试/剥离/JSON 容错都在那边) # 敏感/额度降级链已上收 llm.chat_governed(全局统一治理),本模块不再自持降级链 -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from llm import chat_governed, extract_json # noqa: E402 from parse_outline import ensure_outline_coverage # noqa: E402 @@ -176,7 +176,8 @@ def m3_json(prompt, model, need_keys, system=IDENTITY): 时由 chat_governed 沿全局链自动换模型、并按窗口预算/调用数治理;全链耗尽(used 为 None)时本 函数抛 SensitiveHardStop 交上层硬停(parse_upgrade/parse_salvage 靠它跳窗)。JSON 形状问题 非敏感,不换模型(原样抛 RuntimeError,上层按章跳过)。返回 (data, usage) 契约不变。""" - content, usage, used = chat_governed(prompt, model=model, system=system) + content, usage, used = chat_governed(prompt, model=model, system=system, + caller="parse-book") if used is None: # 全局降级链全部耗尽(内容安全/不可用)——保留硬停语义 raise SensitiveHardStop(f"chat_governed 全局降级链全部耗尽(内容安全或模型不可用),model={model}") # 拿到内容:JSON 形状校验,不合格带提示重试 1 次(仍走 chat_governed 全局治理) @@ -191,7 +192,7 @@ def m3_json(prompt, model, need_keys, system=IDENTITY): if retry == 0: content, u2, used2 = chat_governed( prompt + f"\n\n【重试提示】上次输出无法解析({err}),请严格按输出规则只输出一个 JSON 对象。", - model=model, system=system) + model=model, system=system, caller="parse-book") if used2 is None: break # 重试提示也触发全链耗尽:当作该轮失败,落到下方 RuntimeError # 只合并数值键:上游 usage 带嵌套 dict(如 *_tokens_details),dict+dict 会崩(放量实测) diff --git a/.claude/skills/parse-book/scripts/parse_outline.py b/.claude/skills/deconstruct-book/scripts/parse_outline.py similarity index 98% rename from .claude/skills/parse-book/scripts/parse_outline.py rename to .claude/skills/deconstruct-book/scripts/parse_outline.py index a47c33e..8eba505 100644 --- a/.claude/skills/parse-book/scripts/parse_outline.py +++ b/.claude/skills/deconstruct-book/scripts/parse_outline.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""parse-book skill:窗级大纲聚合(创始人 2026-07-13 拍板)。 +"""deconstruct-book Skill:窗级大纲聚合(创始人 2026-07-13 拍板)。 大纲不从单章按比例抽(单章对大纲层可能零贡献),而是: - window:5–10 万字窗(多章细纲+正文)聚合抽取一次窗级大纲 → example_parse_outline; @@ -13,7 +13,7 @@ import sys import click import psycopg -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from llm import chat_governed, extract_json, SensitiveError # noqa: E402 # 敏感/额度降级链已上收 llm.chat_governed(BUDGET_CHAIN 全局统一 + 额度治理);本模块不再自持降级链。 @@ -22,7 +22,7 @@ from llm import chat_governed, extract_json, SensitiveError # noqa: E402 def chat_degrade(prompt, model): """薄封装 chat_governed(全局统一降级链 + 额度治理),保留 (content, usage) 返回契约。 全链耗尽(used 为 None)时抛 SensitiveError,保留原「交上层跳窗」语义(_do_window/check 靠它跳窗)。""" - content, usage, used = chat_governed(prompt, model=model) + content, usage, used = chat_governed(prompt, model=model, caller="parse-outline") if used is None: raise SensitiveError("chat_governed 全局降级链全部耗尽(内容安全或模型不可用)") return content, usage diff --git a/.claude/skills/parse-book/scripts/parse_salvage.py b/.claude/skills/deconstruct-book/scripts/parse_salvage.py similarity index 99% rename from .claude/skills/parse-book/scripts/parse_salvage.py rename to .claude/skills/deconstruct-book/scripts/parse_salvage.py index ec41cd7..7912364 100644 --- a/.claude/skills/parse-book/scripts/parse_salvage.py +++ b/.claude/skills/deconstruct-book/scripts/parse_salvage.py @@ -15,7 +15,7 @@ import click import psycopg sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent)) -sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from parse_llm import (m3_json, scaffold_prompt, ingest, SensitiveHardStop, # noqa: E402 DSN, TENANT) diff --git a/.claude/skills/parse-book/scripts/test_parse_llm_offline.py b/.claude/skills/deconstruct-book/scripts/test_parse_llm_offline.py similarity index 100% rename from .claude/skills/parse-book/scripts/test_parse_llm_offline.py rename to .claude/skills/deconstruct-book/scripts/test_parse_llm_offline.py diff --git a/.claude/skills/parse-book/scripts/test_parse_outline_offline.py b/.claude/skills/deconstruct-book/scripts/test_parse_outline_offline.py similarity index 100% rename from .claude/skills/parse-book/scripts/test_parse_outline_offline.py rename to .claude/skills/deconstruct-book/scripts/test_parse_outline_offline.py diff --git a/.claude/skills/design-story-foundation/SKILL.md b/.claude/skills/design-story-foundation/SKILL.md new file mode 100644 index 0000000..173f433 --- /dev/null +++ b/.claude/skills/design-story-foundation/SKILL.md @@ -0,0 +1,136 @@ +--- +name: design-story-foundation +description: 在正式规划前固化作品根设定,并按前三章、前十章、前五十章的追读节奏生成可比较的前期设计候选。用户仍在单文档前期设计阶段时使用;不写正文、不落库、不替用户定稿。 +--- + +# 作品设定初始化 + +## 唯一目的 + +本 Skill 服务主会话,负责作品正式规划前的候选设计。它把零散对话收束成一份根设定和若干独立候选,让用户先比较故事吸引力、阶段节奏和设定兑现方式,再决定哪一案进入 `plan-story`。 + +本 Skill 不负责完整设定包、卷纲、逐章细纲或正文。用户没有选定方案时,不得抢跑到正式规划,也不得把任何候选写进 PostgreSQL。 + +## 元数据驱动:输入与权威顺序 + +每次执行先冻结一份 `design-story-foundation/v1` 任务包,至少包含: + +1. 作品名、前期设计 SoT 路径和本轮候选数量。 +2. 根设定全文,以及尚未解决的冲突和问题。 +3. 与本书有关的完整用户对话记录及决策状态。 +4. 前三章、前十章、前五十章和全书阶段的节奏要求。 +5. 参考书证据、禁止照搬项、候选目录、编号号段和篇幅门槛。 +6. 每个候选的唯一编号与输出路径;除此之外,各子代理输入完全相同。 + +冲突时按“用户最新明确要求 > 根设定中的已定事实 > 用户已接受方案 > 待定建议”处理。AI 自己说过但用户未接受的方案不能升格为根设定。 + +任务包的字段、状态和示例见 [候选生成合同](references/candidate-contract.md)。冻结后再派发;执行期间收到的新要求先更新任务包,旧候选随即失效,不允许边生成边暗改口径。 + +本阶段产物不属于 23 型正式作品结构,字段权威就是 `design-story-foundation/v1` 候选合同。用户选定方案后,`plan-story` 才按 `meta/schemas/` 把内容转换成正式 Shadow 规划。 + +## 根设定合同 + +前期设计 SoT 的第一章固定为“根设定”。这里只放作品自身的稳定事实和叙事硬约束,包括标题承诺、主角前提、能力边界、成长台阶、披露节奏、正文表达要求和不能触碰的内容。 + +- 不写设计过程、代理分工、提示词、工具、候选比较或方案来源。 +- 每个点只写一句完整话,连同标签控制在 30—60 个可见字符。 +- 根设定内部最多两级结构;能用一组平铺条目说清时不继续拆章。 +- 用户原话含歧义时保留到“待决问题”,不能擅自补成作品事实。 +- 每次用户修正先更新根设定,再判断哪些候选必须作废重做。 + +根设定只回答“这部作品必须是什么样”。候选如何产生、谁来产生、生成几份,属于本 Skill,不得回写根设定。 + +## 先定节奏,再铺设定 + +设定必须从追读节奏反推。先回答各阶段读者为什么翻下一章,再设计能支撑这些事件的能力、资源、人物和世界规则。 + +| 阶段 | 必须完成 | 允许披露 | +|---|---|---| +| 前三章 | 立住处境、近期目标、首个可验证优势和连续钩子 | 只给当前行动所需规则;终局答案与最终敌方不得提前明牌 | +| 前十章 | 完成一次局部闭环,验证能力边界、代价和主要关系 | 展开当前舞台规则,留下能自然抬高舞台的问题 | +| 前五十章 | 完成初期主冲突与阶段高潮,让主角获得下一阶段资格 | 回收早期承诺,引入中期入口;不能把全书核心一次讲尽 | +| 全书阶段 | 逐级扩大个人、组织、战争与文明尺度 | 每次只揭开下一阶段必需的一层真相,并保留后续问题 | + +每项关键设定都要同时写清“作者掌握的总设定”和“读者在各阶段看到什么”。终局真相可以在作者侧完整存在,正文披露点必须服从根设定,不能因为候选写得完整就在前三章泄底。 + +## 参考书证据 + +用户指定参考作品时,先读该书当前库内全部 `example_parse_outline` 窗级大纲,再用前五十章正文校验开局落地。报告必须分开标注: + +- 大纲明确写出的全书阶段线。 +- 前五十章正文能够直接支持的开局结论。 +- 可借鉴的结构方法、节奏方法和信息披露方法。 +- 不得照搬的专名、人物关系、能力、组织和剧情阶梯。 + +未读完窗级大纲时,不得把前五十章印象写成全书核心。参考书只提供方法证据,不替用户决定本书设定。 + +## 冻结候选合同 + +派发前由主会话一次性冻结候选结构,所有候选使用完全相同的二级标题、顺序和编号号段。结构冻结后,子代理不得自行增删章节或改号段。 + +- 每章按内容写 10—50 项设定,不固定写 10 项,也不为凑上限拆碎同一规则。 +- 每份候选不少于 100 项设定;当前任务另有字数要求时同时执行,未明说时不得私自降低已冻结门槛。 +- 当前长篇设定候选的默认有效字符下限为 50000;用户明确修改时以任务包为准。 +- 每项使用唯一 `S` 编号,编号必须落在本章预留号段内,且在全文中递增、不重复。 +- 候选只能用被冻结的两级目录;具体机制、例子和剧情用途写在设定项正文里。 + +同构只用于比较,不要求五份候选得出相同答案。每份方案必须有自己的故事发动机、阶段冲突、能力成本、关系推进和舞台扩张路径。 + +## 独立生成 + +每个候选交给一个独立 Claude Code 规划子代理。主会话将 planner 身份、本 Skill、冻结任务包和指定输出路径内联给它;模型遵守项目的 planner 配置,不静默换型。 + +1. 每个子代理只负责一份候选,只能读取共同输入和自己的工作文件。 +2. 候选之间不共享草稿、提纲、评价和中间结论;文件隔离由工作目录或沙箱保证,不能只靠提示词提醒。 +3. 长文按冻结目录逐章续写,同一候选沿用自己的设计账本;续跑仍不能读取兄弟候选。 +4. 子代理先在内部检查设定咬合,再落完整条目;主会话不替它补创意,只做编排和机械验证。 +5. 任一候选未达到合同,不得先拿已完成候选做综合,避免后写方案被前案污染。 + +受控 Claude 调用遵守 `execute-claude-task` 的 fresh process、沙箱、期限和模型要求,并由 `record-run-evidence` 保存回执。模型超时或截断时保留该候选已完成的合法章节,从最后一个完整设定项继续;禁止用空话补足字数。 + +## 去 AI 味与语义复核 + +候选完成后单独做表达层复核。可用 `humanizer-zh` 时按其合同处理;没有该能力时,至少检查宣传腔、空泛升华、假对照、整齐三段式、连续同句型、模糊归因和高频套话。 + +去 AI 味只改表达,不得改根设定、数值、编号、章节、阶段披露点和因果。处理后必须重新跑机械门,并做一次语义复核: + +- 根设定是否全部兑现,是否暗加用户未授权的硬设定。 +- 前三章、前十章、前五十章是否各有目标、兑现、代价和新问题。 +- 作者总设定与读者阶段认知是否分开,是否提前泄露终局答案。 +- 能力、资源、等级、敌人和组织是否互相咬合,有无无成本万能解。 +- 设定能否通过事件、差异和后果呈现,是否只能靠旁白说明。 +- 参考作品是否只借了方法,是否换名照搬了专属骨架。 + +## 机械门 + +从 `agent-example/` 运行: + +```bash +.venv/bin/python .claude/skills/design-story-foundation/scripts/validate_candidates.py \ + --root-doc docs/design/<作品>-前期设计.md \ + --min-candidates 5 --min-chars 50000 \ + docs/design/candidates/*.md +``` + +校验器检查根设定句长和结构、候选目录一致性、号段、编号唯一性、每章 10—50 项、总项数、有效字符数和占位符。任一文件失败,整组状态为 `SETTING_INIT_VALIDATION_FAILED`,不得声称候选齐备。 + +## 用户选择与交接 + +全部候选通过后,向用户提交比较报告,不自动合并。每份方案说明开局吸引力、十章兑现、五十章高潮、长线扩张、主要代价和最可能失速的位置;选项必须说明会怎样改变后续故事。 + +只有用户明确选择或给出修改方向后,才开始收敛。用户可以直接指定主案,也可以要求把多份候选逐章统合;后一种方式必须遵守 [串行统合合同](references/serial-merge-contract.md),不能一次性把所有章节交给同一代理拼接。 + +逐章统合时,每一章使用一个 fresh 高推理代理。第一个代理只比较五份候选的第一章;主会话审定并写入统合候选后,第二个代理必须读取最新统合前文,再比较五份候选的第二章。此后依次推进,权威顺序固定为“最新根设定 > 已统合前文 > 当前五份来源章节”。后章不得推翻前章已经确定的因果、人物关系、数值、专名和披露节点。 + +统合代理负责筛选、识别冲突和提出当前章草案,主会话负责消歧、定名、补齐根设定和最终落文档。不能按票数机械取多数,也不能把五案互斥的发动机全部叠加。每章落盘后先检查结构与语义,再生成下一章任务包;失败时停在当前章,不得让后续代理基于未审定草案继续。 + +统合完成后仍是待选候选,不自动进入唯一前期设计 SoT。用户确认统合方向后,才把内容整理回 SoT;其余候选删除,由 Git 历史追溯。用户明确说“前期设计定稿”后,才把选定方案交给 `plan-story`,按正式 schema 生成 Shadow 规划。 + +## 数据与失败边界 + +- 允许读取:用户点名的前期设计文档、对话记录、参考书窗级大纲与授权正文范围。 +- 允许写入:唯一前期设计 SoT 和其临时候选目录;不得修改正文、正式规划、知识卡或状态。 +- 数据库:不读不写任何表;这些文件是产品链之外的用户协作草稿,在系统视角中不是正式内容。 +- Git:不执行 `add`、`commit` 或删除候选,除非用户对相应动作另行明确授权。 + +缺根设定、对话冲突未标出、参考范围不完整、候选之间发生污染或机械门失败时,返回稳定失败码并停在候选阶段:`SETTING_INIT_INPUT_INCOMPLETE`、`SETTING_INIT_CONTRACT_DRIFT`、`SETTING_INIT_CANDIDATE_CONTAMINATED` 或 `SETTING_INIT_VALIDATION_FAILED`。不得用部分结果冒充完成。 diff --git a/.claude/skills/design-story-foundation/references/candidate-contract.md b/.claude/skills/design-story-foundation/references/candidate-contract.md new file mode 100644 index 0000000..de787da --- /dev/null +++ b/.claude/skills/design-story-foundation/references/candidate-contract.md @@ -0,0 +1,97 @@ +# 候选生成合同(design-story-foundation/v1) + +本合同在派发作品设定候选前读取。它定义共同输入、候选结构和复核口径,不保存任何具体作品设定。 + +## 一、冻结任务包 + +主会话先组装任务包,再复制给全部候选子代理。只有 `candidateId` 和 `outputPath` 可以因候选不同而变化。 + +| 字段 | 内容 | +|---|---| +| `contractVersion` | 固定为 `design-story-foundation/v1` | +| `workTitle` | 当前作品名 | +| `sotPath` | 唯一前期设计文档路径 | +| `rootSetting` | 根设定全文,不用摘要替代 | +| `conversationRecord` | 与作品有关的用户原话,按时间排序 | +| `decisionLedger` | 每项标为已接受、已否决、待定或被新要求覆盖 | +| `unresolvedQuestions` | 会实质改变故事方向、仍需用户拍板的问题 | +| `retentionMilestones` | 前三章、前十章、前五十章及后续阶段落点 | +| `disclosureRules` | 作者总设定与读者阶段认知的分界 | +| `referenceEvidence` | 大纲直证、正文归纳、可借鉴、不可照搬四栏 | +| `sectionContract` | 统一二级标题、顺序、预留编号号段 | +| `quantityContract` | 每章 10—50 项、全文至少 100 项、有效字符下限 | +| `candidateId` | 当前子代理唯一编号 | +| `outputPath` | 当前子代理唯一输出文件 | + +对话不能只给总结。原话用于防止整理时改义,`decisionLedger` 用来阻断已经否决的旧方案复活。出现矛盾时,子代理不得自行选择;它只按任务包里已写明的优先级执行,并把真正未决项留给用户。 + +## 二、统一文档形状 + +每份候选只允许一个一级标题。正文使用任务包冻结的二级标题,不增设三级标题。每个内容章紧跟一个号段标记: + +```markdown +## 一、示例章节 + + + +- **S001|设定名称:** 设定正文。 +``` + +目录名称、顺序、号段必须逐字一致。候选序号可以出现在一级标题,不能进入共同目录。每章实际使用 10—50 个编号;空号允许,越界、倒序和重复不允许。 + +一项设定可以写多段,但必须围绕同一规则。正文自然交代以下内容,不使用整齐划一的表单腔: + +- 规则是什么,在什么条件下生效。 +- 谁因此获利,谁承担成本,失控时会发生什么。 +- 它会在哪个阶段进入故事,通过什么事件让读者看见。 +- 它怎样连接角色关系、资源压力、冲突或下一阶段入口。 + +达到篇幅门槛靠机制、差异、案例、后果和边界,不靠同义改写、套话、总结段或重复背景。 + +## 三、节奏与披露 + +候选先完成追读链,再扩写设定。三个早期里程碑都要同时具备“当期问题、行动目标、实际兑现、付出代价、章末新问题”。 + +- 前三章让读者看见异常和价值,不解释最终来源,不把终局阵营拉到台前。 +- 前十章让优势经过对手或任务验证,同时暴露限制,完成第一个局部闭环。 +- 前五十章完成初期主冲突和一次身份或能力抬升,再打开更大舞台。 +- 后续阶段继续扩大问题尺度;早期劳动、训练、资源或关系线要换规模延续,不能用完即丢。 + +总设定写作者掌握的真相;阶段设定写读者当时能确认的事实。一个谜底可以在作者侧确定,但它的征兆、误判、局部解释和正式揭示必须分开放置。 + +## 四、独立性 + +候选子代理只能读取任务包、共同参考证据和自己的文件。不得搜索候选目录,不得读取其他候选,不得询问主会话“前一份怎么写”。主会话也不能把某份候选的优点转述给尚未完成的代理。 + +长文需要多轮时,在同一候选内部维护简短设计账本:已经确定的因果、数值、人物关系、阶段披露点和未完成章节。账本只属于该候选,续跑时与任务包一起提供。 + +## 五、表达复核 + +完成内容复核后再处理语言。表达层重点清理: + +- 空泛评价代替具体事件,例如只说“极具张力”“层层递进”。 +- 每段都用“不是……而是……”或三项排比制造伪力度。 +- 所有设定项使用相同句式、相同结尾或固定总结句。 +- 频繁使用“同时、此外、值得注意的是、总而言之”等连接词。 +- 用旁白宣布人物多强、世界多危险,却没有任务、损失和对比支撑。 +- 为了显得完整,提前解释终局真相或最终敌人的全貌。 + +复核可以改句子长短、用词和段落节奏,不能改事实、编号、目录、量级、代价或披露节点。 + +## 六、交付报告 + +所有候选过门后才生成比较报告。报告逐案回答: + +1. 前三章靠什么让读者继续。 +2. 前十章兑现了什么,暴露了什么限制。 +3. 前五十章在哪个事件形成初期高潮。 +4. 初期机制如何换规模进入中期和后期。 +5. 这套方案最大的收益、代价和失速风险是什么。 + +报告只供用户选择,不宣告胜者。用户没有拍板前,不合并、不落库、不写正文。 + +## 七、用户授权后的逐章统合 + +用户明确要求综合多份候选时,独立生成阶段结束,进入串行统合阶段。统合不再要求来源隔离,但每个代理仍只处理一个当前章节,不能提前读取后续来源章节。详细输入、权威顺序、输出格式与失败边界见 [串行统合合同](serial-merge-contract.md)。 + +串行链的每个节点都要生成 fresh 会话。当前章只有经主会话审定并写入统合候选后,才可作为下一节点的权威前文。未审定的代理输出、分析摘要和舍弃方案不能进入下一节点上下文。 diff --git a/.claude/skills/design-story-foundation/references/serial-merge-contract.md b/.claude/skills/design-story-foundation/references/serial-merge-contract.md new file mode 100644 index 0000000..c796a58 --- /dev/null +++ b/.claude/skills/design-story-foundation/references/serial-merge-contract.md @@ -0,0 +1,57 @@ +# 候选逐章串行统合合同(design-story-foundation/serial-merge-v1) + +本合同用于用户明确要求综合多份已完成候选的场景。它只生成一份新的统合候选,不直接修改作品前期设计 SoT,不写正文,不落数据库。 + +## 一、串行拓扑 + +统合严格按冻结目录从前往后执行,一章一个 fresh 高推理代理。当前章审定落盘之前,不启动下一章。每个节点只接收三类作品内容:最新根设定全文、统合候选已经完成的已统合前文、所有来源候选的当前章节。 + +禁止把来源候选的后续章节提前交给当前代理。禁止复用上一节点会话,防止舍弃方案和未审定分析越过主会话进入后章。代理输出只是建议;主会话完成冲突检查、必要改写和落盘后,落盘版本才成为下一节点输入。 + +## 二、事实权威 + +发生冲突时,按以下顺序裁决: + +1. 用户最新确认的根设定。 +2. 统合候选已经审定落盘的前文章节。 +3. 当前五份来源章节中与前两项兼容的高质量设定。 +4. 为补齐因果所需的最小新增连接内容。 + +来源候选没有投票权。三份写法相同也不能压过根设定,一份写法更完整也可以被采用。当前章可以合并同方向条目、删去重复条目、改写名称与数值以消除冲突,但不能偷偷更换已经确定的故事发动机。 + +## 三、质量选择 + +优先保留能直接产生剧情、代价、差异和后续接口的设定。单纯正确但没有故事用途的百科说明降级;只靠旁白成立的强度宣告降级;与前文重复、只换说法的条目删除。 + +每个保留项至少完成两件事:说清规则或事实;说明它如何通过事件、人物选择、资源损失、对手反应或阶段兑现进入故事。涉及底牌时要同步写清读者在当前阶段能知道的边界。 + +不把五案的互斥卖点全部叠加。故事只能有一条主发动机,其他候选的优点只能作为服务主线的机制、角色资产或事件结构进入。新增内容以补缝为限,不另造第六套世界观。 + +## 四、当前章输出 + +代理先输出简短决策摘要,再输出可直接审定的章节草案,使用以下边界标记: + +```text + +主轴、主要取舍、发现的前文冲突及处理方式。 + + +## 冻结的当前章标题 + +- **S001|设定名:** 设定正文。 + +``` + +章节只使用一个二级标题,不增设三级标题。编号必须在冻结号段内递增且不重复,每章 10—50 项。篇幅靠规则、事件、代价和边界获得,不靠同义扩写。不得在正文候选中记录代理、模型、统合过程或来源票数。 + +## 五、主会话审定 + +主会话逐项检查根设定覆盖、与前文的名称和数值一致性、因果闭合、阶段披露、故事负荷及 AI 模板腔。发现冲突时以最小改动修正当前章,不能为了保留当前好点子反向改掉已审定前文;确实需要改前文时必须停下来向用户说明,而不是自行回写。 + +机械结构通过、语义冲突清零后,主会话才将 `` 内容写入统合候选。`` 留在临时审计文件,不进入作品文档。下一节点读取的是落盘后的完整统合候选,不读取原始输出。 + +## 六、完成条件 + +全部章节完成后,统合候选必须通过与来源候选相同的目录、号段、数量、篇幅和占位符门禁,再进行一次全篇交叉检查。重点检查早期承诺是否在后章换尺度延续,人物与组织是否串位,能力成本是否被后章绕开,以及新增宇宙格局是否遵守既定冲突升级顺序。 + +统合完成仍不等于用户定稿。未经用户确认,不合并进前期设计 SoT,不删除五份来源候选,不进入 `plan-story`。 diff --git a/.claude/skills/design-story-foundation/scripts/serial_merge.py b/.claude/skills/design-story-foundation/scripts/serial_merge.py new file mode 100644 index 0000000..0c79d04 --- /dev/null +++ b/.claude/skills/design-story-foundation/scripts/serial_merge.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +"""构建、调用、校验并接纳前期设计候选的逐章串行统合结果。""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from pathlib import Path +import re +import subprocess +import sys + + +H2_RE = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE) +H3_RE = re.compile(r"^#{3,6}\s+", re.MULTILINE) +RANGE_RE = re.compile(r"") +SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?(S\d{3,})(?=[^\d])", re.MULTILINE) +CHAPTER_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) +DECISION_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) + + +class SerialMergeError(ValueError): + """串行统合合同不满足。""" + + +@dataclass(frozen=True) +class Section: + heading: str + text: str + + +def effective_chars(text: str) -> int: + return len(re.sub(r"\s+", "", text)) + + +def sections(text: str) -> list[Section]: + matches = list(H2_RE.finditer(text)) + result: list[Section] = [] + for index, match in enumerate(matches): + end = matches[index + 1].start() if index + 1 < len(matches) else len(text) + result.append(Section(match.group(1).strip(), text[match.start():end].rstrip())) + return result + + +def exact_section(text: str, heading: str) -> Section: + matches = [section for section in sections(text) if section.heading == heading] + if len(matches) != 1: + raise SerialMergeError(f"章节“{heading}”必须且只能出现一次") + return matches[0] + + +def root_section(text: str) -> Section: + matches = [section for section in sections(text) if "根设定" in section.heading] + if len(matches) != 1: + raise SerialMergeError("根设定章节缺失或重复") + return matches[0] + + +def content_headings(text: str) -> list[str]: + return [section.heading for section in sections(text) if section.heading != "目录"] + + +def validate_prefix(integrated: str, source: str, heading: str) -> tuple[list[str], int]: + expected = content_headings(source) + if heading not in expected: + raise SerialMergeError(f"来源候选中没有章节“{heading}”") + index = expected.index(heading) + actual = content_headings(integrated) + if actual != expected[:index]: + raise SerialMergeError( + f"统合前文不是冻结目录前缀:期望 {expected[:index]},实际 {actual}" + ) + return expected, index + + +def build_packet(args: argparse.Namespace) -> None: + root_text = args.root_doc.read_text(encoding="utf-8") + integrated_text = args.integrated_doc.read_text(encoding="utf-8") + source_texts = [path.read_text(encoding="utf-8") for path in args.candidates] + if not source_texts: + raise SerialMergeError("至少需要一份来源候选") + + expected, index = validate_prefix(integrated_text, source_texts[0], args.heading) + source_shape = ["目录", *expected] + blocks: list[str] = [] + ranges: set[tuple[str, ...]] = set() + for path, text in zip(args.candidates, source_texts, strict=True): + if [section.heading for section in sections(text)] != source_shape: + raise SerialMergeError(f"来源候选目录漂移:{path}") + current = exact_section(text, args.heading) + found_ranges = RANGE_RE.findall(current.text) + if len(found_ranges) != 1: + raise SerialMergeError(f"来源章节号段异常:{path}") + ranges.add(found_ranges[0]) + blocks.append(f"### 来源候选:{path.name}\n\n{current.text}") + if len(ranges) != 1: + raise SerialMergeError(f"来源章节号段不一致:{sorted(ranges)}") + + range_start, range_end = next(iter(ranges)) + prior = integrated_text.rstrip() + packet = ( + "# 逐章串行统合输入包\n\n" + f"- 当前序号:{index + 1}/{len(expected)}\n" + f"- 当前标题:{args.heading}\n" + f"- 冻结号段:S{int(range_start):03d}-S{int(range_end):03d}\n\n" + "## 最新根设定(最高权威)\n\n" + f"{root_section(root_text).text}\n\n" + "## 已审定统合前文(次高权威)\n\n" + f"{prior}\n\n" + "## 五份来源候选的当前章节\n\n" + + "\n\n".join(blocks) + + "\n" + ) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(packet, encoding="utf-8") + print( + f"SERIAL_MERGE_PACKET_OK: {args.heading},来源 {len(blocks)} 份," + f"输入 {effective_chars(packet)} 有效字符" + ) + + +def run_luna(args: argparse.Namespace) -> None: + prompt = ( + f"你是当前第 {args.index} 章的独立串行统合代理。完整阅读合同与输入包," + f"只处理“{args.heading}”。选择并重写 {args.min_settings}—{args.max_settings} 项高质量设定," + f"章节有效字符不少于 {args.min_chars}。严格遵守根设定与已统合前文,不读取或猜测后续章节。" + "输出必须且只能包含 与 两个区块,不加代码围栏。" + ) + command = [ + "pi", + "--model", + args.model, + "--thinking", + args.thinking, + "--no-tools", + "--no-session", + "--no-context-files", + "--no-skills", + "--no-extensions", + "--mode", + "text", + "-p", + f"@{args.contract}", + f"@{args.packet}", + prompt, + ] + completed = subprocess.run( + command, + cwd=args.cwd, + text=True, + capture_output=True, + timeout=args.timeout, + check=False, + ) + if completed.returncode != 0: + if completed.stderr: + print(completed.stderr, file=sys.stderr) + raise SerialMergeError(f"Luna Max 调用失败,退出码 {completed.returncode}") + if not completed.stdout.strip(): + raise SerialMergeError("Luna Max 返回空结果") + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(completed.stdout, encoding="utf-8") + print( + f"SERIAL_MERGE_LUNA_OK: model={args.model} thinking={args.thinking} " + f"output={args.output}" + ) + + +def parse_raw( + raw: str, + heading: str, + range_start: int, + range_end: int, + min_settings: int, + max_settings: int, + min_chars: int, +) -> tuple[str, str, list[int]]: + chapter_matches = CHAPTER_RE.findall(raw) + decision_matches = DECISION_RE.findall(raw) + if len(chapter_matches) != 1 or len(decision_matches) != 1: + raise SerialMergeError("输出必须各含一个 与 区块") + chapter = chapter_matches[0].strip() + decision = decision_matches[0].strip() + chapter_sections = sections(chapter) + if len(chapter_sections) != 1 or chapter_sections[0].heading != heading: + raise SerialMergeError(f"章节标题必须为“{heading}”且不能出现其他二级标题") + if H3_RE.search(chapter): + raise SerialMergeError("章节草案出现三级或更深标题") + found_ranges = RANGE_RE.findall(chapter) + normalized_ranges = [(int(start), int(end)) for start, end in found_ranges] + expected_range = (range_start, range_end) + if len(normalized_ranges) != 1 or normalized_ranges[0] != expected_range: + raise SerialMergeError( + f"章节号段必须为 S{range_start:03d}-S{range_end:03d}" + ) + ids = [int(match.group(1)[1:]) for match in SETTING_RE.finditer(chapter)] + if ids != sorted(ids) or len(ids) != len(set(ids)): + raise SerialMergeError("设定编号必须递增且不重复") + if any(value < range_start or value > range_end for value in ids): + raise SerialMergeError("设定编号越出冻结号段") + if not min_settings <= len(ids) <= max_settings: + raise SerialMergeError( + f"当前章 {len(ids)} 项,要求 {min_settings}—{max_settings} 项" + ) + if effective_chars(chapter) < min_chars: + raise SerialMergeError( + f"当前章 {effective_chars(chapter)} 有效字符,低于 {min_chars}" + ) + return decision, chapter, ids + + +def check_output(args: argparse.Namespace, accept: bool = False) -> None: + raw = args.raw.read_text(encoding="utf-8") + decision, chapter, ids = parse_raw( + raw, + args.heading, + args.range_start, + args.range_end, + args.min_settings, + args.max_settings, + args.min_chars, + ) + if accept: + integrated_text = args.integrated_doc.read_text(encoding="utf-8") + source_text = args.source_candidate.read_text(encoding="utf-8") + validate_prefix(integrated_text, source_text, args.heading) + updated = integrated_text.rstrip() + "\n\n" + chapter.rstrip() + "\n" + args.integrated_doc.write_text(updated, encoding="utf-8") + action = "ACCEPTED" if accept else "CHECK_OK" + print( + f"SERIAL_MERGE_{action}: {args.heading},{len(ids)} 项," + f"{effective_chars(chapter)} 有效字符;决策摘要 {effective_chars(decision)} 字符" + ) + + +def shared_output_args(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--raw", type=Path, required=True) + parser.add_argument("--heading", required=True) + parser.add_argument("--range-start", type=int, required=True) + parser.add_argument("--range-end", type=int, required=True) + parser.add_argument("--min-settings", type=int, default=10) + parser.add_argument("--max-settings", type=int, default=50) + parser.add_argument("--min-chars", type=int, default=4200) + + +def parser() -> argparse.ArgumentParser: + root = argparse.ArgumentParser(description=__doc__) + subparsers = root.add_subparsers(dest="command", required=True) + + packet = subparsers.add_parser("packet") + packet.add_argument("--root-doc", type=Path, required=True) + packet.add_argument("--integrated-doc", type=Path, required=True) + packet.add_argument("--heading", required=True) + packet.add_argument("--output", type=Path, required=True) + packet.add_argument("candidates", type=Path, nargs="+") + packet.set_defaults(handler=build_packet) + + run = subparsers.add_parser("run") + run.add_argument("--packet", type=Path, required=True) + run.add_argument("--contract", type=Path, required=True) + run.add_argument("--heading", required=True) + run.add_argument("--index", type=int, required=True) + run.add_argument("--output", type=Path, required=True) + run.add_argument("--cwd", type=Path, required=True) + run.add_argument("--model", default="catproxy-openai/gpt-5.6-luna") + run.add_argument("--thinking", default="max") + run.add_argument("--min-settings", type=int, default=12) + run.add_argument("--max-settings", type=int, default=18) + run.add_argument("--min-chars", type=int, default=4200) + run.add_argument("--timeout", type=int, default=1200) + run.set_defaults(handler=run_luna) + + check = subparsers.add_parser("check") + shared_output_args(check) + check.set_defaults(handler=lambda args: check_output(args, accept=False)) + + accept = subparsers.add_parser("accept") + shared_output_args(accept) + accept.add_argument("--integrated-doc", type=Path, required=True) + accept.add_argument("--source-candidate", type=Path, required=True) + accept.set_defaults(handler=lambda args: check_output(args, accept=True)) + return root + + +def main() -> int: + args = parser().parse_args() + try: + args.handler(args) + except (OSError, SerialMergeError, subprocess.TimeoutExpired) as error: + print(f"SERIAL_MERGE_FAILED: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.claude/skills/design-story-foundation/scripts/test_contract.py b/.claude/skills/design-story-foundation/scripts/test_contract.py new file mode 100644 index 0000000..c7bb636 --- /dev/null +++ b/.claude/skills/design-story-foundation/scripts/test_contract.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""design-story-foundation 与消费者、项目入口的离线合同测试。""" + +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[4] +SKILL = (ROOT / ".claude/skills/design-story-foundation/SKILL.md").read_text(encoding="utf-8") +REFERENCE = (ROOT / ".claude/skills/design-story-foundation/references/candidate-contract.md").read_text(encoding="utf-8") +SERIAL_MERGE = (ROOT / ".claude/skills/design-story-foundation/references/serial-merge-contract.md").read_text(encoding="utf-8") +PLANNER = (ROOT / ".claude/agents/planner.md").read_text(encoding="utf-8") +AGENTS = (ROOT / "AGENTS.md").read_text(encoding="utf-8") +CHAINS = (ROOT / "meta/chains/README.md").read_text(encoding="utf-8") + + +class SettingInitContractTest(unittest.TestCase): + def test_single_purpose_and_handoff_are_explicit(self) -> None: + self.assertIn("正式规划前", SKILL) + self.assertIn("不写正文、不落库、不替用户定稿", SKILL) + self.assertIn("用户明确说“前期设计定稿”", SKILL) + + def test_root_and_rhythm_contracts_are_present(self) -> None: + for phrase in ("30—60", "前三章", "前十章", "前五十章", "作者掌握的总设定"): + self.assertIn(phrase, SKILL) + + def test_candidate_quantity_is_a_range(self) -> None: + self.assertIn("10—50", SKILL) + self.assertIn("不少于 100 项", SKILL) + self.assertNotIn("每章固定 10 项", SKILL) + + def test_independent_generation_contract_is_frozen(self) -> None: + self.assertIn("candidateId", REFERENCE) + self.assertIn("只有 `candidateId` 和 `outputPath`", REFERENCE) + self.assertIn("不得搜索候选目录", REFERENCE) + + def test_serial_merge_is_strictly_chapter_ordered(self) -> None: + self.assertIn("一章一个 fresh 高推理代理", SERIAL_MERGE) + self.assertIn("最新根设定", SERIAL_MERGE) + self.assertIn("已统合前文", SERIAL_MERGE) + self.assertIn("当前章节", SERIAL_MERGE) + self.assertIn("不能按票数机械取多数", SKILL) + + def test_planner_and_inventory_expose_the_skill(self) -> None: + self.assertIn("`design-story-foundation`", PLANNER) + self.assertIn("`design-story-foundation`", AGENTS) + self.assertIn("setting_init 作品设定初始化", CHAINS) + self.assertIn("候选不落库、不进 Canonical", CHAINS) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/design-story-foundation/scripts/test_serial_merge.py b/.claude/skills/design-story-foundation/scripts/test_serial_merge.py new file mode 100644 index 0000000..d957b97 --- /dev/null +++ b/.claude/skills/design-story-foundation/scripts/test_serial_merge.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""逐章串行统合工具的离线测试。""" + +import argparse +from pathlib import Path +import tempfile +import unittest + +from serial_merge import SerialMergeError, build_packet, parse_raw, validate_prefix + + +def candidate(name: str) -> str: + return f"""# {name} + +## 目录 + +- 第一章 +- 第二章 + +## 一、定位 + + + +- **S001|{name}一:** 第一章来源内容足够长,用来验证前文章节不会混入当前来源区。 +- **S002|{name}二:** 第一章第二条来源内容,同样只应该通过已审定统合前文出现。 + +## 二、世界 + + + +- **S051|{name}三:** 第二章当前来源内容,应该进入发给新代理的比较输入包。 +- **S052|{name}四:** 第二章另一条来源内容,用于确认五案当前章可以并列比较。 +""" + + +class SerialMergeTest(unittest.TestCase): + def setUp(self) -> None: + self.temp = tempfile.TemporaryDirectory() + self.root = Path(self.temp.name) + + def tearDown(self) -> None: + self.temp.cleanup() + + def write(self, name: str, text: str) -> Path: + path = self.root / name + path.write_text(text, encoding="utf-8") + return path + + def test_packet_contains_prior_and_only_current_source_sections(self) -> None: + root_doc = self.write( + "root.md", + "# 设计\n\n## 一、根设定\n\n- **前提:**这是一条足够长的根设定,用来验证输入包带着最高权威进入每个节点。\n\n## 二、其他\n", + ) + sources = [self.write(f"c{index}.md", candidate(f"候选{index}")) for index in range(1, 3)] + integrated = self.write( + "integrated.md", + "# 统合\n\n## 目录\n\n- 第一章\n- 第二章\n\n## 一、定位\n\n\n\n- **S001|已定:** 这是主会话审定后的第一章,不是任何来源草案。\n", + ) + output = self.root / "packet.md" + build_packet( + argparse.Namespace( + root_doc=root_doc, + integrated_doc=integrated, + heading="二、世界", + output=output, + candidates=sources, + ) + ) + text = output.read_text(encoding="utf-8") + self.assertIn("这是主会话审定后的第一章", text) + self.assertIn("第二章当前来源内容", text) + self.assertNotIn("第一章来源内容足够长", text) + + def test_prefix_drift_is_rejected(self) -> None: + source = candidate("来源") + integrated = "# 统合\n\n## 目录\n\n## 二、世界\n" + with self.assertRaises(SerialMergeError): + validate_prefix(integrated, source, "二、世界") + + def test_raw_output_contract(self) -> None: + raw = """保留能够产生剧情的规则。 + +## 二、世界 + +- **S051|规则一:** 规则有明确条件、事件用途、失败代价以及后续阶段接口,不靠旁白成立。 +- **S052|规则二:** 另一条规则与前文兼容,并通过人物行动和资源损失进入故事。 +""" + _, _, ids = parse_raw(raw, "二、世界", 51, 100, 2, 3, 20) + self.assertEqual([51, 52], ids) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/design-story-foundation/scripts/test_validate_candidates.py b/.claude/skills/design-story-foundation/scripts/test_validate_candidates.py new file mode 100644 index 0000000..f314294 --- /dev/null +++ b/.claude/skills/design-story-foundation/scripts/test_validate_candidates.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""前期设计候选校验器的离线测试。""" + +from pathlib import Path +import tempfile +import unittest + +from validate_candidates import ValidationConfig, validate_candidates, validate_root + + +def make_candidate(second_heading: str = "二、资源规则", out_of_range: bool = False) -> str: + first_ids = range(1, 3) + second_ids = (101, 52) if out_of_range else range(51, 53) + first = "\n".join(f"- **S{value:03d}|规则:** 这一条有明确条件、代价和剧情用途。" for value in first_ids) + second = "\n".join(f"- **S{value:03d}|规则:** 这一条有明确条件、代价和剧情用途。" for value in second_ids) + return f"""# 候选 + +## 目录 + +- 第一章 +- 第二章 + +## 一、故事发动机 + + + +{first} + +## {second_heading} + + + +{second} +""" + + +class CandidateValidatorTest(unittest.TestCase): + def setUp(self) -> None: + self.temp_dir = tempfile.TemporaryDirectory() + self.root = Path(self.temp_dir.name) + self.config = ValidationConfig( + min_candidates=2, + min_settings=4, + min_section_settings=2, + max_section_settings=3, + min_chars=0, + min_item_chars=10, + ) + + def tearDown(self) -> None: + self.temp_dir.cleanup() + + def write(self, name: str, content: str) -> Path: + path = self.root / name + path.write_text(content, encoding="utf-8") + return path + + def test_two_same_shape_candidates_pass(self) -> None: + paths = [self.write("a.md", make_candidate()), self.write("b.md", make_candidate())] + reports, problems = validate_candidates(paths, self.config) + self.assertEqual(2, len(reports)) + self.assertEqual([], problems) + + def test_heading_drift_fails_group(self) -> None: + paths = [ + self.write("a.md", make_candidate()), + self.write("b.md", make_candidate(second_heading="二、人物关系")), + ] + _, problems = validate_candidates(paths, self.config) + self.assertIn("CANDIDATE_STRUCTURE_MISMATCH", {problem.code for problem in problems}) + + def test_out_of_range_and_order_are_rejected(self) -> None: + paths = [self.write("a.md", make_candidate()), self.write("b.md", make_candidate(out_of_range=True))] + _, problems = validate_candidates(paths, self.config) + codes = {problem.code for problem in problems} + self.assertIn("SETTING_ID_OUT_OF_RANGE", codes) + self.assertIn("SETTING_ORDER", codes) + + def test_root_contract_accepts_clean_items(self) -> None: + root_doc = self.write( + "root.md", + """# 前期设计 + +## 一、根设定 + +- **作品前提:**主角刚结束高考,随后进入拥有星际机甲的陌生时代并寻找立足之地。 +- **披露要求:**前三章只呈现当前危机和能力征兆,终局答案留到后续阶段逐步揭开。 + +## 二、定盘 +""", + ) + problems = validate_root(root_doc, self.config) + self.assertEqual([], problems) + + def test_root_rejects_process_language(self) -> None: + root_doc = self.write( + "root.md", + """# 前期设计 + +## 一、根设定 + +- **代理要求:**Claude 子代理分别生成候选文档,再由主会话比较和选择最终方向。 + +## 二、定盘 +""", + ) + problems = validate_root(root_doc, self.config) + self.assertIn("ROOT_PROCESS_LEAK", {problem.code for problem in problems}) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/design-story-foundation/scripts/validate_candidates.py b/.claude/skills/design-story-foundation/scripts/validate_candidates.py new file mode 100644 index 0000000..0223230 --- /dev/null +++ b/.claude/skills/design-story-foundation/scripts/validate_candidates.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +"""校验作品设定初始化的根设定与同构候选,不调用模型、不访问数据库。""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Iterable + + +H2_RE = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE) +NESTED_HEADING_RE = re.compile(r"^#{3,6}\s+", re.MULTILINE) +RANGE_RE = re.compile(r"") +SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?(S\d{3,})(?=[^\d])", re.MULTILINE) +ROOT_ITEM_RE = re.compile(r"^\s*-\s+\*\*([^*]+)\*\*(.+)$") +PLACEHOLDER_RE = re.compile( + r"(?:\bTODO\b|\bTBD\b|\[待填\]|待补充|待生成|在此填写|PLACEHOLDER)", + re.IGNORECASE, +) +PROCESS_TERMS = ("Claude", "claude", "子代理", "候选文档", "提示词", "工具调用", "设计过程", "方案来源") + + +@dataclass(frozen=True) +class ValidationConfig: + min_candidates: int = 1 + min_settings: int = 100 + min_section_settings: int = 10 + max_section_settings: int = 50 + min_chars: int = 50000 + min_item_chars: int = 20 + root_min_chars: int = 30 + root_max_chars: int = 60 + + +@dataclass(frozen=True) +class Problem: + code: str + file: str + message: str + + +@dataclass(frozen=True) +class SectionReport: + heading: str + range_start: int + range_end: int + setting_count: int + + +@dataclass(frozen=True) +class CandidateReport: + file: str + effective_chars: int + setting_count: int + headings: tuple[str, ...] + sections: tuple[SectionReport, ...] + + +def effective_chars(text: str) -> int: + """按非空白字符计数,避免用空行填充篇幅。""" + return len(re.sub(r"\s+", "", text)) + + +def visible_chars(text: str) -> int: + """去掉常见 Markdown 标记后统计可见字符。""" + cleaned = re.sub(r"[*_`#>\[\]()]", "", text) + return effective_chars(cleaned) + + +def _problem(code: str, path: Path, message: str) -> Problem: + return Problem(code=code, file=str(path), message=message) + + +def validate_root(path: Path, config: ValidationConfig) -> list[Problem]: + problems: list[Problem] = [] + text = path.read_text(encoding="utf-8") + headings = list(H2_RE.finditer(text)) + root_index = next((index for index, match in enumerate(headings) if "根设定" in match.group(1)), None) + if root_index is None: + return [_problem("ROOT_SECTION_MISSING", path, "未找到包含“根设定”的二级标题")] + + start = headings[root_index].end() + end = headings[root_index + 1].start() if root_index + 1 < len(headings) else len(text) + body = text[start:end] + + if NESTED_HEADING_RE.search(body): + problems.append(_problem("ROOT_STRUCTURE", path, "根设定内部出现三级或更深标题")) + + for term in PROCESS_TERMS: + if term in body: + problems.append(_problem("ROOT_PROCESS_LEAK", path, f"根设定包含设计过程词:{term}")) + + labels: set[str] = set() + items = 0 + for line_number, raw_line in enumerate(body.splitlines(), start=1): + line = raw_line.strip() + if not line or line == "---": + continue + match = ROOT_ITEM_RE.match(line) + if not match: + problems.append( + _problem("ROOT_ITEM_FORMAT", path, f"根设定第 {line_number} 个相对行不是单行加粗标签条目") + ) + continue + items += 1 + label = match.group(1).strip() + if label in labels: + problems.append(_problem("ROOT_LABEL_DUPLICATE", path, f"根设定标签重复:{label}")) + labels.add(label) + length = visible_chars(line.lstrip("- ")) + if not config.root_min_chars <= length <= config.root_max_chars: + problems.append( + _problem( + "ROOT_ITEM_LENGTH", + path, + f"根设定“{label}”为 {length} 个可见字符,要求 {config.root_min_chars}—{config.root_max_chars}", + ) + ) + if items == 0: + problems.append(_problem("ROOT_EMPTY", path, "根设定没有可校验条目")) + return problems + + +def _parse_candidate(path: Path, config: ValidationConfig) -> tuple[CandidateReport, list[Problem]]: + text = path.read_text(encoding="utf-8") + problems: list[Problem] = [] + h2_matches = list(H2_RE.finditer(text)) + headings = tuple(match.group(1).strip() for match in h2_matches) + + if not h2_matches: + problems.append(_problem("CANDIDATE_STRUCTURE", path, "候选没有二级标题")) + if NESTED_HEADING_RE.search(text): + problems.append(_problem("CANDIDATE_STRUCTURE", path, "候选出现三级或更深标题,违反统一两级结构")) + if PLACEHOLDER_RE.search(text): + problems.append(_problem("CANDIDATE_PLACEHOLDER", path, "候选仍含待填占位文字")) + + sections: list[SectionReport] = [] + all_ids: list[int] = [] + previous_range_end = 0 + + for index, heading_match in enumerate(h2_matches): + start = heading_match.end() + end = h2_matches[index + 1].start() if index + 1 < len(h2_matches) else len(text) + body = text[start:end] + ranges = RANGE_RE.findall(body) + setting_matches = list(SETTING_RE.finditer(body)) + + if not ranges and not setting_matches and heading_match.group(1).strip() == "目录": + continue + if len(ranges) != 1: + problems.append( + _problem("SECTION_RANGE", path, f"章节“{heading_match.group(1).strip()}”必须且只能有一个号段标记") + ) + continue + + range_start, range_end = (int(value) for value in ranges[0]) + if range_start > range_end: + problems.append(_problem("SECTION_RANGE", path, f"章节号段倒置:S{range_start:03d}-S{range_end:03d}")) + if range_start <= previous_range_end: + problems.append(_problem("SECTION_RANGE", path, "章节号段未按顺序递增或发生重叠")) + previous_range_end = max(previous_range_end, range_end) + + ids = [int(match.group(1)[1:]) for match in setting_matches] + all_ids.extend(ids) + if ids != sorted(ids): + problems.append(_problem("SETTING_ORDER", path, f"章节“{heading_match.group(1).strip()}”的编号未递增")) + for setting_id in ids: + if not range_start <= setting_id <= range_end: + problems.append( + _problem( + "SETTING_ID_OUT_OF_RANGE", + path, + f"S{setting_id:03d} 不在章节号段 S{range_start:03d}-S{range_end:03d} 内", + ) + ) + + count = len(ids) + if not config.min_section_settings <= count <= config.max_section_settings: + problems.append( + _problem( + "SECTION_SETTING_COUNT", + path, + f"章节“{heading_match.group(1).strip()}”有 {count} 项,要求 {config.min_section_settings}—{config.max_section_settings}", + ) + ) + + for item_index, setting_match in enumerate(setting_matches): + item_end = setting_matches[item_index + 1].start() if item_index + 1 < len(setting_matches) else len(body) + item_text = body[setting_match.start():item_end] + if visible_chars(item_text) < config.min_item_chars: + problems.append( + _problem("SETTING_ITEM_TOO_SHORT", path, f"{setting_match.group(1)} 内容过短,疑似只有标题或占位句") + ) + + sections.append( + SectionReport( + heading=heading_match.group(1).strip(), + range_start=range_start, + range_end=range_end, + setting_count=count, + ) + ) + + duplicates = sorted({setting_id for setting_id in all_ids if all_ids.count(setting_id) > 1}) + if duplicates: + display = ", ".join(f"S{setting_id:03d}" for setting_id in duplicates[:10]) + problems.append(_problem("SETTING_ID_DUPLICATE", path, f"全文编号重复:{display}")) + + char_count = effective_chars(text) + if char_count < config.min_chars: + problems.append( + _problem("CANDIDATE_LENGTH", path, f"有效字符 {char_count},低于门槛 {config.min_chars}") + ) + if len(all_ids) < config.min_settings: + problems.append( + _problem("CANDIDATE_SETTING_COUNT", path, f"全文共 {len(all_ids)} 项设定,低于门槛 {config.min_settings}") + ) + + report = CandidateReport( + file=str(path), + effective_chars=char_count, + setting_count=len(all_ids), + headings=headings, + sections=tuple(sections), + ) + return report, problems + + +def validate_candidates( + paths: Iterable[Path], + config: ValidationConfig, + root_doc: Path | None = None, +) -> tuple[list[CandidateReport], list[Problem]]: + candidate_paths = sorted((Path(path) for path in paths), key=lambda item: str(item)) + problems: list[Problem] = [] + reports: list[CandidateReport] = [] + + if len(candidate_paths) < config.min_candidates: + problems.append( + Problem( + code="CANDIDATE_COUNT", + file="", + message=f"只有 {len(candidate_paths)} 份候选,要求至少 {config.min_candidates} 份", + ) + ) + if root_doc is not None: + if not root_doc.is_file(): + problems.append(_problem("ROOT_FILE_MISSING", root_doc, "根设定文档不存在")) + else: + problems.extend(validate_root(root_doc, config)) + + for path in candidate_paths: + if not path.is_file(): + problems.append(_problem("CANDIDATE_FILE_MISSING", path, "候选文件不存在")) + continue + report, file_problems = _parse_candidate(path, config) + reports.append(report) + problems.extend(file_problems) + + if reports: + baseline = reports[0] + baseline_shape = tuple( + (section.heading, section.range_start, section.range_end) for section in baseline.sections + ) + for report in reports[1:]: + shape = tuple((section.heading, section.range_start, section.range_end) for section in report.sections) + if report.headings != baseline.headings or shape != baseline_shape: + problems.append( + Problem( + code="CANDIDATE_STRUCTURE_MISMATCH", + file=report.file, + message=f"目录或号段与基准候选 {baseline.file} 不一致", + ) + ) + return reports, problems + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="校验作品设定初始化候选") + parser.add_argument("files", nargs="+", type=Path, help="候选 Markdown 文件") + parser.add_argument("--root-doc", type=Path, help="含根设定的唯一前期设计文档") + parser.add_argument("--min-candidates", type=int, default=1) + parser.add_argument("--min-settings", type=int, default=100) + parser.add_argument("--min-section-settings", type=int, default=10) + parser.add_argument("--max-section-settings", type=int, default=50) + parser.add_argument("--min-chars", type=int, default=50000) + parser.add_argument("--min-item-chars", type=int, default=20) + parser.add_argument("--root-min-chars", type=int, default=30) + parser.add_argument("--root-max-chars", type=int, default=60) + parser.add_argument("--json", action="store_true", help="输出稳定 JSON") + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + if args.min_section_settings > args.max_section_settings: + parser.error("--min-section-settings 不能大于 --max-section-settings") + if args.root_min_chars > args.root_max_chars: + parser.error("--root-min-chars 不能大于 --root-max-chars") + + config = ValidationConfig( + min_candidates=args.min_candidates, + min_settings=args.min_settings, + min_section_settings=args.min_section_settings, + max_section_settings=args.max_section_settings, + min_chars=args.min_chars, + min_item_chars=args.min_item_chars, + root_min_chars=args.root_min_chars, + root_max_chars=args.root_max_chars, + ) + reports, problems = validate_candidates(args.files, config, args.root_doc) + status = "ok" if not problems else "invalid" + code = "SETTING_INIT_OK" if not problems else "SETTING_INIT_VALIDATION_FAILED" + payload = { + "status": status, + "code": code, + "reports": [asdict(report) for report in reports], + "problems": [asdict(problem) for problem in problems], + } + + if args.json: + print(json.dumps(payload, ensure_ascii=False, indent=2)) + else: + print(f"{code}: {len(reports)} 份候选,{len(problems)} 个问题") + for report in reports: + print(f"- {report.file}: {report.setting_count} 项,{report.effective_chars} 有效字符") + for problem in problems: + print(f"[{problem.code}] {problem.file}: {problem.message}") + return 0 if not problems else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.claude/skills/embed/SKILL.md b/.claude/skills/embed-knowledge/SKILL.md similarity index 82% rename from .claude/skills/embed/SKILL.md rename to .claude/skills/embed-knowledge/SKILL.md index 7e64a35..c1096ea 100644 --- a/.claude/skills/embed/SKILL.md +++ b/.claude/skills/embed-knowledge/SKILL.md @@ -1,9 +1,9 @@ --- -name: embed -description: New-API 嵌入封装——Qwen3-Embedding-8B、dimensions=1024、禁系统代理、批量+失败重试;输入知识行(draft/entity)批量嵌入并写 example_knowledge_embedding,content_hash 幂等不重嵌。B2/B3 的向量生产端。 +name: embed-knowledge +description: 使用固定 Qwen3 嵌入模型将知识草稿或实体批量写入 pgvector,并按内容哈希幂等处理 owner 与版本。知识行需要建立或刷新检索向量时使用;不嵌入参考书全文。 --- -# embed —— 嵌入封装(New-API 网关) +# 嵌入知识内容 对应 muse API 面:AI 网关(嵌入)。通道事实见 [`db/连接信息.md`](../../../db/连接信息.md):BASE `http://100.64.0.8:3000`、模型 `Qwen/Qwen3-Embedding-8B`、请求体 `"dimensions":1024`(实测生效)、**禁系统代理**(`trust_env=False`)。 @@ -11,16 +11,16 @@ description: New-API 嵌入封装——Qwen3-Embedding-8B、dimensions=1024、 ```bash # 批量补嵌 pending 草稿(无活向量,或活向量的当前 payload+model hash 已过期) -.venv/bin/python .claude/skills/embed/scripts/embed_drafts.py +.venv/bin/python .claude/skills/embed-knowledge/scripts/embed_drafts.py # 指定 work(默认兼容参考书拆书批次,按 source_id)或限量 -.venv/bin/python .claude/skills/embed/scripts/embed_drafts.py --work-id 3 --limit 100 +.venv/bin/python .claude/skills/embed-knowledge/scripts/embed_drafts.py --work-id 3 --limit 100 # 章后抽卡按作品的 draft.work_id 筛选(source_id 是章节 id) -.venv/bin/python .claude/skills/embed/scripts/embed_drafts.py --work-id 12 --source-type chapter_extract +.venv/bin/python .claude/skills/embed-knowledge/scripts/embed_drafts.py --work-id 12 --source-type chapter_extract # 自由文本试嵌(调试/B3 查询端复用同实现) -.venv/bin/python .claude/skills/embed/scripts/embed_drafts.py --probe "机甲近战的节奏控制" +.venv/bin/python .claude/skills/embed-knowledge/scripts/embed_drafts.py --probe "机甲近战的节奏控制" ``` ## 合同 @@ -35,9 +35,9 @@ description: New-API 嵌入封装——Qwen3-Embedding-8B、dimensions=1024、 ## 离线验证 ```bash -.venv/bin/python .claude/skills/embed/scripts/test_embed_drafts_offline.py -.venv/bin/python -m py_compile .claude/skills/embed/scripts/embed_drafts.py \ - .claude/skills/embed/scripts/test_embed_drafts_offline.py +.venv/bin/python .claude/skills/embed-knowledge/scripts/test_embed_drafts_offline.py +.venv/bin/python -m py_compile .claude/skills/embed-knowledge/scripts/embed_drafts.py \ + .claude/skills/embed-knowledge/scripts/test_embed_drafts_offline.py ``` 离线测试只使用 fake connection 检查并发顺序、SQL 条件和 owner 反例,不连接真实数据库,不调用 embedding 或 reset。 diff --git a/.claude/skills/embed/scripts/embed_drafts.py b/.claude/skills/embed-knowledge/scripts/embed_drafts.py similarity index 99% rename from .claude/skills/embed/scripts/embed_drafts.py rename to .claude/skills/embed-knowledge/scripts/embed_drafts.py index b74a21f..46eb90e 100644 --- a/.claude/skills/embed/scripts/embed_drafts.py +++ b/.claude/skills/embed-knowledge/scripts/embed_drafts.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""embed skill:知识行批量嵌入(New-API / Qwen3-Embedding-8B / 1024 维)。 +"""embed-knowledge Skill:知识行批量嵌入(New-API / Qwen3-Embedding-8B / 1024 维)。 合同见同 skill SKILL.md;通道事实见 db/连接信息.md。失败原样报错不静默。 """ diff --git a/.claude/skills/embed/scripts/test_embed_drafts_offline.py b/.claude/skills/embed-knowledge/scripts/test_embed_drafts_offline.py similarity index 100% rename from .claude/skills/embed/scripts/test_embed_drafts_offline.py rename to .claude/skills/embed-knowledge/scripts/test_embed_drafts_offline.py diff --git a/.claude/skills/replay-eval/SKILL.md b/.claude/skills/evaluate-frozen-replay/SKILL.md similarity index 53% rename from .claude/skills/replay-eval/SKILL.md rename to .claude/skills/evaluate-frozen-replay/SKILL.md index a94aecd..90b1fab 100644 --- a/.claude/skills/replay-eval/SKILL.md +++ b/.claude/skills/evaluate-frozen-replay/SKILL.md @@ -1,16 +1,16 @@ --- -name: replay-eval -description: 回放评测的冻结与结果边界合同。把参考作品冻结到 as_of 章号,生成可审计的输入清单,并在生成/评分前阻断未来信息、未授权来源和全文留存。 +name: evaluate-frozen-replay +description: 将参考作品冻结到 as_of 章号,编排细纲或正文的隔离回放,并生成可审计的逐样本结果。需要验证知识或上下文方案时使用;阻断未来信息、未授权来源和不可接受的候选流入生产。 disable-model-invocation: true --- # 回放评测 -本 skill 定义评测编排边界,不替代统一读取器,也不写正式规划或知识。Writer/冻结合同以 [父仓专题-03](../../../../design-docs/专题-03-AI编排上下文与质量评测实现规范.md) 为唯一 owner,Gate 裁决以 [父仓专题-04](../../../../design-docs/专题-04-生成质量门控与创作健康度设计方案.md) 为准,adapter 隔离以 [父仓专题-05](../../../../design-docs/专题-05-AI统一交互协议与外部AgentAdapter设计.md) 为准。Gate A 具体执行参数只认 `configs/writer-gate-a-deep-space-v1.json`。 +本 Skill 定义评测编排边界,不替代统一读取器,也不写正式规划或知识。Writer/冻结合同以 [父仓专题-03](../../../../design-docs/专题-03-AI编排上下文与质量评测实现规范.md) 为唯一 owner,Gate 裁决以 [父仓专题-04](../../../../design-docs/专题-04-生成质量门控与创作健康度设计方案.md) 为准,adapter 隔离以 [父仓专题-05](../../../../design-docs/专题-05-AI统一交互协议与外部AgentAdapter设计.md) 为准。Gate A 具体执行参数只认 `configs/writer-gate-a-deep-space-v1.json`。 -本 skill 是回放**编排层**,只做冻结、授权、防泄漏审计、回放编排与探针刷新:评分/裁决(rubric/gate,含 `writer_gate.py`/`writer_rubric.py`/`gate_input_builder.py`/`fine_outline_rubric.py`)由 **quality-gate** 提供;受控 Claude CLI 调用与 CAS/raw vault 底座(`claude_runtime.py`/`file_cas.py`/`raw_vault.py`)由 **runtime** 提供;候选检测由 **detect** 提供;冻结快照/授权校验/泄漏审计/只读装载能力(`build_snapshot.py`/`check_snapshot.py`/`audit_leakage.py`/`load_reference_work.py`)由 **snapshot** 提供。依赖箭头只向下,本 skill 不自带评分/裁决、运行时底座或冻结快照能力。 +本 Skill 是回放**编排层**,只做冻结、授权、防泄漏审计、回放编排与探针刷新:评分/裁决由 `score-content-quality` 提供;Claude CLI 调用由 `execute-claude-task` 提供;CAS/raw 证据由 `record-run-evidence` 提供;候选检测由 `check-content-consistency` 提供;冻结快照与只读装载由 `freeze-context` 提供。依赖箭头只向下,本 Skill 不自带这些实现。 -真实参考作品配置由 snapshot skill 的 `scripts/load_reference_work.py` 在 `REPEATABLE READ READ ONLY` 事务中从实验库组装;它只取作品元数据、窗级大纲、章级细纲摘要和预注册卡 ID 对应的候选卡历史。候选卡必须标记为 `eval_draft`,不能当作生产知识检索结果。 +真实参考作品配置由 `freeze-context/scripts/load_reference_work.py` 在 `REPEATABLE READ READ ONLY` 事务中从实验库组装;它只取作品元数据、窗级大纲、章级细纲摘要和预注册卡 ID 对应的候选卡历史。候选卡必须标记为 `eval_draft`,不能当作生产知识检索结果。 来源版本只认原文件证明链:`example_reference_work.source_file` 必须唯一匹配成功 import task 和未软删 knowledge document,三方文件名一致,文档 `file_hash` 为 64 位小写 SHA-256,且 import `command_id` 以该 hash 前 16 位开头。`sourceHash=sha256:`,`sourceVersion=raw-file-v1:sha256:`;作品 revision 和导入章数不得改变原文件版本。 @@ -42,18 +42,20 @@ disable-model-invocation: true ## 产物边界 -- 原始 prompt、response、候选、标准事实摘要和完整输入只能留在仓库外受控 raw vault;必须受显式保留授权、租约和清理回执约束。 +- 原始 prompt、response、候选、标准事实摘要和完整输入只能留在仓库外受控 raw vault;必须受显式保留授权、租约和迁移回执约束。正式评测每轮结束迁移到受控归档,删除必须另行明确授权。 - 最终报告只允许评分、摘要、章节定位、失败类别和哈希。 - 禁止把完整 Prompt/Response、供应商原始响应、原书正文、完整目标细纲、token、密钥或未脱敏授权资料写入 Git 仓库或安全摘要。 ## 编排入口 - `scripts/run_replay.py --mode dry_run`:只执行授权、来源、冻结和三臂 manifest 预检,不调用模型;这是首个机制 smoke 入口。 -- snapshot skill 的 `scripts/load_reference_work.py`:从 PostgreSQL 只读事务组装仓库外临时配置;读取 `example_reference_authorization_snapshot` 当前原文件版本的最新快照,组装 authorization 外层与 snapshot。缺授权记录仍生成可审计配置,但送入 `run_replay` 后必须保持 `blocked_authorization`。 +- `freeze-context/scripts/load_reference_work.py`:从 PostgreSQL 只读事务组装仓库外临时配置;读取 `example_reference_authorization_snapshot` 当前原文件版本的最新快照,组装 authorization 外层与 snapshot。缺授权记录仍生成可审计配置,但送入 `run_replay` 后必须保持 `blocked_authorization`。 - `scripts/run_replay.py --mode execute`:在全部前置门通过后,依次执行三臂 planner、整组 schema、逐臂盲 detector、两个独立盲 judge、rubric 校验、稳定性门和去盲汇总;`--output-dir` 必须位于仓库外的临时目录。 -- detector 输入输出均为 JSON;输入只有匿名候选 ID、候选和公共冻结到 `as_of` 的规划上下文,不含 arm 名、`cardInjection`、`cardManifest`、任何臂特有卡内容、目标章 proxy 或其他评委结果。卡注入合法性只由确定性预检负责。任一 `high` 严重度发现整组标记 `detector_blocked`,judge 调用数必须为 0;报告不合约时标记 `detector_invalid`。 +- detector 输入输出均为 JSON;输入只有匿名候选 ID、候选和公共冻结到 `as_of` 的规划上下文,不含 arm 名、`cardInjection`、`cardManifest`、任何臂特有卡内容、目标章 proxy 或其他评委结果。卡注入合法性只由确定性预检负责。报告不合约属于系统失败,须在 judge 前失败关闭;合同合法的 `failed` / `needs_evidence` 属于候选质量信号,三臂均须保留候选与报告并继续盲评和 Gate,Gate A 只由 C 臂高严重度残留与硬约束覆盖率裁决质量失败。 +- 合法的非 `passed` 报告在安全 manifest 与 CAS 中只保存按臂分组的 `semantic-diagnostic-v1`:固定结果枚举、固定原因/区段枚举、调用次数和有界阻断计数。完整报告只留受控 raw 和编排器内存中的 Gate builder 输入;安全输出不得保存错误消息、正文、引文、事实文本、补证查询/原因、任何检测项 ID/字段路径、纠错草稿或模型原始字段。检测器不合约时同样只保存固定闭集诊断并失败关闭。 +- 任一样本发生系统失败、检测器不合约、盲评无效/不稳定、预算或 raw/CAS 失败后,整轮已无法构建完整 Gate 输入,必须立即停止后续样本并进入统一迁移;不得继续调用模型消耗预算,也不得因失败删除本轮诊断 raw。 - 两个 judge 使用不同 `judgeId` 和独立无会话进程。第二个 judge 的匿名候选顺序必须与第一个完全相反;任何 rubric 不合约标记 `judge_invalid`,任一同维差值大于 `0.5` 标记 `judge_unstable`,两者都不得标记 `completed`。 -- 只有三臂 schema、detector、双 judge rubric 和稳定性门全部通过,才去盲生成逐维 `B-A` / `C-A` 差值矩阵并标记 `completed`。`--detector-bin`、`--judge-primary-bin`、`--judge-secondary-bin` 可分别指定本地 runner;未指定时复用 `--planner-bin`,测试只能使用 fake binary。 +- 只有三臂 schema 合法、detector 均产出合同合法报告、双 judge rubric 与稳定性门通过,才去盲生成逐维 `B-A` / `C-A` 差值矩阵并标记 `completed`;detector 报告合同合法不等于其质量终态必须为 `passed`。`--detector-bin`、`--judge-primary-bin`、`--judge-secondary-bin` 可分别指定本地 runner;未指定时复用 `--planner-bin`,测试只能使用 fake binary。 - planner、detector、judge 子进程统一受 `--timeout-seconds` 限制,默认 300 秒;任一超时分别落盘 `planner_timeout`、`detector_timeout`、`judge_timeout`,不得继续进入后续阶段或标记 `completed`。 - `scripts/write_report.py`:从 `run_result.json` 生成独立严格 schema 的安全摘要,只接受受限标识符、枚举、数字、短安全摘要和 SHA-256;不会读取候选正文,也不会把候选路径以外的原始响应写入报告。 @@ -69,11 +71,11 @@ disable-model-invocation: true 三臂都必须构造并校验完整 `WriterContext v1`,且固定 `mode=diagnostic_only`、`purpose=evaluation`、`acceptanceEligible=false`。adapter 随后投影 `WriterCreativeInput v2`;writer 模型只看到创作投影,不看到运行身份、manifest、hash、实验臂或验收状态: -- A:`evidenceStrategy=historical_prose_only`,保留连续四章脱敏基线,不放 `indexHints`。 -- B:`evidenceStrategy=card_index_only`,只放 `retrievalResult.indexHints`,`proseEvidence` 为空;这是合同内可审计的诊断基线例外,不是生产旁路。 -- C:`evidenceStrategy=card_index_plus_prose`,保留连续四章脱敏基线,并放冻结 `indexHints` 与配置中的补充原文证据。 +- A:`evidenceStrategy=historical_prose_only`,保留连续四章脱敏基线,不放 `indexHints`,`patternReferences` 恒空。 +- B:`evidenceStrategy=card_index_only`,只放 `retrievalResult.indexHints` 与冻结公共 `patternReferences`,`proseEvidence` 为空;这是合同内可审计的诊断基线例外,不是生产旁路。 +- C:`evidenceStrategy=card_index_plus_prose`,保留连续四章脱敏基线,并放冻结 `indexHints`、与 B 相同的公共 `patternReferences` 及配置中的补充原文证据。 -A/C 单变量回执在 `WriterCreativeInput v2` 上比较。正式 Gate A 的 `allowedDifferencePaths` 必须非空,A/C `contextSha256` 必须不同,且所有路径只能位于 `factConstraints` 或 `proseExcerpts`;`indexHints` 等模型不可见字段的差异不能使样本合格。 +A/C 单变量回执在 `WriterCreativeInput v2` 上比较。正式 Gate A 的 `allowedDifferencePaths` 必须非空,A/C `contextSha256` 必须不同,且所有路径只能位于 `factConstraints`、`proseExcerpts` 或预注册的 `patternReferences`;`indexHints` 等模型不可见字段的差异不能使样本合格。 `indexHints` 每项只能包含 `cardId/name/type/content/sourceId/sourceVersion/asOf`,只能用于 `diagnostic_only` 的 `evaluation/diagnostic`,不得进入 writer 或 semantic detector 的模型输入。可信组装器只能把经来源回读确认的内容转成 `factEvidence` / `factConstraints`;eval_draft 的 `content` 不能直接成为 `supported/pass` 依据。所有 `asOf` 和来源章号不得超过样本冻结点。 @@ -83,25 +85,39 @@ A/C 单变量回执在 `WriterCreativeInput v2` 上比较。正式 Gate A 的 `a `newCharacterRatio` 定义为:具名 `requiredCharacters` 中,在 `asOfChapter` 前无记录的角色比例。真实 loader 必须在同一 `REPEATABLE READ READ ONLY` 事务内直接扫描冻结历史 Canonical 正文,按名字返回首次命中章和命中章集合,再重算 `knownBeforeAsOf/absentBeforeAsOf/newCharacterRatio`;卡内容不得参与这个判定。泛称角色不参与猜测;例如第 544 章的“内应”无法确定具体身份,必须记录 `newCharacterRatio=null` 和 `newCharacterRatioStatus=unresolved_generic_role`,不得默认成 0。 -盲评使用独立的 `writer-blind-input-v3` 内容边界。可信 adapter 校验匿名候选 hash、共同细纲和 oracleTruthPack 后,只向 judge 模型投影匿名候选 ID+正文、细纲硬约束/可调节 beat/声明新事实、oracle 断言和 rubric。模型输入不得包含 `indexHints`、卡 manifest、`evidenceStrategy`、真实 A/B/C、各臂 WriterContext、raw 路径、reviewer 身份、任何 hash 或其他评委结果。模型只返回 `blind-judge-draft-v3` 的评分、理由、候选引文、受控证据引用、维度排序和 verdict;candidate/input/oracle/report/model-receipt hash 与字符 offset 由 adapter 绑定。映射只保留在编排器内存中,评分完成后才去盲。 +盲评使用独立的 `writer-blind-input-v4` 内容边界。输入必须绑定预注册 `scenario` 和 `writer-replay-rubric-v2`;三位评委的场景或策略版本不一致时在模型调用前失败关闭。可信 adapter 校验匿名候选 hash、共同细纲和 oracleTruthPack 后,只向 judge 模型投影匿名候选 ID+正文、细纲硬约束/可调节 beat/声明新事实、oracle 断言,以及四个通用指标与当前场景评分策略的定义、判据和分数锚点。模型输入不得包含 `indexHints`、卡 manifest、`evidenceStrategy`、真实 A/B/C、各臂 WriterContext、raw 路径、reviewer 身份、任何 hash 或其他评委结果。adapter 可附带从当前候选确定性切出的逐字引文提示,以及当前候选/维度/断言/约束的精确顺序和期望数量;这些提示只降低格式误差,不替代本地完整集、引文和证据绑定校验。模型只返回 `blind-judge-draft-v3` 的评分、理由、候选引文、受控证据引用、维度排序和 verdict;candidate/input/oracle/report/model-receipt hash 与字符 offset 由 adapter 绑定。映射只保留在编排器内存中,评分完成后才去盲。盲评失败的安全摘要只保存错误码、评委/尝试/调用次数、数组计数和缺失/重复/越界数量,不保存正文、引文、理由或原始字段值。通用维度可跨章节聚合,兼容 ID `narrative_tension` 只按同一场景类型聚合,禁止跨场景平均后抵消单一类型退化。 正文 dry-run 命令: ```bash -PYTHONPATH=.claude/skills/replay-eval/scripts .venv/bin/python -m run_writer_replay \ - --config .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ +PYTHONPATH=.claude/skills/evaluate-frozen-replay/scripts .venv/bin/python -m run_writer_replay \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ --dry-run ``` -dry-run 只输出计划、manifest 和上下文摘要,不调用 writer、semantic detector 或 judge,不生成候选正文,也不得声称真实回放完成。`--execute` 必须在创建 runner、raw vault 或调用模型前校验预算、授权、冻结快照、三角色 profile、schema、prompt、内容模式和调用计划;任一不一致都失败关闭。 +dry-run 只输出计划、manifest 和上下文摘要,不调用 writer、semantic detector 或 judge,不生成候选正文,也不得声称真实回放完成。`--execute` 必须在创建 runner、raw vault 或调用模型前校验预算、授权、冻结快照、三角色 profile、schema、prompt、内容模式、调用计划和显式 runtime 认证;任一不一致都失败关闭。默认真实 runtime 只接受最小环境白名单中的 API key、OAuth token 或绑定安全 base URL 的 auth token,不继承全局 Claude 配置目录;认证缺失时返回 `blocked_runtime_authentication / RUNTIME_AUTHENTICATION_REQUIRED`,不得消耗预算槽位或建立 raw vault。 -预算合同分为不可变的 `plannedCalls` 和独立安全上限 `maxCalls`。Gate A 当前三角色各预注册 15 次、各 `maxCalls=150`、单次 cap `$5`、总预算 `$300`;启动预留只按 `plannedCalls * maxBudgetUsdPerCall`,即 `$225`,不得按 150 次计算。每次调用前账本同时检查角色计划槽位、`maxCalls`、累计实际成本和未执行计划的最坏预留;调用后只能使用可信 `ExecutionReceipt.totalCostUsd` 结算。缺回执成本、重复/错序结算、单次 cap 或总预算越界均 fail closed,未触发的第三评计划必须保留在 `remainingPlannedCalls`。 +正式 execute 还必须显式传 `--raw-archive-dir <仓外绝对目录>`;缺失、相对路径、位于本轮输出目录内或与临时 vault 跨文件系统时,均在建立 vault 和调用模型前失败关闭。运行结束以 `rawDisposition.status=migrated` 和 `raw-vault-migration-receipt-v1` 证明产物已迁移,不再以删除后的 `closed` 作为完成条件。 + +离线完整链冒烟由 `scripts/test_run_writer_replay.py` 的单样本三臂 production fake 用例承担:它真实经过 writer 投影、机械门、semantic detector、双评委、raw vault、CAS 和 GateInputBuilder,但所有模型均为本地确定性 fake,不产生费用、不得作为 Gate 样本结果。真实一次调用能力只由 `refresh_runtime_probe.py` 的合成 writer 探针验证;它不代表 detector/judge 或五样本 Gate 已通过。 + +预算合同分为不可变的 `plannedCalls` 和独立安全上限 `maxCalls`。Gate A 当前预注册 writer 60 次(15 次基础写作 + 最多 45 次篇幅修订)、semantic detector 24 次(15 次基础检测 + 9 次纠错/API 重试余量)、blind judge 45 次(最多三位评委,每位基础调用后最多两个格式纠错/API 重试槽位);各 `maxCalls=150`、单次 cap `$5`、总预算 `$2250`。启动预留只按 `plannedCalls * maxBudgetUsdPerCall`,即 `$645`;`$2250` 是三角色各 150 次安全容量对应的有限执行上限,不是预计消费。本预算授权不等于正式 `--execute` 授权。每次调用前账本同时检查角色计划槽位、`maxCalls`、累计实际成本和未执行计划的最坏预留;调用后只能使用可信 `ExecutionReceipt.totalCostUsd` 结算。缺回执成本、重复/错序结算、单次 cap 或总预算越界均 fail closed,未触发的修订、纠错与第三评计划必须保留在 `remainingPlannedCalls`。 + +## 稳定运行纪律 + +正式 `--execute` 会发起长时间、多角色的模型调用,运行方式本身是合同的一部分: + +- 正式 execute 必须由**持续等待的前台受控会话**运行,全程阻塞到进程自己退出并读到退出码;**禁止用 `run_in_background` 之类后台启动**。后台启动会被外部任务管理器在数分钟后按 `status=killed` 收掉,Python 来不及进入 `finally`,留下 open raw lease、无 manifest、无费用回执。 +- **禁止用不带 `pipefail` 的 `python ... | tail` 捕获退出码**:管道退出码默认取最后一个命令(`tail`)的,Python 的非零码会被静默吞掉,失败关闭看起来像成功。要么开 `set -o pipefail`,要么直接读 Python 进程退出码。 +- 进程内对**外部终止只承诺 SIGTERM / SIGINT 可收口**:`execute-claude-task` 会把两者转成受控异常,编排层再通过 `record-run-evidence` 迁移 raw、写安全 manifest、把在途且无回执的调用记为 `costUnknown=true` / `failureReason=EXECUTION_COST_UNKNOWN` 并禁止后续调用,最后以非零退出。**SIGKILL 无法捕获**,只能事后靠 raw vault 的迁移恢复与 CAS 扫描收敛残留。 +- 受控终止进入最终迁移后,SIGTERM/SIGINT handler 必须保持 defer/ignore,覆盖 raw migration、CAS 收口和 manifest 原子写入;manifest 发布完成后才恢复调用方原 handler。 +- 单个 raw lease 覆盖整轮串行回放。启动时只按三角色中最长的单次 `timeoutSeconds + cleanupMargin` 校验剩余租期;每次模型调用前再按当前角色的同一公式复检,复检必须发生在消耗预算槽位和外发调用之前。`plannedCalls * timeoutSeconds` 是预算/容量安全上界,不是运行时预测,不能因其理论总和超过 24 小时而直接阻断;租期不足时必须在下一笔调用前安全停止、迁移已有 raw 并保留未执行计划。 ## 正文 Gate 输入 -Gate 裁决器 `writer_gate.py`(评分/裁决模块归属 quality-gate skill,本 skill 只做回放编排并向其提交逐样本脱敏结果)只信任 `gate-input.json.samples[]` 的逐样本脱敏结果。有效样本数、作品数、场景覆盖、C 臂硬门统计、C-A 五维增量、退化比例和六类混淆项全部由判定器内部计算;顶层传入的同名聚合值和聚合 `confounds` 不参与裁决。六类混淆项是:假阴、假阳、泄露、评委不稳定、新角色无卡、场景选择偏差。前五类逐样本汇总;场景选择偏差是集合级混淆项,当评测集 `workId` 去重少于两个(单作品)或未覆盖全部预注册场景类型(只选部分卡友好场景)时产出 finding,标记样本对「卡是否有效」不具代表性、不得据此得出普适结论;它只作报告标注,不改写任何 Gate 终态。 +Gate 裁决器 `writer_gate.py`(归属 `score-content-quality`,本 Skill 只提交逐样本脱敏结果)只信任 `gate-input.json.samples[]` 的逐样本脱敏结果。有效样本数、作品数、场景覆盖、C 臂硬门统计、C-A 五维增量、退化比例和六类混淆项全部由判定器内部计算;顶层传入的同名聚合值和聚合 `confounds` 不参与裁决。六类混淆项是:假阴、假阳、泄露、评委不稳定、新角色无卡、场景选择偏差。前五类逐样本汇总;场景选择偏差是集合级混淆项,当评测集 `workId` 去重少于两个(单作品)或未覆盖全部预注册场景类型(只选部分卡友好场景)时产出 finding,标记样本对「卡是否有效」不具代表性、不得据此得出普适结论;它只作报告标注,不改写任何 Gate 终态。 -预算、授权或执行合同不合法时不能生成 Gate 输入。Gate A/B 的终态只能由 quality-gate 的 `writer_gate.py` 按父仓专题-04 的唯一顺序产生;本 skill 只做回放编排,不产生也不改写终态,人工不得改写。 +预算、授权或执行合同不合法时不能生成 Gate 输入。Gate A/B 的终态只能由 `score-content-quality` 的 `writer_gate.py` 按父仓专题-04 的唯一顺序产生;本 Skill 只做回放编排,不产生也不改写终态,人工不得改写。 ## 运行探针刷新工具 @@ -116,8 +132,8 @@ Gate 裁决器 `writer_gate.py`(评分/裁决模块归属 quality-gate skill 离线用法(冒烟,不调模型): ```bash -.venv/bin/python .claude/skills/replay-eval/scripts/refresh_runtime_probe.py \ - --config .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/refresh_runtime_probe.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ --output /tmp/writer-gate-a-deep-space-v1.probe-refreshed.json \ --dry-run ``` @@ -125,8 +141,8 @@ Gate 裁决器 `writer_gate.py`(评分/裁决模块归属 quality-gate skill 真实刷新(经授权后由主代理执行,会发起一次真实 Claude 调用,受 writer profile 单次 cap $5 与冻结 deadline 约束): ```bash -.venv/bin/python .claude/skills/replay-eval/scripts/refresh_runtime_probe.py \ - --config .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/refresh_runtime_probe.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ --output /tmp/writer-gate-a-deep-space-v1.probe-refreshed.json ``` diff --git a/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-card-selectors-v1.json b/.claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-card-selectors-v1.json similarity index 100% rename from .claude/skills/replay-eval/configs/writer-gate-a-deep-space-card-selectors-v1.json rename to .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-card-selectors-v1.json diff --git a/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json b/.claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json similarity index 94% rename from .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json rename to .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json index 9a0985b..0a2e466 100644 --- a/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json +++ b/.claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json @@ -49,7 +49,7 @@ "oracleInputProvenance": "oracle_reference_scaffold", "maxContextChars": 140000, "modelVersion": "claude-opus-4-8[1m]", - "adapterVersion": "writer-runtime-v1|claude-cli-2.1.211|binary-sha256-5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", + "adapterVersion": "writer-runtime-v1|claude-cli-2.1.231|binary-sha256-ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", "sampling": { "temperature": "unsupported", "topP": "unsupported", @@ -62,8 +62,8 @@ "writer": { "profileVersion": "writer-gate-a-claude-opus-v2", "claudeExecutablePath": "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude", - "claudeExecutableSha256": "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", - "claudeCliVersion": "2.1.211", + "claudeExecutableSha256": "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", + "claudeCliVersion": "2.1.231", "modelAlias": "opus", "resolvedModelId": "claude-opus-4-8[1m]", "effort": "high", @@ -73,8 +73,8 @@ "jsonSchemaId": "writer-draft-v2", "jsonSchemaSha256": "sha256:a1fc5efbcd7aee11082b547eb4e156fe27abe0d669991d5825e3e69901278709", "systemPromptId": "writer-gate-a-system-v2", - "systemPrompt": "你是这部书的执笔写手。根据本章细纲、叙事状态、事实约束和文风样本,把这一章写成鲜活连贯的正文。\n\n## 写作纪律\n1. 细纲的硬事件、结果方向、伏笔动作、章末钩子、必须出场实体是不可删除或反转的硬骨架;只有可调整节拍允许重排。\n2. 硬骨架是「要发生什么」,正文写「怎么发生」:每个节拍都展开成有动作、对话、感官细节的场景,绝不把细纲的描述句原样抄进正文。「XX三人被困出口,决定撤至通道,等XX送机甲」这类概述句,必须戏剧化成人物在做什么、说什么、感受到什么。\n3. 具体压倒抽象:名词给实物,动词给动作;情绪用行为与细节展示,不许直接宣告。\n4. 每场戏三件套:这场要什么、被什么挡住、落点在哪。\n5. 锚点(必须角色、章末钩子、硬事件)自然长在场景与对话里;章末钩子在结尾自然收出悬念,不是末尾补一句概述。\n6. 文风样本只取人物声音、动作习惯、叙事质感;事实约束只约束真伪。角色绝不说出不该知道的事。\n7. 篇幅落在给定区间内——用有戏剧张力的场景把正文写够,每个场景都有三件套;既不堆形容词注水,也不把该有的场景缩掉。不写未声明的新地名、能力、组织、身份、战绩或关系。", - "systemPromptSha256": "sha256:c66689ba23072da8be555acfe3d9260765dd6128299d058db4ae6370a9e68e2a", + "systemPrompt": "你是这部书的执笔写手。根据本章细纲、叙事状态、事实约束和文风样本,把这一章写成鲜活连贯的正文。\n\n## 写作纪律\n1. 细纲的硬事件、结果方向、伏笔动作、章末钩子、必须出场实体是不可删除或反转的硬骨架;只有可调整节拍允许重排。\n2. 硬骨架是「要发生什么」,正文写「怎么发生」:每个节拍都展开成有动作、对话、感官细节的场景,绝不把细纲的描述句原样抄进正文。「XX三人被困出口,决定撤至通道,等XX送机甲」这类概述句,必须戏剧化成人物在做什么、说什么、感受到什么。\n3. 具体压倒抽象:名词给实物,动词给动作;情绪用行为与细节展示,不许直接宣告。\n4. 每场戏三件套:这场要什么、被什么挡住、落点在哪。\n5. 锚点(必须角色、章末钩子、硬事件)自然长在场景与对话里;章末钩子在结尾自然收出悬念,不是末尾补一句概述。\n6. 文风样本只取人物声音、动作习惯、叙事质感;事实约束只约束真伪。角色绝不说出不该知道的事。\n7. 篇幅落在给定区间内——用有戏剧张力的场景把正文写够,每个场景都有三件套;既不堆形容词注水,也不把该有的场景缩掉。不写未声明的新地名、能力、组织、身份、战绩或关系。\n8. 连贯一致:不得与历史原文(近几章 proseEvidence)已确立的事实、人物状态、时空关系相冲突;场景转换、人物位移、机甲或能力的切换要交代因果过渡,不能留断裂(如人物上一刻在 A 机甲、下一刻声音从 B 机甲传出,必须交代怎么过去的);新出现的人物、舰船、能力、地点要有铺垫或一句话交代来历;句子主语与指代要清楚,避免让读者误解谁做了什么。", + "systemPromptSha256": "sha256:7d5b7de5378b4c9c376fce6115e2eb22797c28cb3db12f6b954e0aeaa487d17c", "normalTerminalReasons": [ "completed" ], @@ -86,8 +86,8 @@ "semantic_detector": { "profileVersion": "semantic-detector-gate-a-claude-opus-v3", "claudeExecutablePath": "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude", - "claudeExecutableSha256": "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", - "claudeCliVersion": "2.1.211", + "claudeExecutableSha256": "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", + "claudeCliVersion": "2.1.231", "modelAlias": "opus", "resolvedModelId": "claude-opus-4-8[1m]", "effort": "high", @@ -97,8 +97,8 @@ "jsonSchemaId": "semantic-detection-draft-v3", "jsonSchemaSha256": "sha256:0432d705c0a78a057273f5bcda128e5d32161054a36931d2b7eec21d7289be15", "systemPromptId": "semantic-detector-gate-a-system-v3", - "systemPrompt": "你是这部书的检测员。根据当前候选正文、冻结细纲与硬约束、事实证据和历史原文证据,对这一个候选做一次语义核查。\n\n## 核查范围\n1. 细纲的硬事件、结果方向、出场实体、伏笔动作与章末钩子,在正文里是否语义成立。\n2. 冻结事实、角色知情范围、人物行为逻辑、能力代价、地点规则、物品边界与叙事状态,正文是否与它们冲突。\n3. 正文是否引入了需要登记的新设定;是否存在必须补充证据才能判断的缺口。\n4. 谜底、真相和未来信息只能用来防止提前泄露,不能写进正文可见内容。\n\n## 核查纪律\n- 证据不足必须标 unknown 并说明缺口原因,不得猜成通过。\n- 每条结论都要引一句正文里可定位的原话作证据;引文必须真实出自正文。\n- 只依据给定的候选与证据判断,不把主观观感伪装成事实结论。\n- 一次只核查这一个候选,不继承其它会话,不负责改写正文。", - "systemPromptSha256": "sha256:0f4f4d69b22645be73034f3618db0a9a300ec019a93d8c72aaf7a6b4d44b122b", + "systemPrompt": "你是这部书的检测员。根据当前候选正文、冻结细纲与硬约束、事实证据和历史原文证据,对这一个候选做一次语义核查。\n\n## 核查范围\n1. 细纲的硬事件、结果方向、出场实体、伏笔动作与章末钩子,在正文里是否语义成立。\n2. 冻结事实、角色知情范围、人物行为逻辑、能力代价、地点规则、物品边界与叙事状态,正文是否与它们冲突。\n3. 正文是否引入了需要登记的新设定;是否存在必须补充证据才能判断的缺口。\n4. 谜底、真相和未来信息只能用来防止提前泄露,不能写进正文可见内容。\n\n## 引文铁律(最重要)\n- candidateQuote 必须是候选正文里**逐字相邻、真实存在**的一段原话。建议引**短句**(10–40 字,单句或同一段内),**绝不要把不相邻的几句拼接成一条引文**——长引用极易把中间隔了内容的句子缝在一起,导致校验失败。\n- 引用前先确认这几个字在正文里确实一字不差、紧挨着出现;有一点不确定就不引,改用 unknown 或证据缺口。**绝不允许编造正文里没有的话当引文。**\n- 引文选有辨识度的片段,不要引「。」「他说」这类到处出现的短词。\n\n## 输出各项含义\n- claims:对正文事实性陈述的核查,coverageState 取 supported(有据)/declared_new(新声明)/unknown(证据不足)/conflict(与既有事实冲突)。\n- findings:发现的问题,按严重度,如提前泄露、逻辑冲突、知情越界。\n- assertionVerdicts:对输入的 oracle 断言逐条裁决。\n- hardConstraintVerdicts:对细纲硬约束逐条裁决是否满足。\n- newSettingCandidates:正文引入、需要登记的新设定。\n- evidenceGaps:证据不足、需补检索才能判断的缺口。\n\n## 新内容判定(很重要)\n- 细纲已声明的内容——hardConstraints 里的硬事件、章末钩子、必须出场角色,以及 declaredNewFacts——是本章**应当揭示**的内容。正文落实它们时,识别为 declared_new(本章声明的新内容),**不要标成证据缺口或冲突**。\n- 本章新揭示的设定/能力/符号(如新机甲、新程序、新信号),只要是细纲要求的揭示,就是 declared_new,不因「前文没铺垫」而判为证据缺口。\n- 只有细纲**未声明**、又无历史原文或事实证据支撑、且无法由正文自洽解释的内容,才是证据缺口。\n\n## 核查纪律\n- 证据不足必须标 unknown 并说明缺口原因,不得猜成通过。\n- 只依据给定的候选与证据判断,不把主观观感伪装成事实结论。\n- 一次只核查这一个候选,不继承其它会话,不负责改写正文。\n\n## 纠错\n- 如果输入含 correction 字段(previousDraft 是你上一轮的产出,error 是它不合格的原因),请针对 error 修正后重新输出**完整**检测报告。最常见错误是 candidateQuote 不在候选正文里——请改用候选正文中逐字真实存在、紧挨着的短句作引文。", + "systemPromptSha256": "sha256:dbb146e325a112d5676f50e09af73e12f03a659c0824b27b2956f8c6ae64c9ec", "normalTerminalReasons": [ "completed" ], @@ -110,8 +110,8 @@ "blind_judge": { "profileVersion": "blind-judge-gate-a-claude-opus-v3", "claudeExecutablePath": "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude", - "claudeExecutableSha256": "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", - "claudeCliVersion": "2.1.211", + "claudeExecutableSha256": "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", + "claudeCliVersion": "2.1.231", "modelAlias": "opus", "resolvedModelId": "claude-opus-4-8[1m]", "effort": "high", @@ -121,8 +121,8 @@ "jsonSchemaId": "blind-judge-draft-v3", "jsonSchemaSha256": "sha256:c2daa6ca501dd570e2936519f32b37af47d1cd23c50f1937336fc8ad3000889b", "systemPromptId": "blind-judge-gate-a-system-v3", - "systemPrompt": "你是这部书的质量评委。对给定的匿名候选做一次独立评分:候选正文、所有候选共同的细纲、oracle 断言和评分标准(rubric)都在输入里。\n\n## 独立性(命根子)\n- 不因为\"需要收敛\"而调整严格度,不猜测别的评委会打几分。\n- 只用当前输入里的事实评分,不使用输入之外的事实。\n\n## 评分纪律\n- 逐候选、逐维给分(0–10,0.5 步长),每一维都给具体理由。\n- 每条判断引一句候选里可定位的原话作证据;引文必须真实出自候选。\n- 给出每个维度上匿名候选的完整排序,以及每个候选对全部 oracle 断言和硬约束的裁决;不省略、不增加对象。\n- 证据只来自候选、细纲、oracle 断言或你的推理,引用对象必须来自当前输入。", - "systemPromptSha256": "sha256:1d107f60bc670dc5bb3144bc04884eb4061f07331fce03154294e3831b86de69", + "systemPrompt": "你是这部书的质量评委。对给定的匿名候选做一次独立评分:候选正文、所有候选共同的细纲、oracle 断言和评分标准(rubric)都在输入里。\n\n## 独立性(命根子)\n- 不因为\"需要收敛\"而调整严格度,不猜测别的评委会打几分。\n- 只用当前输入里的事实评分,不使用输入之外的事实。\n\n## 引文铁律(最重要)\n- 每条判断的 candidateQuote 必须是候选正文里**逐字真实出现**的原话片段。引用前先确认这句话确实在候选里一字不差地存在;有一点不确定就不引。**绝不允许编造一句候选里没有的话当引文。**\n- 引文选有辨识度的片段,不要引「。」「他说」这类到处出现的短词。\n\n## 输出各项含义\n- candidateScores:对每个匿名候选、按 rubric 的每一维给分(0–10,0.5 步长)+ 具体 reason + 支撑该分的 candidateQuote + evidenceRefs。\n- evidenceRefs 的证据类型只允许 candidate(候选正文)/fine_outline(细纲)/oracle_assertion(oracle 断言)/judge_inference(你的推理),引用对象必须来自当前输入。\n- 每个维度给出匿名候选的完整排序;每个候选对全部 oracle 断言和硬约束逐条给 verdict;不省略、不增加对象。\n\n## 评分纪律\n- 逐候选、逐维给分,每一维都给具体理由。\n- 证据只来自候选、细纲、oracle 断言或你的推理。\n\n## 纠错\n- 如果输入含 correction 字段(previousDraft 是你上一轮的完整产出,error 是它不合格的原因),请只针对 error 修正后重新输出完整评审草稿。最常见错误是 candidateQuote 不在对应候选正文中;必须改用该候选正文里逐字相邻、真实存在的短句,不能改写或拼接。", + "systemPromptSha256": "sha256:f12d3edcf822419bdabf0f04ff139d8f7e3e5721b31456dc6b77a8d62c1a074d", "normalTerminalReasons": [ "completed" ], @@ -135,42 +135,42 @@ "executionAuthorization": { "runtimeProbe": { "status": "successful", - "checkedAt": "2026-07-25T16:08:06+00:00", + "checkedAt": "2026-08-14T02:00:14+00:00", "claudeExecutablePath": "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude", - "claudeExecutableSha256": "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", - "claudeCliVersion": "2.1.211", + "claudeExecutableSha256": "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", + "claudeCliVersion": "2.1.231", "modelAlias": "opus", "resolvedModelId": "claude-opus-4-8[1m]", - "executionProfileSha256": "sha256:ffbb0b9acbe9be2d3a95e8911fed3dc98db697c949840e4d57d15827bd79015f", - "executionReceiptSha256": "sha256:c345074abe9fec76446dca9e585bdf13282e9e108a2f148cddc04e9522ea35f0", - "structuredOutputSha256": "sha256:0cc933c5c9e6236a004913f1323a11f0b31a7c4d93de6746630231fe7ebf8f79", + "executionProfileSha256": "sha256:25168029a85cc2a8609b9ff6eda51e213ec04c7d7384ac9ca00eb0c0a48055ac", + "executionReceiptSha256": "sha256:d7ad7cae0531ce4891eb44df30bf03e25e4685163fdda1ba771a9605a528f4b7", + "structuredOutputSha256": "sha256:3aa0dcd09037fb46accc79491b7ba2b576b8b673beb9a4930edc4b8aba6657be", "terminalReason": "completed", - "totalCostUsd": "0.042795", + "totalCostUsd": "0.048025", "modelMatch": true, "exitCode": 0, "apiErrorStatus": null, - "receiptSha256": "sha256:53d7da7e153e052190a2d1febb74fc632a293e87f07e651e73071d4f28c8abde" + "receiptSha256": "sha256:f24b756cbd9160cd5ed33991d5bcdcf2e9190986f3d27646447be6d6ca7444b9" }, "profileSha256": { - "writer": "sha256:ffbb0b9acbe9be2d3a95e8911fed3dc98db697c949840e4d57d15827bd79015f", - "semantic_detector": "sha256:717c94cd7a86e751c2ac3c6f40c11d14bfea36e958ef349888f0f4d66fe1582f", - "blind_judge": "sha256:e6bfe7e9a75cc7d97dcb26784461a1e5fce09b64c321e48340346a1e7a0b3694" + "writer": "sha256:25168029a85cc2a8609b9ff6eda51e213ec04c7d7384ac9ca00eb0c0a48055ac", + "semantic_detector": "sha256:8729e4733cfd79d373ac3c97ca4d3cf8961c9fbf2021c9d084a00b58de43890a", + "blind_judge": "sha256:433cd5cf3952a6fef18c8baff867a95323ffdcfc50d0e9b60040c612fd410903" }, "budget": { "status": "approved", - "reason": "用户已批准 Gate A 总预算 450 美元;plannedCalls 为 writer 45 次(15 基础 + 30 修订,每臂最多 2 次篇幅修订)、semantic_detector 与 blind_judge 各 15 次,maxCalls 为各 150 次安全上限;单次 cap 5 美元,启动预留按 plannedCalls 乘单次 cap,实际成本按可信回执累计。", - "totalBudgetUsd": "450.000000", + "reason": "用户要求以跑通 Gate A 完整流程为目标,当前总预算不设业务限制;2250 美元覆盖三角色各 150 次安全上限。plannedCalls 为 writer 60 次(15 基础 + 45 修订)、semantic_detector 24 次(15 基础 + 9 个格式纠错或瞬时 API 重试余量)、blind_judge 45 次(最多三位评委且每位至多两个格式纠错或瞬时 API 重试槽位);单次 cap 5 美元,实际成本按可信回执累计。", + "totalBudgetUsd": "2250.000000", "plannedCalls": { - "writer": 45, - "semantic_detector": 15, - "blind_judge": 15 + "writer": 60, + "semantic_detector": 24, + "blind_judge": 45 }, "maxCalls": { "writer": 150, "semantic_detector": 150, "blind_judge": 150 }, - "receiptSha256": "sha256:b68eaa66c32e96e6b739a0ff395116fb89de67fa30680dddf0b8f3dae8c1c397" + "receiptSha256": "sha256:764267881671d91d9c3ba65e9a858ba28c6a592079ffbe9d15457da291043847" }, "rawRetention": { "status": "approved", diff --git a/.claude/skills/replay-eval/scripts/fine_outline_detector.py b/.claude/skills/evaluate-frozen-replay/scripts/fine_outline_detector.py similarity index 100% rename from .claude/skills/replay-eval/scripts/fine_outline_detector.py rename to .claude/skills/evaluate-frozen-replay/scripts/fine_outline_detector.py diff --git a/.claude/skills/replay-eval/scripts/load_writer_reference_work.py b/.claude/skills/evaluate-frozen-replay/scripts/load_writer_reference_work.py similarity index 88% rename from .claude/skills/replay-eval/scripts/load_writer_reference_work.py rename to .claude/skills/evaluate-frozen-replay/scripts/load_writer_reference_work.py index a6cf091..5e0ef59 100644 --- a/.claude/skills/replay-eval/scripts/load_writer_reference_work.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/load_writer_reference_work.py @@ -17,14 +17,14 @@ import sys import uuid from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Mapping, Sequence +from typing import Any, Callable, Mapping, Sequence import psycopg from psycopg.rows import dict_row SCRIPT_DIR = Path(__file__).resolve().parent -READ_CONTEXT_SCRIPTS = SCRIPT_DIR.parents[1] / "read-context" / "scripts" -SNAPSHOT_SCRIPTS = SCRIPT_DIR.parents[1] / "snapshot" / "scripts" +READ_CONTEXT_SCRIPTS = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" +SNAPSHOT_SCRIPTS = SCRIPT_DIR.parents[1] / "freeze-context" / "scripts" sys.path.insert(0, str(READ_CONTEXT_SCRIPTS)) sys.path.insert(0, str(SNAPSHOT_SCRIPTS)) @@ -50,7 +50,15 @@ from retrieve_writer_sources import ( # noqa: E402 build_retrieval_plan, retrieve_writer_sources, ) -from writer_contract import han_count, normalize_text # noqa: E402 +from writer_contract import ( # noqa: E402 + PATTERN_NAME_MAX_CHARS, + PATTERN_POINTS_MAX_FIELDS, + PATTERN_POINT_MAX_CHARS, + PATTERN_SUMMARY_MAX_CHARS, + han_count, + normalize_text, + pattern_references_for_arm, +) from writer_eval_preregister import build_balanced_preregistration # noqa: E402 @@ -65,10 +73,17 @@ EXPECTED_ORACLE_INPUT_PROVENANCE = "oracle_reference_scaffold" PREREGISTERED_MAX_CONTEXT_CHARS = 140_000 RUNTIME_ADAPTER_VERSION_PREFIX = "writer-runtime-v1" BUDGET_ROLES = ("writer", "semantic_detector", "blind_judge") -# raw vault 对租约的硬上限是 24 小时(raw_vault.MAX_RETENTION),vault 跑完即自动清理; -# 此窗口只是「万一中途崩溃」后由 vault 回收孤儿租约的安全顶——正常跑完用不到它, -# 6 小时对五章装配绰绰有余,且严格小于 24 小时硬上限,保证 create_vault 校验通过。 -RAW_RETENTION_WINDOW = timedelta(hours=6) +# raw vault 对租约的硬上限是 24 小时。五章三臂会串行执行多角色长调用,因此装配时 +# 使用接近硬上限但留有时钟余量的窗口;execute 仍会在启动和每笔调用前复检。 +RAW_RETENTION_WINDOW = timedelta(hours=23) + +# 公共范式库五型(muse_knowledge_draft.draft_payload->>'型'): +# 套路 / 通用桥段 / 叙事技法 / 情感桥段 / 打斗桥段。C 臂按型各召回若干张。 +PATTERN_CARD_TYPES = ("trope", "scene_pattern", "craft", "emotion", "combat") +# 每型最多取 2 张:五型合计 ≤ 10,严格低于总量硬上限,避免撑爆写手上下文预算。 +PATTERN_TOP_PER_TYPE = 2 +# 范式引用总量硬上限;即使提高每型 top,也不会超过这个数。 +PATTERN_TOTAL_CAP = 12 class WriterReferenceWorkError(AdapterError): @@ -1531,14 +1546,183 @@ def load_writer_reference_rows( } +def _default_pattern_card_searcher( + *, dsn: str, tenant_id: int +) -> Callable[..., list[dict[str, Any]]]: + """惰性导入公共范式库检索器,返回签名 ``(intent, *, ttype, top)`` 的调用体。 + + WHY 惰性:离线测试与 dry-run 不应被迫加载数据库/嵌入依赖,也不能在装配时 + 真连库;只有生产入口 ``main`` 才显式取用本函数,把真实检索接入 C 臂。 + """ + + search_scripts = SCRIPT_DIR.parents[1] / "search-knowledge" / "scripts" + sys.path.insert(0, str(search_scripts)) + from search import search_cards # noqa: E402 惰性导入,避免模块级副作用 + + def _searcher(intent: str, *, ttype: str, top: int) -> list[dict[str, Any]]: + # 公共范式还在 draft 双轨,但必须走专用检索面;dsn/tenant 显式绑定本次 loader, + # 防止真实正文来自一套快照、范式却被默认常量带到另一库或另一租户。 + return search_cards( + intent, + scope="public_pattern", + ttype=ttype, + purpose="generation", + top=top, + dsn=dsn, + tenant_id=tenant_id, + ) + + return _searcher + + +def _flatten_pattern_point(value: Any) -> str: + """把范式卡字段值拍平成文本。WHY:writingPoints 合同是「字符串→字符串」,而 + search_cards 的 visibleFields 值可能是列表/对象,统一拍平后才能过合同。""" + + if isinstance(value, str): + return value + return json.dumps(value, ensure_ascii=False, sort_keys=True) + + +def _truncate_for_writer(text: str, max_chars: int) -> str: + """按 code point 截断到上限以内,超长补一个省略号并重新 NFC 归一化。 + + WHY:截断可能落在组合字符边界、导致结果不再是 NFC,而合同 _string 会复核 + value == NFC(value);因此截断后必须再归一化一次,保证产出永远过得了合同。 + """ + + if len(text) <= max_chars: + return text + return normalize_text(text[: max(0, max_chars - 1)] + "…") + + +def _pattern_content_projection(card: Mapping[str, Any]) -> dict[str, Any]: + """把 search_cards 的 name/summary/visibleFields 投影为合同允许的限量内容字段。 + + WHY(SoT 变更):写手要真正读到范式卡的名字、一句话摘要和写法要点,而不只是一个 + 来源标签;但 visibleFields 原始字段可能长达数千字,直接灌入会撑爆写手上下文预算, + 因此逐字段截断、只取前若干个字段。上限与合同(writer_contract._pattern_source_ref) + 共用同一组常量,合同侧再失败关闭复核,双重保证体量受控。 + """ + + content: dict[str, Any] = {} + name = normalize_text(str(card.get("name") or "")).strip() + if name: + content["name"] = _truncate_for_writer(name, PATTERN_NAME_MAX_CHARS) + summary = normalize_text(str(card.get("summary") or "")).strip() + if summary: + content["summary"] = _truncate_for_writer(summary, PATTERN_SUMMARY_MAX_CHARS) + visible = card.get("visibleFields") + if isinstance(visible, Mapping): + points: dict[str, str] = {} + # visibleFields 来自库内 jsonb,键序确定;按序取前 N 个非空字段作为写法要点。 + for key, value in visible.items(): + if len(points) >= PATTERN_POINTS_MAX_FIELDS: + break + point_key = normalize_text(str(key)).strip() + point_value = normalize_text(_flatten_pattern_point(value)).strip() + if not point_key or not point_value: + continue + points[point_key] = _truncate_for_writer(point_value, PATTERN_POINT_MAX_CHARS) + if points: + content["writingPoints"] = points + return content + + +def _retrieve_pattern_references( + intent: str, + *, + card_searcher: Callable[..., list[dict[str, Any]]], + types: Sequence[str] = PATTERN_CARD_TYPES, + top_per_type: int = PATTERN_TOP_PER_TYPE, + total_cap: int = PATTERN_TOTAL_CAP, +) -> list[dict[str, Any]]: + """按本章检索意图,从公共范式库五型各召回 top-k 卡,投影为写手合同 patternReferences。 + + WHY:Writer Gate A 的 C 臂要验证「范式指导是否提升质量」,需要把公共范式卡接入 + 写手输入。每张卡投影成 WriterContext v1 ``patternReferences``:来源指针 + (sourceId / sourceVersion / sourceType,保证可回读可审计)**外加内容字段** + (name/summary/writingPoints,保证写手真正读到范式卡的名字、摘要与写法要点)。 + 内容字段经 ``_pattern_content_projection`` 截断到合同上限以内,确保通过 + ``validate_writer_context`` 的 ``_pattern_source_ref`` 校验。search_cards 已直接给出 + 稳定的 ``sourceId``(draft:{id})与 ``sourceVersion``(draft-revision:{n}),正好复用。 + + 总量受控:每型最多 top_per_type 张,且累计不超过 total_cap,避免撑爆上下文预算。 + """ + + intent_text = normalize_text(str(intent or "")).strip() + if not intent_text: + # 没有检索意图(细纲为空)就不召回,失败关闭而非注入空引用。 + return [] + references: list[dict[str, Any]] = [] + seen: set[tuple[str, str]] = set() + for card_type in types: + if len(references) >= total_cap: + break + # 剩余名额决定本型实际 top,保证累计严格不超过 total_cap。 + top = min(top_per_type, total_cap - len(references)) + if top <= 0: + break + for card in card_searcher(intent_text, ttype=card_type, top=top): + # WHY: SQL 是第一道范围门,loader 仍只接受专用公共范式面标记为可用于生产 + # 检索的行;fake/未来替换实现若漏做范围过滤,也不能把治理草稿注入写手。 + if ( + card.get("retrievalScope") != "public_pattern" + or card.get("productionRetrievalEligible") is not True + or card.get("sourceKind") != "draft" + ): + continue + source_id = normalize_text(str(card.get("sourceId") or "")).strip() + source_version = normalize_text(str(card.get("sourceVersion") or "")).strip() + if not source_id or not source_version: + # 缺稳定来源指针的卡不能进冻结上下文,跳过而非混入空引用。 + continue + key = (source_version, source_id) + if key in seen: + # 跨型去重:同一张卡只注入一次。 + continue + seen.add(key) + card_kind = normalize_text(str(card.get("type") or card_type)).strip() or card_type + references.append( + { + "sourceId": source_id, + "sourceVersion": source_version, + # sourceType 会成为写手最终看到的 kind;用范式卡的型作标识。 + "sourceType": card_kind, + # SoT 变更:内容字段(名字/摘要/写法要点)随来源指针一起注入,写手 + # 才能真正读到范式卡;此前只有上面三个指针字段,写手只见一个空标签。 + **_pattern_content_projection(card), + } + ) + if len(references) >= total_cap: + break + return references + + +def _pattern_references_for_arm(arm: str, c_references: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]: + """装配端分臂的薄封装:语义唯一事实源在 writer_contract.pattern_references_for_arm。 + + WHY:装配与回放是两段独立 assemble 的链路,必须按完全相同的规则分臂(A 恒空 / + 其余臂拿 C 候选),否则 C 臂真写读不到范式卡或 A 臂混入范式卡。判定逻辑一律走 + 合同模块,不在装配端另写一套;保留这个私有入口只为兼容既有离线测试的导入面。 + """ + + return pattern_references_for_arm(arm, c_references) + + def assemble_writer_gate_config( *, base_config: Mapping[str, Any], selector_config: Mapping[str, Any], selector_digest: str, rows: Mapping[str, Any], + pattern_card_searcher: Callable[..., list[dict[str, Any]]] | None = None, ) -> dict[str, Any]: - """把同一事务快照装配成 canonical_frozen_prose 五样本配置。""" + """把同一事务快照装配成 canonical_frozen_prose 五样本配置。 + + ``pattern_card_searcher`` 为 None 时不注入范式卡(A/C 两臂 patternReferences 均空), + 保持历史行为与离线测试的零数据库依赖;生产入口显式传入真实检索器才启用 C 臂注入。 + """ common_controls = _validate_loader_controls( base_config, @@ -1790,6 +1974,24 @@ def assemble_writer_gate_config( "tokenBudget": assembly_token_budget, } ) + # 检索意图取自写手细纲:硬约束(含大纲文字与门禁要求)+ 可调节拍。 + # WHY:这两段是本章创作意图的最稠密表达,用它做向量检索能召回最贴合的范式卡。 + pattern_intent = "\n".join( + [str(item) for item in fine_outline["hardConstraints"]] + + [str(item) for item in fine_outline["adjustableBeats"]] + ) + # 未提供检索器时为空,保持历史行为;提供时仅 C 臂经 _pattern_references_for_arm 取用。 + c_pattern_references = ( + _retrieve_pattern_references(pattern_intent, card_searcher=pattern_card_searcher) + if pattern_card_searcher is not None + else [] + ) + # 把 C 臂候选范式卡冻结进 writerContextInput.patternReferences,随 config.json 序列化。 + # WHY:回放端(run_writer_replay)真写时会从 config.json 重新 assemble 各臂上下文; + # 若候选不写进 writerContextInput,C 臂真写就拿不到范式卡,实验失效。这里冻结全量 + # 候选(含来源指针,只留在冻结上下文供审计回读),回放端读出后再经同一事实源 + # pattern_references_for_arm 按臂分配——A 恒空,单变量规则两端只有一处定义。 + context_input["patternReferences"] = _pattern_references_for_arm("C", c_pattern_references) writer_contexts: dict[str, dict[str, Any]] = {} try: for arm, strategy in ( @@ -1815,7 +2017,7 @@ def assemble_writer_gate_config( recent_chapters=context_input["recentChapters"], output_contract=context_input["outputContract"], token_budget=assembly_token_budget, - pattern_references=context_input.get("patternReferences", []), + pattern_references=_pattern_references_for_arm(arm, c_pattern_references), generated_at=context_input["generatedAt"], evidence_strategy=strategy, )["context"] @@ -1929,6 +2131,11 @@ def main() -> int: selector_config=selectors, selector_digest=selector_digest, rows=rows, + # 生产装配才真连公共范式库:C 臂注入范式卡,A 臂保持空对照。 + pattern_card_searcher=_default_pattern_card_searcher( + dsn=args.dsn, + tenant_id=args.tenant_id, + ), ) output_dir = args.output_dir or ( PRIVATE_TMP / f"writer-gate-a-{uuid.uuid4().hex}" diff --git a/.claude/skills/replay-eval/scripts/refresh_runtime_probe.py b/.claude/skills/evaluate-frozen-replay/scripts/refresh_runtime_probe.py similarity index 98% rename from .claude/skills/replay-eval/scripts/refresh_runtime_probe.py rename to .claude/skills/evaluate-frozen-replay/scripts/refresh_runtime_probe.py index 2c16198..3e0c1d5 100644 --- a/.claude/skills/replay-eval/scripts/refresh_runtime_probe.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/refresh_runtime_probe.py @@ -35,12 +35,12 @@ from pathlib import Path from typing import Any, Callable, Mapping, Protocol SCRIPT_DIR = Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" +EXECUTION_DIR = SCRIPT_DIR.parents[1] / "execute-claude-task" / "scripts" +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" if str(SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(SCRIPT_DIR)) -if str(RUNTIME_DIR) not in sys.path: - sys.path.insert(0, str(RUNTIME_DIR)) +if str(EXECUTION_DIR) not in sys.path: + sys.path.insert(0, str(EXECUTION_DIR)) if str(QUALITY_GATE_DIR) not in sys.path: sys.path.insert(0, str(QUALITY_GATE_DIR)) diff --git a/.claude/skills/replay-eval/scripts/run_replay.py b/.claude/skills/evaluate-frozen-replay/scripts/run_replay.py similarity index 99% rename from .claude/skills/replay-eval/scripts/run_replay.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_replay.py index 90007f3..87f8309 100644 --- a/.claude/skills/replay-eval/scripts/run_replay.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_replay.py @@ -19,8 +19,8 @@ from pathlib import Path from typing import Any, Mapping, Sequence SCRIPT_DIR = Path(__file__).resolve().parent -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" -SNAPSHOT_DIR = SCRIPT_DIR.parents[1] / "snapshot" / "scripts" +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" +SNAPSHOT_DIR = SCRIPT_DIR.parents[1] / "freeze-context" / "scripts" if str(QUALITY_GATE_DIR) not in sys.path: sys.path.insert(0, str(QUALITY_GATE_DIR)) if str(SNAPSHOT_DIR) not in sys.path: @@ -40,7 +40,7 @@ from writer_gate import verify_writer_gate_receipt REQUIRED_ARMS = ("outline_only", "outline_plus_cards", "outline_plus_placebo_cards") REPO_ROOT = Path(__file__).resolve().parents[4] -SKILL_PATH = REPO_ROOT / ".claude/skills/fine-outline/SKILL.md" +SKILL_PATH = REPO_ROOT / ".claude/skills/plan-chapter/SKILL.md" PLANNER_PATH = REPO_ROOT / ".claude/agents/planner.md" JUDGE_IDS = ("judge-primary", "judge-secondary") DEFAULT_TIMEOUT_SECONDS = 300.0 @@ -166,7 +166,7 @@ def _planner_prompt( "规划上下文冻结到 as_of;卡只是事实索引和补充,不得替代公共大纲与叙事现在时。", "--- planner identity ---", identity, - "--- fine-outline skill ---", + "--- plan-chapter Skill ---", skill, "--- frozen input ---", _safe_json(context), diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/__init__.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/__init__.py similarity index 83% rename from .claude/skills/replay-eval/scripts/run_writer_replay/__init__.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/__init__.py index edc8ca1..b134db9 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/__init__.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/__init__.py @@ -13,6 +13,7 @@ import argparse import copy import hashlib import json +import os import random import re import subprocess @@ -41,6 +42,7 @@ from claude_runtime import ( # noqa: E402 ClaudeRuntimeError, ExecutionProfile, ExecutionReceipt, + _minimal_environment, run_claude, sha256_json, verify_execution_profile, @@ -101,9 +103,12 @@ from ._common import ( PRIVATE_TMP, PROFILE, QUALITY_GATE_DIR, + RAW_LEASE_CLEANUP_MARGIN_SECONDS, READ_CONTEXT_DIR, REQUIRED_ARMS, - RUNTIME_DIR, + EXECUTION_DIR, + EVIDENCE_DIR, + ReplayInterrupted, ReplayJudge, SCRIPT_DIR, SKILLS_DIR, @@ -118,6 +123,9 @@ from ._common import ( _run_id, _safe_json, _sequence, + defer_termination_handlers, + install_termination_handlers, + restore_termination_handlers, ) from .budget import ( _BudgetedModelRunner, @@ -145,7 +153,7 @@ from .sample import ( _validate_preregistered_length, ) from .blind import ( - _blind_input_v3, + _blind_input_v4, _blind_mapping, _build_blind_input, _candidate_summary, @@ -154,6 +162,7 @@ from .blind import ( _semantic_input_v3, ) from .execute import ( + _assert_raw_call_window, _execute_production, _execute_sample_for_test, _execution_receipt_wrapper, @@ -161,6 +170,7 @@ from .execute import ( _run_budgeted_writer_call, _run_projected_writer, _run_projected_writer_with_receipt, + _raw_call_min_retention, _runner_receipts, _runner_structured_outputs, _verify_private_test_profile, @@ -187,7 +197,21 @@ class _RecordingRuntimeModelRunner: if self.profile.adapter_role != adapter_role or self.profile.json_schema != output_schema: raise WriterReplayError(f"{adapter_role} production profile 与适配器不一致") - invocation = run_claude(self.profile, model_input) + try: + invocation = run_claude(self.profile, model_input) + except ClaudeRuntimeError as error: + # WHY: 失败调用的可信回执挂在 runtime 异常上;先保留再重抛,外层才能同时 + # 结算实际费用并保留原始稳定错误码,不能被“runner 未保留回执”二次覆盖。 + failure_receipt = error.receipt + if failure_receipt is not None: + receipt_value = ( + failure_receipt.as_dict() + if hasattr(failure_receipt, "as_dict") + else failure_receipt + ) + if isinstance(receipt_value, Mapping): + self.receipts.append(copy.deepcopy(dict(receipt_value))) + raise receipt = invocation.receipt.as_dict() self.receipts.append(receipt) self.structured_outputs.append(copy.deepcopy(dict(invocation.structured_output))) @@ -224,6 +248,7 @@ def _validate_execute_authorization( ) -> tuple[dict[str, Any] | None, str | None, str | None]: """在创建 vault 或调用 runner 前证明完整生产 profile 和最坏预算。""" + uses_default_runtime = adapters is None records, blocked_status, blocked_code = _validate_authorization_records(config) if records is None: return None, blocked_status, blocked_code @@ -302,6 +327,22 @@ def _validate_execute_authorization( or not HASH_PATTERN.fullmatch(probe_output_sha256) ): return None, "blocked_execute_probe_contract", "EXECUTE_PROBE_CONTRACT_MISMATCH" + if uses_default_runtime: + try: + # 只验证白名单认证字段的形状与互斥关系;不读取、复制或回显 token。真实 runtime + # 会在每次调用时再次执行同一验证。放在总门禁内可保证缺认证时不建 vault、不消耗 + # 预算槽位,也不会把“子进程根本没启动”误记成成本未知。 + _minimal_environment( + os.environ, + PRIVATE_TMP, + require_authentication=True, + ) + except ValueError: + return ( + None, + "blocked_runtime_authentication", + "RUNTIME_AUTHENTICATION_REQUIRED", + ) assert planned_calls is not None and max_calls is not None return { "adapters": adapters, @@ -317,6 +358,7 @@ def run_writer_replay( *, run_id: str | None = None, output_dir: Path | None = None, + raw_archive_dir: Path | None = None, execute: bool = False, test_adapters: WriterReplayTestAdapters | None = None, production_adapters: WriterReplayProductionAdapters | None = None, @@ -392,6 +434,7 @@ def run_writer_replay( if resolved_output == PRIVATE_TMP or not resolved_output.is_relative_to(PRIVATE_TMP): raise WriterReplayError("execute 输出目录必须位于 /private/tmp 的独立子目录") if test_adapters is None: + uses_default_runtime = production_adapters is None authorized, blocked_status, blocked_code = _validate_execute_authorization( config, production_adapters ) @@ -430,6 +473,54 @@ def run_writer_replay( atomic_write_json(resolved_output / "manifest.json", result) (resolved_output / "manifest.json").chmod(0o600) return result + if raw_archive_dir is None: + if uses_default_runtime: + result.update( + { + "status": "blocked_raw_archive", + "ok": False, + "executeAdapterStatus": "production_preflight_blocked", + "errors": ["RAW_ARCHIVE_REQUIRED"], + } + ) + resolved_output.mkdir(parents=True, exist_ok=True, mode=0o700) + resolved_output.chmod(0o700) + atomic_write_json(resolved_output / "manifest.json", result) + (resolved_output / "manifest.json").chmod(0o600) + return result + resolved_archive = resolved_output.parent / f"{replay_run_id}-raw-archive" + else: + if not raw_archive_dir.is_absolute(): + result.update( + { + "status": "blocked_raw_archive", + "ok": False, + "executeAdapterStatus": "production_preflight_blocked", + "errors": ["RAW_ARCHIVE_INVALID"], + } + ) + resolved_output.mkdir(parents=True, exist_ok=True, mode=0o700) + resolved_output.chmod(0o700) + atomic_write_json(resolved_output / "manifest.json", result) + (resolved_output / "manifest.json").chmod(0o600) + return result + resolved_archive = raw_archive_dir.absolute() + if resolved_archive == resolved_output or resolved_archive.is_relative_to( + resolved_output + ): + result.update( + { + "status": "blocked_raw_archive", + "ok": False, + "executeAdapterStatus": "production_preflight_blocked", + "errors": ["RAW_ARCHIVE_INVALID"], + } + ) + resolved_output.mkdir(parents=True, exist_ok=True, mode=0o700) + resolved_output.chmod(0o700) + atomic_write_json(resolved_output / "manifest.json", result) + (resolved_output / "manifest.json").chmod(0o600) + return result return _execute_production( config=config, replay_run_id=replay_run_id, @@ -438,6 +529,7 @@ def run_writer_replay( samples=samples, prepared=prepared, resolved_output=resolved_output, + raw_archive_root=resolved_archive, authorization=authorized, ) @@ -549,6 +641,11 @@ def _parse_args() -> argparse.Namespace: parser.add_argument("--config", type=Path, required=True) parser.add_argument("--run-id") parser.add_argument("--output-dir", type=Path) + parser.add_argument( + "--raw-archive-dir", + type=Path, + help="正式 execute 的仓外受控 raw 归档根;每轮结束迁移而不删除", + ) mode = parser.add_mutually_exclusive_group() mode.add_argument("--dry-run", action="store_true") mode.add_argument("--execute", action="store_true") @@ -564,6 +661,7 @@ def main() -> int: config, run_id=args.run_id, output_dir=args.output_dir, + raw_archive_dir=args.raw_archive_dir, execute=args.execute, ) print(json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2)) diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/__main__.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/__main__.py similarity index 100% rename from .claude/skills/replay-eval/scripts/run_writer_replay/__main__.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/__main__.py diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/_common.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/_common.py similarity index 62% rename from .claude/skills/replay-eval/scripts/run_writer_replay/_common.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/_common.py index 1e536a6..36914a9 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/_common.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/_common.py @@ -12,6 +12,7 @@ import hashlib import json import random import re +import signal import subprocess import sys from dataclasses import dataclass @@ -21,13 +22,22 @@ from typing import Any, Callable, Mapping, Protocol SCRIPT_DIR = Path(__file__).resolve().parents[1] # 包目录的上一级仍是 scripts/ SKILLS_DIR = SCRIPT_DIR.parents[1] -RUNTIME_DIR = SKILLS_DIR / "runtime" / "scripts" -CONTINUATION_DIR = SKILLS_DIR / "continuation" / "scripts" -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" -DETECT_DIR = SKILLS_DIR / "detect" / "scripts" -QUALITY_GATE_DIR = SKILLS_DIR / "quality-gate" / "scripts" -SNAPSHOT_DIR = SKILLS_DIR / "snapshot" / "scripts" -for import_path in (RUNTIME_DIR, CONTINUATION_DIR, READ_CONTEXT_DIR, DETECT_DIR, QUALITY_GATE_DIR, SNAPSHOT_DIR): +EXECUTION_DIR = SKILLS_DIR / "execute-claude-task" / "scripts" +EVIDENCE_DIR = SKILLS_DIR / "record-run-evidence" / "scripts" +CONTINUATION_DIR = SKILLS_DIR / "write-next-chapter" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" +DETECT_DIR = SKILLS_DIR / "check-content-consistency" / "scripts" +QUALITY_GATE_DIR = SKILLS_DIR / "score-content-quality" / "scripts" +SNAPSHOT_DIR = SKILLS_DIR / "freeze-context" / "scripts" +for import_path in ( + EXECUTION_DIR, + EVIDENCE_DIR, + CONTINUATION_DIR, + READ_CONTEXT_DIR, + DETECT_DIR, + QUALITY_GATE_DIR, + SNAPSHOT_DIR, +): if str(import_path) not in sys.path: sys.path.insert(0, str(import_path)) @@ -125,15 +135,21 @@ MONEY_QUANTUM = Decimal("0.000001") # 仍越界才在编排层终局门失败并记下实际字数。修订指令是三臂共用的固定常量 # (arm-invariant),实际字数、篇幅区间与上一版正文作为数据放进 lengthRevision # 块传给模型,不拼进这段指令文本。 -MAX_LENGTH_REVISIONS = 2 +MAX_LENGTH_REVISIONS = 3 LENGTH_REVISION_INSTRUCTION = ( - "下面是你上一版正文,它的汉字数(lengthRevision.actualHanChars)不在目标篇幅区间 " - "[minChars, maxChars] 内(目标 targetChars)。请在完全保持情节、人物、文风、事实约束" - "不变的前提下,把正文调整到区间内:偏短就增加有戏剧张力的场景、动作、对话与感官细节" - "把内容写够,每个新增场景都要有「要什么、被什么挡住、落点在哪」,绝不靠堆形容词或重复" - "叙述注水;偏长就删除冗余、合并重复场景、收紧表达,不伤情节与人物。" + "下面是你上一版正文。机器篇幅门只统计 candidateBody 中的汉字,不统计标点、空格、数字或" + "拉丁字母;计数结果是 lengthRevision.actualHanChars。请以 targetChars 为修订目标,不要只擦到" + "minChars 或 maxChars 边缘。revisionDirection=expand 时,必须完整保留上一版已有内容,不得" + "压缩、删除或合并已有内容;应新增约 targetDeltaHanChars 个汉字,用有戏剧张力的场景、动作、" + "对话与感官细节扩写,每个新增场景都要有「要什么、被什么挡住、落点在哪」,绝不靠堆形容词" + "或重复叙述注水。revisionDirection=trim 时,应删减约 targetDeltaHanChars 个汉字,不得继续" + "扩写;只删除冗余、合并重复场景、收紧表达,不得损伤情节、人物、文风和事实约束。" "只返回修订后的完整正文(WriterDraft v2 的 candidateBody),不要附带任何解释。" ) +# WHY: raw lease 的最低剩余租期必须覆盖「当前角色的单次 timeout」再加一段明确清理余量, +# 否则像 27 秒这种租约会在模型调用还没结束时就到期。启动按三角色最长单次调用校验, +# 后续在每笔调用前按实际角色复检;plannedCalls*timeout 只作容量上界,不作运行时预测。 +RAW_LEASE_CLEANUP_MARGIN_SECONDS = 60 class WriterReplayError(ValueError): @@ -148,6 +164,64 @@ class BudgetLedgerError(WriterReplayError): """执行预算计划、调用数或实际成本未满足合同。""" +class ReplayInterrupted(BaseException): + """外部 SIGTERM/SIGINT 转成的受控终止。 + + WHY: 必须继承 BaseException 而不是 Exception,业务代码里大量的 ``except Exception`` + (含模型调用包装层)才不会把它当成普通错误吞掉;它要一路冒泡到生产编排顶层,由那里 + 统一失败关闭、清理 raw 并写安全 manifest。只承诺收口 SIGTERM/SIGINT,SIGKILL 收不到。 + """ + + def __init__(self, signum: int) -> None: + self.signum = signum + # 消息只含信号编号(纯数字),绝不携带 raw 路径、正文或 Prompt/Response。 + super().__init__(f"terminated_by_signal_{signum}") + + +def install_termination_handlers() -> dict[int, Any]: + """把 SIGTERM/SIGINT 转成 :class:`ReplayInterrupted`,返回旧 handler 供恢复。 + + WHY: 正式 execute 会发起长时间模型调用,外部任务管理器发来的 SIGTERM 若走默认处理会 + 直接杀死进程,留下 open lease、无 manifest、无费用回执。转成受控异常后才能失败关闭。 + 只有主线程能安装信号 handler;非主线程装不了就返回空 dict,由顶层其它失败关闭机制兜底。 + """ + + previous: dict[int, Any] = {} + + def _handler(signum: int, _frame: Any) -> None: + raise ReplayInterrupted(signum) + + for sig in (signal.SIGTERM, signal.SIGINT): + try: + previous[sig] = signal.signal(sig, _handler) + except ValueError: + # WHY: 非主线程无法安装信号 handler,只能让调用方沿既有失败关闭路径退出。 + pass + return previous + + +def defer_termination_handlers() -> None: + """在最终清理窗口忽略后续 SIGTERM/SIGINT。""" + + for sig in (signal.SIGTERM, signal.SIGINT): + try: + signal.signal(sig, signal.SIG_IGN) + except (ValueError, OSError, RuntimeError, TypeError): + # WHY: 非主线程没有可切换的进程级 handler,不能让清理阶段因再次设置信号而失败。 + pass + + +def restore_termination_handlers(previous: Mapping[int, Any]) -> None: + """恢复安装前的 handler,避免污染调用方 / 测试进程。""" + + for sig, old in previous.items(): + try: + signal.signal(sig, old) + except (ValueError, OSError, RuntimeError, TypeError): + # WHY: 恢复失败不能盖掉正在发布的安全 manifest。 + pass + + class ReplayJudge(Protocol): """盲评边界;只接收盲化内容,生产实现使用独立 Claude runtime。""" diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/authorization.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/authorization.py similarity index 100% rename from .claude/skills/replay-eval/scripts/run_writer_replay/authorization.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/authorization.py diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/blind.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/blind.py similarity index 59% rename from .claude/skills/replay-eval/scripts/run_writer_replay/blind.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/blind.py index e15e7d5..aa3376b 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/blind.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/blind.py @@ -1,4 +1,4 @@ -"""盲评/检测输入构造:v3 细纲/语义/盲化输入投影、候选摘要与去盲映射。""" +"""盲评/检测输入构造:v3 细纲/语义、v4 盲化输入投影、候选摘要与去盲映射。""" from __future__ import annotations @@ -70,7 +70,11 @@ from writer_contract import ( # noqa: E402 validate_writer_context, validate_writer_draft, ) -from writer_rubric import adjudicate_reviews, deblind_reports # noqa: E402 +from writer_rubric import ( # noqa: E402 + RUBRIC_POLICY_VERSION, + adjudicate_reviews, + deblind_reports, +) from writer_eval_preregister import ( # noqa: E402 PreregistrationError, build_balanced_preregistration, @@ -228,6 +232,56 @@ def _outline_v3(writer_context: Mapping[str, Any]) -> tuple[dict[str, Any], list return projected, constraints +# sourceRef 检测输入闭集:与 run_writer_semantic_detector._source_ref 的合同逐字段对齐。 +# WHY: writer 侧的 proseEvidence/factEvidence sourceRef 允许携带 sourceType 等额外字段 +# (那是 writer 合同,writer 看到的上下文保持原样);但投影给 semantic-detector 时,检测输入 +# 校验把 sourceRef 当闭集拒收任何多余字段。这里在投影阶段清洗成合同形状,writer_context 本身不动。 +_SOURCE_REF_ALLOWED = frozenset( + { + "sourceId", + "sourceVersion", + "chapter", + "blockId", + "startCodePoint", + "endCodePoint", + "contentSha256", + } +) + + +def _clean_source_ref(ref: Any) -> Any: + """把单个 sourceRef 清洗成检测输入闭集形状,丢弃 sourceType 等多余字段。 + + ref 是 Mapping 时只保留 _SOURCE_REF_ALLOWED 内的键并返回新 dict(值深拷贝,避免与 + writer_context 内部共享引用);ref 不是 Mapping 时原样深拷贝返回,交由下游检测校验按合同处理。 + """ + + if not isinstance(ref, Mapping): + return copy.deepcopy(ref) + return {key: copy.deepcopy(ref[key]) for key in ref if key in _SOURCE_REF_ALLOWED} + + +def _clean_evidence_source_refs(evidence: Any) -> Any: + """深拷贝证据列表,并把每条证据的 sourceRef 清洗成检测闭集形状。 + + 只清洗 sourceRef 子对象;证据的其它字段(evidenceId/fact/text/chapter/purpose/ + isRecentBaseline/contentSha256 等)原样保留。非 list / 非 dict 条目按现状深拷贝透传。 + """ + + if not isinstance(evidence, list): + return copy.deepcopy(evidence) + cleaned: list[Any] = [] + for item in evidence: + if not isinstance(item, dict): + cleaned.append(copy.deepcopy(item)) + continue + new_item = {key: copy.deepcopy(value) for key, value in item.items() if key != "sourceRef"} + if "sourceRef" in item: + new_item["sourceRef"] = _clean_source_ref(item["sourceRef"]) + cleaned.append(new_item) + return cleaned + + def _semantic_input_v3( *, run_id: str, @@ -250,8 +304,10 @@ def _semantic_input_v3( "contextSnapshotSha256": writer_context["contextSnapshot"]["contextSha256"], "fineOutline": fine_outline, "hardConstraints": constraints, - "factEvidence": copy.deepcopy(writer_context.get("factEvidence", [])), - "proseEvidence": copy.deepcopy(writer_context.get("proseEvidence", [])), + # 投影给检测前清洗 sourceRef:writer 侧证据 sourceRef 带的 sourceType 等多余字段 + # 会被检测输入闭集校验拒收,这里收敛成合同形状(writer_context 本身不变)。 + "factEvidence": _clean_evidence_source_refs(writer_context.get("factEvidence", [])), + "proseEvidence": _clean_evidence_source_refs(writer_context.get("proseEvidence", [])), "asOf": writer_context["asOf"], "authorizationSnapshotId": writer_context["authorizationSnapshot"]["snapshotId"], } @@ -259,9 +315,9 @@ def _semantic_input_v3( def _project_oracle_pack(value: Mapping[str, Any]) -> dict[str, Any]: - """移除 loader 内部授权明细,只向 judge 提供共同真值字段。""" + """严格验证 loader oracle,并投影成 judge 的最小共同真值合同。""" - allowed = { + source_fields = { "schemaVersion", "evaluationSetVersion", "sampleId", @@ -269,18 +325,105 @@ def _project_oracle_pack(value: Mapping[str, Any]) -> dict[str, Any]: "asOf", "sourceSnapshotSha256", "authorizationSnapshotId", + "authorization", "historicalAssertions", "targetAssertions", + "packSha256", } - projected = {key: copy.deepcopy(value[key]) for key in allowed if key in value} + if set(value) != source_fields or value.get("schemaVersion") != "oracle-truth-pack-v1": + raise WriterReplayError("oracleTruthPack 顶层字段或版本非法") + source_without_hash = {key: copy.deepcopy(item) for key, item in value.items() if key != "packSha256"} + if value.get("packSha256") != canonical_sha256(source_without_hash): + raise WriterReplayError("oracleTruthPack 来源哈希不一致") + authorization = _mapping(value.get("authorization"), "oracleTruthPack.authorization") + if set(authorization) != { + "allowedPurpose", "sourceStatus", "sourceVersion", "revalidationAt", + "evaluatorOnly", "snapshotId", + } or authorization.get("snapshotId") != value.get("authorizationSnapshotId"): + raise WriterReplayError("oracleTruthPack 授权绑定非法") + as_of = value.get("asOf") + if isinstance(as_of, bool) or not isinstance(as_of, int) or as_of < 1: + raise WriterReplayError("oracleTruthPack asOf 非法") + + projected_assertions: dict[str, list[dict[str, Any]]] = {} + assertion_ids: list[str] = [] + recent_history_start = max(1, as_of - 3) + for field, expected_type in ( + ("historicalAssertions", "canonical_history"), + ("targetAssertions", "target_reference_scaffold"), + ): + projected_items: list[dict[str, Any]] = [] + for index, raw in enumerate(_sequence(value.get(field), f"oracleTruthPack.{field}")): + item = _mapping(raw, f"oracleTruthPack.{field}[{index}]") + if set(item) != { + "assertionId", "assertionType", "statement", "sourceVersion", + "chapterBoundary", "contentSha256", + } or item.get("assertionType") != expected_type: + raise WriterReplayError(f"oracleTruthPack.{field} 断言字段或类型非法") + boundary = _mapping( + item.get("chapterBoundary"), + f"oracleTruthPack.{field}[{index}].chapterBoundary", + ) + if set(boundary) != {"minChapter", "maxChapter"}: + raise WriterReplayError(f"oracleTruthPack.{field} 章界字段非法") + start = boundary.get("minChapter") + end = boundary.get("maxChapter") + if ( + isinstance(start, bool) or not isinstance(start, int) or start < 1 + or isinstance(end, bool) or not isinstance(end, int) or end < start + or (field == "historicalAssertions" and end > as_of) + or (field == "targetAssertions" and (start != as_of + 1 or end != as_of + 1)) + ): + raise WriterReplayError(f"oracleTruthPack.{field} 章界非法") + assertion_id = str(item.get("assertionId") or "") + statement = str(item.get("statement") or "") + source_version = str(item.get("sourceVersion") or "") + content_hash = item.get("contentSha256") + if ( + not assertion_id or not statement or not source_version + or content_hash != "sha256:" + hashlib.sha256(statement.encode("utf-8")).hexdigest() + ): + raise WriterReplayError(f"oracleTruthPack.{field} 断言内容或哈希非法") + assertion_ids.append(assertion_id) + projected_item = { + "assertionId": assertion_id, + "text": statement, + "sourceVersion": source_version, + "chapterStart": start, + "chapterEnd": end, + "contentSha256": content_hash, + } + # WHY: loader 的完整 oracle 保留全书历史供受控验真,但单次盲评不能把数百章全文 + # 直灌模型。评委只取三臂共同的冻结点前连续四章;更早事实应由标准事实摘要补充, + # 不能借机注入 A/C 臂专属原文或卡检索轨迹。 + if field != "historicalAssertions" or end >= recent_history_start: + projected_items.append(projected_item) + projected_assertions[field] = projected_items + recent_chapters = { + item["chapterEnd"] for item in projected_assertions["historicalAssertions"] + } + if recent_chapters != set(range(recent_history_start, as_of + 1)): + raise WriterReplayError("oracleTruthPack 最近四章历史断言不完整") + if not projected_assertions["targetAssertions"] or len(assertion_ids) != len(set(assertion_ids)): + raise WriterReplayError("oracleTruthPack 目标断言为空或 assertionId 重复") + + projected = { + key: copy.deepcopy(value[key]) + for key in { + "schemaVersion", "evaluationSetVersion", "sampleId", "workId", "asOf", + "sourceSnapshotSha256", "authorizationSnapshotId", + } + } + projected.update(projected_assertions) projected["packSha256"] = canonical_sha256(projected) return projected -def _blind_input_v3( +def _blind_input_v4( *, run_id: str, sample_id: str, + scenario: str, reviewer_id: str, candidate_order: list[str], mapping: Mapping[str, str], @@ -288,13 +431,15 @@ def _blind_input_v3( writer_context: Mapping[str, Any], oracle_pack: Mapping[str, Any], ) -> dict[str, Any]: - """构造三候选共同 oracle 的盲评输入,正文只在调用期与 vault 中存在。""" + """构造绑定场景评分策略的共同 oracle 盲评输入。""" fine_outline, _constraints = _outline_v3(writer_context) payload = { - "schemaVersion": "writer-blind-input-v3", + "schemaVersion": "writer-blind-input-v4", "runId": run_id, "sampleId": sample_id, + "scenario": scenario, + "rubricPolicyVersion": RUBRIC_POLICY_VERSION, "reviewerInvocationId": reviewer_id, "candidateOrder": list(candidate_order), "candidates": [ diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/budget.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/budget.py similarity index 91% rename from .claude/skills/replay-eval/scripts/run_writer_replay/budget.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/budget.py index 367b4c8..9fd1010 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/budget.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/budget.py @@ -107,6 +107,7 @@ class _ExecutionBudgetLedger: planned_calls: Mapping[str, int], max_calls: Mapping[str, int], profiles: Mapping[str, ExecutionProfile], + pre_call_guard: Callable[[str], None] | None = None, ) -> None: self.total_budget = _budget_amount(total_budget, "totalBudgetUsd") self.planned_calls = {role: int(planned_calls[role]) for role in BUDGET_ROLES} @@ -121,6 +122,7 @@ class _ExecutionBudgetLedger: self._next_token = 0 self._in_flight: dict[str, int] = {} self._settled_invocations: set[str] = set() + self._pre_call_guard = pre_call_guard self.cost_unknown = False self.failure_reason: str | None = None @@ -159,6 +161,11 @@ class _ExecutionBudgetLedger: raise BudgetLedgerError(f"{role} maxCalls 已耗尽") if self.total_actual_cost + self._remaining_reserve() > self.total_budget: raise BudgetLedgerError("剩余计划预留超过总预算") + # WHY: raw 租期复检必须发生在所有只读预算校验之后、消耗计划槽位和外发调用之前。 + # 守卫失败代表调用尚未发起,不能把它记成已用调用或成本未知;三角色都从 begin + # 经过这一共同边界。 + if self._pre_call_guard is not None: + self._pre_call_guard(role) self.used_calls[role] += 1 self.remaining_planned_calls[role] -= 1 self._next_token += 1 @@ -180,39 +187,50 @@ class _ExecutionBudgetLedger: if self._in_flight.get(role) != token: raise BudgetLedgerError(f"{role} 调用 token 错序或重复结算") - del self._in_flight[role] if role not in BUDGET_ROLES or not isinstance(receipt, Mapping): self._fail("EXECUTION_RECEIPT_INVALID") + del self._in_flight[role] raise BudgetLedgerError("EXECUTION_RECEIPT_INVALID") if receipt.get("adapterRole") != role: self._fail("EXECUTION_RECEIPT_ROLE_MISMATCH") + del self._in_flight[role] raise BudgetLedgerError(f"{role} ExecutionReceipt 角色不匹配") invocation_id = receipt.get("invocationId") if not isinstance(invocation_id, str) or not invocation_id: self._fail("EXECUTION_RECEIPT_INVALID") + del self._in_flight[role] raise BudgetLedgerError("ExecutionReceipt 缺少 invocationId") if invocation_id in self._settled_invocations: self._fail("EXECUTION_RECEIPT_DUPLICATE") + del self._in_flight[role] raise BudgetLedgerError("ExecutionReceipt 重复结算") try: cost = _budget_amount(receipt.get("totalCostUsd"), f"{role}.totalCostUsd") except BudgetLedgerError as error: self._fail("EXECUTION_COST_UNKNOWN", cost_unknown=True) + del self._in_flight[role] raise BudgetLedgerError("EXECUTION_COST_UNKNOWN") from error if cost > self.caps[role]: self._fail("EXECUTION_COST_OVER_CAP") + del self._in_flight[role] raise BudgetLedgerError(f"{role} 实际成本超过单次 cap") next_total = (self.total_actual_cost + cost).quantize( MONEY_QUANTUM, rounding=ROUND_HALF_UP ) if next_total > self.total_budget: self._fail("EXECUTION_TOTAL_BUDGET_EXCEEDED") + del self._in_flight[role] raise BudgetLedgerError("累计实际成本超过总预算") + # WHY: 在可信成本写入账本前保留 in-flight 标记;终止信号若落在结算任一步, + # execute 的 handler 会保守标记成本未知。只有金额与 invocation 都已持久到内存账本 + # 后才清除标记,避免“模型已有回执但账本仍记零成本”。 self._settled_invocations.add(invocation_id) self.total_actual_cost = next_total if self.total_actual_cost + self._remaining_reserve() > self.total_budget: self._fail("EXECUTION_TOTAL_BUDGET_EXCEEDED") + del self._in_flight[role] raise BudgetLedgerError("累计成本与剩余计划预留超过总预算") + del self._in_flight[role] def snapshot(self) -> dict[str, Any]: """返回不含正文、prompt、raw path 的安全账本摘要。""" diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/execute.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/execute.py similarity index 64% rename from .claude/skills/replay-eval/scripts/run_writer_replay/execute.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/execute.py index 02debd6..a14b697 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/execute.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/execute.py @@ -12,6 +12,7 @@ import re import subprocess import sys from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from decimal import Decimal, InvalidOperation, ROUND_HALF_UP from pathlib import Path from typing import Any, Callable, Mapping, Protocol @@ -54,6 +55,7 @@ from run_writer_blind_judge import ( # noqa: E402 ) from run_writer_semantic_detector import ( # noqa: E402 SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA, + build_safe_semantic_diagnostic, run_writer_semantic_detector, ) from run_writer_pipeline import ( # noqa: E402 @@ -81,12 +83,17 @@ from ._common import ( LENGTH_REVISION_INSTRUCTION, MAX_LENGTH_REVISIONS, PROFILE, + RAW_LEASE_CLEANUP_MARGIN_SECONDS, REQUIRED_ARMS, + ReplayInterrupted, WriterReplayError, WriterReplayTestAdapters, _mapping, _public_sample, _raw_json, + defer_termination_handlers, + install_termination_handlers, + restore_termination_handlers, ) from .budget import ( _BudgetedModelRunner, @@ -95,7 +102,7 @@ from .budget import ( _receipt_from_error, ) from .blind import ( - _blind_input_v3, + _blind_input_v4, _build_blind_input, _candidate_summary, _outline_v3, @@ -194,6 +201,8 @@ def _run_budgeted_writer_call( business_input, runner=runner, business_validator=validate_writer_draft, + run_id=normalized_context.get("runId"), + caller="writer", ) candidate = build_candidate_envelope( normalized_context, @@ -210,6 +219,41 @@ def _run_budgeted_writer_call( return candidate, invocation.receipt +def _raw_call_min_retention(profile: ExecutionProfile) -> timedelta: + """返回一次角色调用从发起到可安全清理所需的最低剩余租期。""" + + return timedelta( + seconds=profile.timeout_seconds + RAW_LEASE_CLEANUP_MARGIN_SECONDS + ) + + +def _assert_raw_call_window( + retain_until: str, + profile: ExecutionProfile, + *, + now: datetime | None = None, +) -> None: + """在调用前确认 raw 租期覆盖该角色 deadline 与清理余量。""" + + try: + retention = datetime.fromisoformat(str(retain_until or "").replace("Z", "+00:00")) + if retention.tzinfo is None: + raise ValueError("retainUntil 缺少时区") + except ValueError as error: + raise RawVaultError("RAW_LEASE_INVALID", "raw retainUntil 非法") from error + current = now or datetime.now(timezone.utc) + if current.tzinfo is None: + raise RawVaultError("RAW_LEASE_INVALID", "raw 当前时间必须带时区") + if ( + retention.astimezone(timezone.utc) - current.astimezone(timezone.utc) + < _raw_call_min_retention(profile) + ): + raise RawVaultError( + "RAW_LEASE_INSUFFICIENT_RETENTION", + "raw 剩余租期不足以覆盖下一次模型调用与清理", + ) + + def _run_projected_writer( context: Mapping[str, Any], *, @@ -450,18 +494,38 @@ def _runner_receipts(model_runner: Any, start: int, role: str) -> list[dict[str, def _runner_structured_outputs( - model_runner: Any, start: int, expected_count: int, role: str -) -> list[dict[str, Any]]: - """读取模型调用当时的原始 structured output,供回执内容哈希复核。""" + model_runner: Any, + start: int, + execution_receipts: list[Mapping[str, Any]], + role: str, +) -> list[dict[str, Any] | None]: + """按回执顺序对齐原始输出;API 失败回执的位置显式保留为 None。""" outputs = getattr(model_runner, "structured_outputs", None) - if not isinstance(outputs, list) or len(outputs) - start != expected_count: + if not isinstance(outputs, list): raise WriterReplayError(f"{role} 模型运行器未保留原始 structured output") - result: list[dict[str, Any]] = [] - for output in outputs[start:]: + pending_outputs = outputs[start:] + output_index = 0 + result: list[dict[str, Any] | None] = [] + for receipt_index, receipt in enumerate(execution_receipts): + expected_hash = receipt.get("structuredOutputSha256") + if expected_hash is None: + result.append(None) + continue + if output_index >= len(pending_outputs): + raise WriterReplayError(f"{role} 原始 structured output 数量不足") + output = pending_outputs[output_index] + output_index += 1 if not isinstance(output, Mapping): raise WriterReplayError(f"{role} 原始 structured output 形状非法") - result.append(copy.deepcopy(dict(output))) + normalized = copy.deepcopy(dict(output)) + if canonical_sha256(normalized) != expected_hash: + raise WriterReplayError( + f"{role} 第 {receipt_index + 1} 条回执未绑定原始 structured output" + ) + result.append(normalized) + if output_index != len(pending_outputs): + raise WriterReplayError(f"{role} 原始 structured output 数量多余") return result @@ -501,9 +565,10 @@ def _execute_production( samples: list[Mapping[str, Any]], prepared: list[Mapping[str, Any]], resolved_output: Path, + raw_archive_root: Path, authorization: Mapping[str, Any], ) -> dict[str, Any]: - """执行完整生产编排;所有 raw 字节受同一 lease 保护,安全结果只保留回执。""" + """执行完整生产编排;raw 受同一 lease 保护,结束后迁移到仓外受控归档。""" adapters = authorization["adapters"] profiles = { @@ -518,21 +583,32 @@ def _execute_production( max_calls = _mapping( authorization.get("maxCalls"), "executionAuthorization.budget.maxCalls" ) + raw_auth = _mapping(authorization.get("raw"), "executionAuthorization.rawRetention") + manager: RawVaultManager | None = None + lease: VaultLease | None = None + + def _guard_next_model_call(role: str) -> None: + """让三角色在共同预算入口按各自 timeout 复检当前 lease。""" + + if lease is None: + raise RawVaultError("RAW_LEASE_INVALID", "raw lease 尚未建立") + _assert_raw_call_window(lease.retain_until, profiles[role]) + ledger = _ExecutionBudgetLedger( total_budget=_budget_amount(budget.get("totalBudgetUsd"), "totalBudgetUsd"), planned_calls={role: int(planned_calls[role]) for role in BUDGET_ROLES}, max_calls={role: int(max_calls[role]) for role in BUDGET_ROLES}, profiles=profiles, + pre_call_guard=_guard_next_model_call, ) semantic_runner = _BudgetedModelRunner( adapters.semantic_model_runner, ledger, "semantic_detector" ) judge_runner = _BudgetedModelRunner(adapters.judge_model_runner, ledger, "blind_judge") - raw_auth = _mapping(authorization.get("raw"), "executionAuthorization.rawRetention") - manager: RawVaultManager | None = None - lease: VaultLease | None = None - cleanup_receipt: dict[str, Any] | None = None - completed_cas: list[tuple[str, FileCasStore, dict[str, Any]]] = [] + migration_receipt: dict[str, Any] | None = None + recovery_receipt: dict[str, Any] | None = None + cas_records: list[dict[str, Any]] = [] + active_sample_result: dict[str, Any] | None = None result: dict[str, Any] = { "runId": replay_run_id, "mode": "execute", @@ -547,10 +623,21 @@ def _execute_production( } resolved_output.mkdir(parents=True, exist_ok=True, mode=0o700) resolved_output.chmod(0o700) + # WHY: 长时间模型调用期间,外部 SIGTERM/SIGINT 必须转成受控异常失败关闭,而不是被默认 + # 处理直接杀死进程(那会留下 open lease、无 manifest、无费用回执)。finally 里恢复原 + # handler,避免污染调用方 / 测试进程。 + interrupt_handlers = install_termination_handlers() try: - manager = adapters.vault_manager_factory(resolved_output / "journal" / "raw-vault") source_version = str(_mapping(config.get("authorization"), "authorization").get("sourceVersion") or "") source_hash = str(_mapping(config.get("authorization"), "authorization").get("sourceHash") or "") + # WHY: plannedCalls*timeout 是预算与容量上界,不是预计运行时长。启动只要求 lease + # 覆盖三角色中最长的一次调用加清理余量;随后每次 ledger.begin 都按实际角色 timeout + # 复检。这样不会因条件性修订/纠错的理论总和超过 24 小时而误阻断,也不会让下一笔 + # 调用跨过 raw 授权窗口。 + longest_profile = max(profiles.values(), key=lambda item: item.timeout_seconds) + min_retention = _raw_call_min_retention(longest_profile) + _assert_raw_call_window(str(raw_auth.get("retainUntil") or ""), longest_profile) + manager = adapters.vault_manager_factory(resolved_output / "journal" / "raw-vault") lease = manager.create_vault( authorization_id=str(raw_auth.get("authorizationId") or ""), approved_by=str(raw_auth.get("approvedBy") or ""), @@ -559,6 +646,7 @@ def _execute_production( content_hashes=[source_hash], purpose="offline_evaluation", retain_until=str(raw_auth.get("retainUntil") or ""), + min_remaining=min_retention, ) _raw_json(manager, lease, "run/config.json", config) @@ -583,6 +671,20 @@ def _execute_production( raw_sample = raw_by_id[sample_id] current = prepared_by_id[sample_id] cas = adapters.cas_store_factory(resolved_output / "journal" / "cas" / sample_id) + sample_result: dict[str, Any] = { + **_public_sample(current), + "ok": False, + "status": "running", + "candidates": {}, + } + # 先登记 CAS 和样本,再调用可能在返回前已提交副作用的 initialize。 + cas_record: dict[str, Any] = { + "sampleId": sample_id, + "cas": cas, + "state": None, + } + cas_records.append(cas_record) + active_sample_result = sample_result cas_state = cas.initialize( run_id=replay_run_id, sample_id=sample_id, @@ -594,13 +696,10 @@ def _execute_production( safe_summary={"completedArms": []}, cleanup_state="pending", ) - sample_result: dict[str, Any] = { - **_public_sample(current), - "ok": False, - "status": "running", - "candidates": {}, - } + cas_record["state"] = cas_state candidates: dict[str, dict[str, Any]] = {} + semantic_diagnostic: dict[str, Any] | None = None + semantic_diagnostics: dict[str, dict[str, Any]] = {} execution_receipts[sample_id] = {} detector_reports[sample_id] = {} try: @@ -621,18 +720,22 @@ def _execute_production( projected=projected, ) writer_receipts = [writer_receipt] - # 篇幅自纠环:候选越界不丢弃,退回写手按 lengthRevision 修订,每臂最多两遍。 + # 篇幅自纠环:候选越界不丢弃,退回写手按 lengthRevision 修订,每臂最多三遍。 for _ in range(MAX_LENGTH_REVISIONS): actual = han_count(candidate["candidateBody"]) if int(contract["minChars"]) <= actual <= int(contract["maxChars"]): break revision_input = dict(creative_input) + target = int(contract["targetChars"]) revision_input["lengthRevision"] = { "previousDraft": candidate["candidateBody"], "actualHanChars": actual, "minChars": int(contract["minChars"]), "maxChars": int(contract["maxChars"]), - "targetChars": int(contract["targetChars"]), + "targetChars": target, + "countingRule": "han_chars_only", + "revisionDirection": "expand" if actual < target else "trim", + "targetDeltaHanChars": abs(target - actual), "instruction": LENGTH_REVISION_INSTRUCTION, } _raw_json( @@ -720,9 +823,16 @@ def _execute_production( receipts=semantic_receipts, ) ) - if semantic.get("ok") is not True or semantic.get("status") != "passed": - code = str(semantic.get("primaryCode") or "SEMANTIC_CHECK_FAILED") - raise WriterReplayError(f"{sample_id} {arm} semantic 失败: {code}") + # WHY: ok=false 表示检测器输出或回执不合约,属于系统失败;合法报告里的 + # failed/needs_evidence 是候选质量信号,必须保留候选并继续三臂、盲评与 Gate, + # 由 Gate 只按 C 臂硬门裁决,不能把 A/B 对照臂质量差误报成链路故障。 + semantic_status = str(semantic.get("status") or "") + if semantic.get("ok") is not True or not isinstance(semantic.get("report"), Mapping): + semantic_diagnostic = build_safe_semantic_diagnostic(semantic) + raise WriterReplayError(semantic_diagnostic["primaryCode"]) + if semantic_status != "passed": + semantic_diagnostics[arm] = build_safe_semantic_diagnostic(semantic) + cas_record["semanticDiagnostics"] = copy.deepcopy(semantic_diagnostics) candidates[arm] = dict(candidate) sample_result["candidates"][arm] = _candidate_summary(candidate, arm) detector_reports[sample_id][arm] = semantic["report"] @@ -736,9 +846,17 @@ def _execute_production( "candidateSha256": candidate["candidateSha256"], "semanticReportSha256": semantic["report"]["reportSha256"], }, - safe_summary={"completedArms": list(candidates)}, + safe_summary={ + "completedArms": list(candidates), + **( + {"semanticDiagnostics": copy.deepcopy(semantic_diagnostics)} + if semantic_diagnostics + else {} + ), + }, cleanup_state="pending", ) + cas_record["state"] = cas_state mapping = _mapping( blind_by_id[sample_id].get("blindMapping"), @@ -750,9 +868,10 @@ def _execute_production( raise WriterReplayError(f"{sample_id} 缺少 oracleTruthPack") oracle_pack = _project_oracle_pack(oracle_source) _raw_json(manager, lease, f"samples/{sample_id}/oracle-truth-pack.json", oracle_pack) - first = _blind_input_v3( + first = _blind_input_v4( run_id=replay_run_id, sample_id=sample_id, + scenario=str(current.get("scenario") or ""), reviewer_id=f"{sample_id}-reviewer-1", candidate_order=blind_order, mapping=mapping, @@ -760,9 +879,10 @@ def _execute_production( writer_context=current["_writerContexts"]["A"], oracle_pack=oracle_pack, ) - second = _blind_input_v3( + second = _blind_input_v4( run_id=replay_run_id, sample_id=sample_id, + scenario=str(current.get("scenario") or ""), reviewer_id=f"{sample_id}-reviewer-2", candidate_order=list(reversed(blind_order)), mapping=mapping, @@ -770,9 +890,10 @@ def _execute_production( writer_context=current["_writerContexts"]["A"], oracle_pack=oracle_pack, ) - third = _blind_input_v3( + third = _blind_input_v4( run_id=replay_run_id, sample_id=sample_id, + scenario=str(current.get("scenario") or ""), reviewer_id=f"{sample_id}-reviewer-3", candidate_order=blind_order, mapping=mapping, @@ -792,6 +913,36 @@ def _execute_production( model_runner=judge_runner, ) _raw_json(manager, lease, f"samples/{sample_id}/judge-output.json", judge) + judge_call_count = len( + getattr(adapters.judge_model_runner, "receipts", []) + ) - judge_start + if judge.get("ok") is not True: + # WHY: 完整评委草稿只进 raw 归档;安全 manifest 必须能回答哪一位评委、 + # 第几次尝试和哪类结构门失败,避免只剩一个顶层错误码无法复盘。 + safe_judge_diagnostic: dict[str, Any] = { + "schemaVersion": "blind-judge-run-diagnostic-v1", + "status": str(judge.get("status") or "failed_judge_invalid"), + "primaryCode": str(judge.get("primaryCode") or "JUDGE_INVALID"), + "reviewCount": judge.get("reviewCount"), + "attemptCount": judge.get("attemptCount"), + "modelCallCount": judge_call_count, + "causes": copy.deepcopy(judge.get("causes") or []), + } + if isinstance(judge.get("safeDiagnostic"), Mapping): + safe_judge_diagnostic["modelOutput"] = copy.deepcopy( + judge["safeDiagnostic"] + ) + sample_result["judgeDiagnostic"] = safe_judge_diagnostic + cas_record["judgeDiagnostic"] = copy.deepcopy(safe_judge_diagnostic) + if judge.get("ok") is not True and judge_call_count == 0: + # WHY: 输入/合同在首个评委调用前失败时本来就没有 ExecutionReceipt; + # 先强取回执会覆盖真正的稳定错误码,使生产故障无法定位。 + status = str(judge.get("status") or "failed_judge_invalid") + if status not in {"failed_judge_invalid", "failed_judge_unstable"}: + status = "failed_judge_invalid" + raise WriterReplayError( + f"{status}:{judge.get('primaryCode', 'JUDGE_INVALID')}" + ) judge_receipts = _runner_receipts( adapters.judge_model_runner, judge_start, "blind_judge" ) @@ -799,7 +950,7 @@ def _execute_production( reviewer_structured_outputs = _runner_structured_outputs( adapters.judge_model_runner, judge_output_start, - len(judge_receipts), + judge_receipts, "blind_judge", ) _raw_json( @@ -813,17 +964,47 @@ def _execute_production( if status not in {"failed_judge_invalid", "failed_judge_unstable"}: status = "failed_judge_invalid" raise WriterReplayError(f"{status}:{judge.get('primaryCode', 'JUDGE_INVALID')}") + reviewer_reports = judge.get("reviewerReports") + if not isinstance(reviewer_reports, list): + raise WriterReplayError("评委结果缺少 reviewerReports 数组") + reviewer_final_receipt_indexes: list[int] = [] + for reviewer_index, reviewer_report in enumerate( + reviewer_reports + ): + reviewer_report = _mapping( + reviewer_report, + f"judge.reviewerReports[{reviewer_index}]", + ) + receipt_hash = reviewer_report.get("modelReceiptSha256") + matches = [ + index + for index, receipt in enumerate(judge_receipts) + if canonical_sha256(receipt) == receipt_hash + ] + if ( + len(matches) != 1 + or matches[0] in reviewer_final_receipt_indexes + or ( + reviewer_final_receipt_indexes + and matches[0] <= reviewer_final_receipt_indexes[-1] + ) + ): + raise WriterReplayError( + "评委最终报告未唯一绑定按调用顺序排列的 ExecutionReceipt" + ) + reviewer_final_receipt_indexes.append(matches[0]) bound_judge_report = { **copy.deepcopy({key: value for key, value in judge.items() if key != "reviewerReports"}), "reviewerReports": copy.deepcopy(judge["reviewerReports"]), - # WHY: 把每个 reviewer 的模型原始 structured output 按 reviewer 顺序一并绑进报告。 - # reviewer_structured_outputs 与 judge_receipts 同源于 judge_output_start 起的同一段 - # runner 记录、且与 reviewerReports 同为每个 reviewer 一次调用产物,三者下标一一对应。 - # 原始输出随报告进 Gate builder 后,builder 可只读复核 + # WHY: 把每次调用的模型原始 structured output 按回执顺序绑进报告;前置 + # API 失败没有输出,其位置显式为 null。最终索引再把 reviewerReports 绑定到 + # 对应成功调用。原始输出随报告进 Gate builder 后,builder 可只读复核 # canonical_sha256(原始输出) == execution_receipt.structuredOutputSha256, # 恢复「报告评分内容 == 模型真实输出」绑定,堵住「一致篡改报告评分 + 重签 # reportSha256 + 重推导 panel」绕过 recomputed_panel 校验的防御纵深回退。 "reviewerStructuredOutputs": copy.deepcopy(reviewer_structured_outputs), + # 保留纠错前后的全量输出,并显式指出每位 reviewer 的最终有效调用。 + "reviewerFinalReceiptIndexes": reviewer_final_receipt_indexes, "modelReceiptSha256": canonical_sha256(judge_receipt_hashes), } bound_judge_report["reportSha256"] = canonical_sha256(bound_judge_report) @@ -842,18 +1023,27 @@ def _execute_production( expected_candidate_version=1, state="JUDGE_COMPLETED", result={"judgeStatus": judge["status"], "reviewCount": judge["reviewCount"]}, - safe_summary={"completedArms": list(candidates), "judgeStatus": judge["status"]}, + safe_summary={ + "completedArms": list(candidates), + "judgeStatus": judge["status"], + **( + {"semanticDiagnostics": copy.deepcopy(semantic_diagnostics)} + if semantic_diagnostics + else {} + ), + }, cleanup_state="pending", ) - completed_cas.append((sample_id, cas, cas_state)) + cas_record["state"] = cas_state sample_result.update( { "ok": True, "status": "pending_raw_cleanup", - "detector": { - arm: copy.deepcopy(detector_reports[sample_id][arm]) - for arm in REQUIRED_ARMS - }, + **( + {"semanticDiagnostics": copy.deepcopy(semantic_diagnostics)} + if semantic_diagnostics + else {} + ), "judge": { "status": judge["status"], "reviewCount": judge["reviewCount"], @@ -921,10 +1111,22 @@ def _execute_production( ) for arm in REQUIRED_ARMS } - except (WriterReplayError, WriterAdapterError, CasConflictError, CasRecoveryError) as error: - message = str(error) + except ( + WriterReplayError, + WriterAdapterError, + RawVaultError, + CasConflictError, + CasRecoveryError, + ) as error: + message = ( + str(getattr(error, "code", "RAW_VAULT_FAILED")) + if isinstance(error, RawVaultError) + else str(error) + ) if isinstance(error, (CasConflictError, CasRecoveryError)): sample_status = "failed_cas_conflict" + elif isinstance(error, RawVaultError): + sample_status = "failed_raw_vault" elif ledger.cost_unknown: sample_status = "failed_budget_cost_unknown" message = ledger.failure_reason or "EXECUTION_COST_UNKNOWN" @@ -935,12 +1137,15 @@ def _execute_production( sample_status = "failed_judge_unstable" elif message.startswith("failed_judge_invalid"): sample_status = "failed_judge_invalid" - elif "semantic 失败" in message: + elif semantic_diagnostic is not None: sample_status = "failed_semantic_detector" else: sample_status = "failed_system" failed_status = failed_status or sample_status sample_result.update({"ok": False, "status": sample_status, "errors": [message]}) + if semantic_diagnostic is not None: + sample_result["semanticDiagnostic"] = copy.deepcopy(semantic_diagnostic) + cas_record["semanticDiagnostic"] = copy.deepcopy(semantic_diagnostic) # 篇幅越界时把实际汉字数与合同边界写进安全字段(全是数字,不含正文)。 # 修订环耗尽后由编排层终局门抛出 candidate_length_out_of_range 并携带 details; # 其它错误没有 details,因此只有真正越界才会落 lengthViolation。 @@ -953,21 +1158,32 @@ def _execute_production( "targetChars": int(details["targetChars"]), } try: - if cas_state["state"] != "COMPLETED": - cas.transition( + if cas_record["state"]["state"] not in {"COMPLETED", "FAILED"}: + cas_state = cas.transition( expected_revision=cas_state["revision"], expected_attempt=1, expected_candidate_version=1, - state="FAILED", + state="FAILED_PENDING_CLEANUP", result={"status": sample_status}, - safe_summary={"completedArms": list(candidates)}, + safe_summary={ + "completedArms": list(candidates), + **( + {"semanticDiagnostic": copy.deepcopy(semantic_diagnostic)} + if semantic_diagnostic is not None + else {} + ), + }, cleanup_state="pending", ) + cas_record["state"] = cas_state except (CasConflictError, CasRecoveryError): sample_result["status"] = "failed_cas_conflict" failed_status = "failed_cas_conflict" result["samples"].append(sample_result) - if ledger.failed: + active_sample_result = None + # 任一样本进入失败态后,本轮已不可能构建完整 Gate 输入;立即停止后续样本, + # 避免在已失败的 run 上继续调用模型和消耗预算。 + if failed_status is not None: break if execution_receipts: @@ -1013,6 +1229,9 @@ def _execute_production( failed_status = "failed_gate_input_builder" result["errors"] = [str(error)] else: + # Gate 输入与成功终态是一组不可分割的发布动作;从这里到 manifest 落盘都忽略 + # 后续终止信号,避免先留下可消费 Gate 文件、再把同一 run 改写为 interrupted。 + defer_termination_handlers() atomic_write_json(resolved_output / "gate-input.json", gate_input) (resolved_output / "gate-input.json").chmod(0o600) result["gateInputSha256"] = gate_input["gateInputSha256"] @@ -1029,36 +1248,174 @@ def _execute_production( "errors": [getattr(error, "code", type(error).__name__)], } ) + except (ReplayInterrupted, KeyboardInterrupt): + # WHY: 外部终止可能正好落在「已发起但还没拿到可信回执」的模型调用中间,此时无法知道 + # 这次调用是否已经产生费用,必须按成本未知失败关闭并禁止任何后续调用;不能假装零成本 + # 继续。错误字段只放稳定码,绝不放异常文本(可能携带 raw 路径 / 正文)。 + if ledger._in_flight: + ledger.cost_unknown = True + ledger.failure_reason = "EXECUTION_COST_UNKNOWN" + ledger._in_flight.clear() + elif ledger.failure_reason is None: + # 终止落在两次调用之间,已结算调用成本可知,但整轮仍被中断。 + ledger.failure_reason = "EXECUTION_INTERRUPTED" + if active_sample_result is not None: + active_sample_result.update( + { + "ok": False, + "status": "failed_interrupted", + "errors": [ledger.failure_reason or "EXECUTION_INTERRUPTED"], + } + ) + if not any(item is active_sample_result for item in result["samples"]): + result["samples"].append(active_sample_result) + result.update( + { + "status": "failed_interrupted", + "ok": False, + "errors": [ledger.failure_reason or "EXECUTION_INTERRUPTED"], + } + ) finally: - if manager is not None and lease is not None: - try: - cleanup_receipt = manager.cleanup(lease) - except Exception as error: + # WHY: 首个受控终止后,清理、CAS 收口和 manifest 原子发布必须屏蔽后续终止,避免再次 + # 直接打断失败关闭;原 handler 只能在安全 manifest 发布完成后恢复。 + defer_termination_handlers() + try: + migration_failed = False + if manager is not None: + if lease is not None: + try: + migration_receipt = manager.migrate( + lease, archive_root=raw_archive_root + ) + if migration_receipt.get("status") != "migrated": + raise RawVaultError( + "RAW_MIGRATION_FAILED", "raw 迁移未返回 migrated" + ) + except BaseException as error: + migration_failed = True + result.update( + { + "status": "failed_raw_migration", + "ok": False, + "errors": [getattr(error, "code", "RAW_MIGRATION_FAILED")], + } + ) + else: + try: + # create_vault 可能已写入 lease/vault 后才在返回前被打断;恢复只记录 + # 数量,不把随机 raw ID、路径或正文带进安全 manifest。 + recovered = manager.recover() + recovery_receipt = { + "schemaVersion": "raw-vault-recovery-receipt-v1", + "runId": replay_run_id, + "status": "recovered", + "cleanedVaultCount": len(recovered.get("cleanedVaultIds", [])), + "closedMissingVaultCount": len( + recovered.get("closedMissingVaultIds", []) + ), + "cleanedOrphanCount": len(recovered.get("cleanedOrphanIds", [])), + } + except BaseException as error: + migration_failed = True + result.update( + { + "status": "failed_raw_migration", + "ok": False, + "errors": [getattr(error, "code", "RAW_MIGRATION_FAILED")], + } + ) + elif cas_records: + migration_failed = True result.update( { - "status": "failed_raw_cleanup", + "status": "failed_raw_migration", "ok": False, - "errors": [getattr(error, "code", "RAW_CLEANUP_FAILED")], + "errors": ["RAW_MIGRATION_MISSING"], } ) - else: - try: - for sample_id, cas, cas_state in completed_cas: - final_state = cas.transition( - expected_revision=cas_state["revision"], - expected_attempt=1, - expected_candidate_version=1, - state="COMPLETED", - result={"status": "completed"}, - safe_summary={"rawCleanup": "closed"}, - cleanup_state="closed", + + final_cleanup_state = "failed" if migration_failed else "migrated" + for cas_record in cas_records: + cas = cas_record["cas"] + cas_state = cas_record.get("state") + latest = getattr(cas, "latest", None) + if callable(latest): + try: + latest_state = latest() + except BaseException as error: + result.update( + { + "status": "failed_cas_conflict", + "ok": False, + "errors": [getattr(error, "code", "CAS_RECOVERY_FAILED")], + } ) - for sample_result in result["samples"]: - if sample_result.get("sampleId") == sample_id: - sample_result["status"] = "completed" - sample_result["casRevision"] = final_state["revision"] - break - except (CasConflictError, CasRecoveryError) as error: + continue + if latest_state is not None: + cas_state = latest_state + cas_record["state"] = latest_state + if not isinstance(cas_state, Mapping): + result.update( + { + "status": "failed_cas_conflict", + "ok": False, + "errors": ["CAS_RECOVERY_FAILED"], + } + ) + continue + current_state = cas_state["state"] + if current_state in {"COMPLETED", "FAILED"}: + result.update( + { + "status": "failed_cas_conflict", + "ok": False, + "errors": ["CAS_TERMINAL_STATE_UNEXPECTED"], + } + ) + continue + raw_disposition_ok = ( + not migration_failed + and migration_receipt is not None + and migration_receipt.get("status") == "migrated" + ) + target_state = ( + "COMPLETED" + if ( + result.get("ok") is True + and result.get("status") == "completed" + and raw_disposition_ok + and current_state == "JUDGE_COMPLETED" + ) + else "FAILED" + ) + try: + final_safe_summary = {"rawDisposition": final_cleanup_state} + if isinstance(cas_record.get("semanticDiagnostics"), Mapping): + final_safe_summary["semanticDiagnostics"] = copy.deepcopy( + cas_record["semanticDiagnostics"] + ) + if isinstance(cas_record.get("semanticDiagnostic"), Mapping): + final_safe_summary["semanticDiagnostic"] = copy.deepcopy( + cas_record["semanticDiagnostic"] + ) + if isinstance(cas_record.get("judgeDiagnostic"), Mapping): + final_safe_summary["judgeDiagnostic"] = copy.deepcopy( + cas_record["judgeDiagnostic"] + ) + final_state = cas.transition( + expected_revision=cas_state["revision"], + expected_attempt=1, + expected_candidate_version=1, + state=target_state, + result={ + "status": "completed" if target_state == "COMPLETED" else "failed", + "cleanupState": final_cleanup_state, + }, + safe_summary=final_safe_summary, + cleanup_state=final_cleanup_state, + ) + except BaseException as error: result.update( { "status": "failed_cas_conflict", @@ -1066,13 +1423,46 @@ def _execute_production( "errors": [getattr(error, "code", "CAS_CONFLICT")], } ) - if cleanup_receipt is not None: - result["rawCleanup"] = { - "status": cleanup_receipt["status"], - "receiptSha256": canonical_sha256(cleanup_receipt), - } - result["budgetLedger"] = ledger.snapshot() - result["budgetLedgerSha256"] = canonical_sha256(result["budgetLedger"]) - atomic_write_json(resolved_output / "manifest.json", result) - (resolved_output / "manifest.json").chmod(0o600) + continue + cas_record["state"] = final_state + sample_id = str(cas_record["sampleId"]) + for sample_result in result["samples"]: + if sample_result.get("sampleId") != sample_id: + continue + sample_result["casRevision"] = final_state["revision"] + if target_state == "COMPLETED": + sample_result["status"] = "completed" + elif sample_result.get("status") in {"running", "pending_raw_cleanup"}: + sample_result["status"] = ( + "failed_raw_migration" if migration_failed else "failed_system" + ) + break + + if migration_receipt is not None: + result["rawDisposition"] = { + "status": migration_receipt["status"], + "archiveId": migration_receipt["archiveId"], + "archiveSha256": migration_receipt["archiveSha256"], + "fileCount": migration_receipt["fileCount"], + "totalBytes": migration_receipt["totalBytes"], + "retentionPolicy": migration_receipt["retentionPolicy"], + "receiptSha256": canonical_sha256(migration_receipt), + } + elif recovery_receipt is not None: + result["rawDisposition"] = { + "status": recovery_receipt["status"], + "receiptSha256": canonical_sha256(recovery_receipt), + } + elif migration_failed: + result["rawDisposition"] = {"status": "failed"} + if result.get("ok") is not True or result.get("status") != "completed": + # 原子写成功后的 chmod/后续收口仍可能抛 OSError;失败 manifest 发布前必须 + # 撤销 Gate 文件,消费者不能只看到一个与最终终态冲突的旧输入。 + (resolved_output / "gate-input.json").unlink(missing_ok=True) + result["budgetLedger"] = ledger.snapshot() + result["budgetLedgerSha256"] = canonical_sha256(result["budgetLedger"]) + atomic_write_json(resolved_output / "manifest.json", result) + (resolved_output / "manifest.json").chmod(0o600) + finally: + restore_termination_handlers(interrupt_handlers) return result diff --git a/.claude/skills/replay-eval/scripts/run_writer_replay/sample.py b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/sample.py similarity index 96% rename from .claude/skills/replay-eval/scripts/run_writer_replay/sample.py rename to .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/sample.py index 73f9b5c..a159f44 100644 --- a/.claude/skills/replay-eval/scripts/run_writer_replay/sample.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay/sample.py @@ -66,6 +66,7 @@ from writer_contract import ( # noqa: E402 build_writer_creative_input, calculate_target_chars, han_count, + pattern_references_for_arm, retrieval_identity, validate_writer_context, validate_writer_draft, @@ -477,9 +478,16 @@ def _build_arm_contexts( context_input.get("outputContract"), "writerContextInput.outputContract" ), token_budget=token_budget, - pattern_references=_sequence( - context_input.get("patternReferences", []), - "writerContextInput.patternReferences", + # 分臂唯一事实源在合同模块(pattern_references_for_arm):loader 已把 C 臂 + # 候选冻结进 writerContextInput.patternReferences,真写时按当前臂分配—— + # A 恒空、B/C 拿候选。不能把同一份候选原样喂给所有臂,否则 A 臂混入范式卡 + # 破坏单变量对照;也不能在回放端另写一套分臂,否则与装配端语义漂移。 + pattern_references=pattern_references_for_arm( + arm, + _sequence( + context_input.get("patternReferences", []), + "writerContextInput.patternReferences", + ), ), generated_at=str(context_input.get("generatedAt") or ""), evidence_strategy=EVIDENCE_STRATEGIES[arm], @@ -690,6 +698,9 @@ def _prepare_sample( "recentChapters": context_input.get("recentChapters", []), "retrievalResult": context_input.get("retrievalResult", {}), "narrativeState": context_input.get("narrativeState", {}), + # 范式卡内容会真实进入 B/C 臂写手输入,必须与其它冻结证据接受同一套 + # 目标章事实扫描;否则公共卡误含未来事实时会绕过泄漏门禁。 + "patternReferences": context_input.get("patternReferences", []), }, }, leakage.get("targetFacts"), diff --git a/.claude/skills/replay-eval/scripts/test_fine_outline_detector.py b/.claude/skills/evaluate-frozen-replay/scripts/test_fine_outline_detector.py similarity index 96% rename from .claude/skills/replay-eval/scripts/test_fine_outline_detector.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_fine_outline_detector.py index abb13d3..83ca4c7 100644 --- a/.claude/skills/replay-eval/scripts/test_fine_outline_detector.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_fine_outline_detector.py @@ -75,7 +75,11 @@ class FineOutlineDetectorTest(unittest.TestCase): } self.assertTrue(validate_detector_report(report, "blind-1")["ok"]) - skill_path = pathlib.Path(__file__).resolve().parents[2] / "detect" / "SKILL.md" + skill_path = ( + pathlib.Path(__file__).resolve().parents[2] + / "check-content-consistency" + / "SKILL.md" + ) skill = skill_path.read_text(encoding="utf-8") self.assertNotIn("要单列为资料覆盖发现", skill) self.assertIn("归 judge/eval", skill) diff --git a/.claude/skills/evaluate-frozen-replay/scripts/test_gate_input_builder.py b/.claude/skills/evaluate-frozen-replay/scripts/test_gate_input_builder.py new file mode 100644 index 0000000..e9721b9 --- /dev/null +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_gate_input_builder.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +"""GateInputBuilder 的安全输入与同 ID 混淆项测试。""" + +from __future__ import annotations + +import copy +import pathlib +import sys +import unittest + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" +for _import_dir in (SCRIPT_DIR, QUALITY_GATE_DIR): + if str(_import_dir) not in sys.path: + sys.path.insert(0, str(_import_dir)) + +from gate_input_builder import GateInputBuildError, GateInputBuilder, canonical_sha256 +from test_writer_gate import build_input, source_bundle +from writer_rubric import RUBRIC_POLICY_VERSION + + +class GateInputBuilderTest(unittest.TestCase): + @staticmethod + def _rehash_wrapper(wrapper): + """只重签 wrapper,故意不替攻击者重签生产内层回执。""" + + wrapper["receiptSha256"] = canonical_sha256( + {key: value for key, value in wrapper.items() if key != "receiptSha256"} + ) + + def test_builder_emits_v3_policy_binding_and_complete_confounders(self): + gate_input = build_input(source_bundle()) + self.assertEqual(gate_input["schemaVersion"], "writer-gate-input-v3") + self.assertEqual(gate_input["rubricPolicyVersion"], RUBRIC_POLICY_VERSION) + self.assertTrue(gate_input["builderReceiptSha256"].startswith("sha256:")) + self.assertEqual( + set(gate_input["samples"][0]["confounders"]), + { + "falseNegative", + "falsePositive", + "leakage", + "reviewerInstability", + "newCharactersWithoutCards", + }, + ) + + def test_sensitive_manifest_field_is_rejected(self): + bundle = source_bundle() + bundle["manifest"]["samples"][0]["candidateBody"] = "正文不得进入安全 manifest" + with self.assertRaisesRegex(GateInputBuildError, "敏感字段"): + GateInputBuilder().build(**bundle) + + def test_same_id_and_candidate_binding_are_mandatory(self): + bundle = source_bundle() + verdict = bundle["detector_reports"]["sample-1"]["A"]["assertionVerdicts"][0] + verdict["assertionId"] = "forged-id" + report = bundle["detector_reports"]["sample-1"]["A"] + from gate_input_builder import canonical_sha256 + report["reportSha256"] = canonical_sha256( + {key: value for key, value in report.items() if key != "reportSha256"} + ) + gate_input = build_input(bundle) + self.assertTrue(gate_input["samples"][0]["systemFailure"]) + self.assertFalse(gate_input["samples"][0]["schemaValid"]) + + def test_detector_unknown_is_quality_signal_and_reduces_c_coverage(self): + """合法 needs_evidence 可进入 Gate;unknown 不计入 C 臂硬约束覆盖率。""" + + bundle = source_bundle(1, gate="A") + report = bundle["detector_reports"]["sample-1"]["C"] + report["status"] = "needs_evidence" + report["hardConstraintVerdicts"][0]["verdict"] = "unknown" + report["reportSha256"] = canonical_sha256( + {key: value for key, value in report.items() if key != "reportSha256"} + ) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertTrue(sample["schemaValid"], sample) + self.assertFalse(sample["systemFailure"], sample) + self.assertEqual(sample["cArm"]["hardConstraintCoverage"], 0.0) + + def test_empty_wrong_role_and_unbound_inner_execution_receipts_fail_closed(self): + """wrapper 自哈希不能替代生产 runtime 原始 ExecutionReceipt 绑定。""" + + mutations = { + "empty": lambda wrapper: wrapper.update( + {"executionReceipts": [], "executionReceiptSha256": []} + ), + "wrong_role": lambda wrapper: wrapper["executionReceipts"][0].update( + {"adapterRole": "semantic_detector"} + ), + "unbound": lambda wrapper: wrapper["executionReceipts"][0].update( + {"inputSha256": "sha256:" + "f" * 64} + ), + } + expected_reason = { + "empty": "A_writer_inner_receipts_empty", + "wrong_role": "A_writer_inner_receipt_role_mismatch", + "unbound": "A_writer_inner_receipt_hash_mismatch", + } + for name, mutate in mutations.items(): + bundle = source_bundle(1, gate="A") + wrapper = bundle["execution_receipts"]["sample-1"]["A"]["writer"] + mutate(wrapper) + self._rehash_wrapper(wrapper) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + with self.subTest(name=name): + self.assertTrue(sample["systemFailure"]) + self.assertIn(expected_reason[name], sample["systemFailureReasons"]) + + def test_writer_and_detector_retry_chains_bind_to_final_receipt(self): + """1..3 条重试回执均保留,semantic 报告必须绑定最后一条。""" + + bundle = source_bundle(1, gate="A") + for arm in ("A", "B", "C"): + for role in ("writer", "semantic_detector"): + wrapper = bundle["execution_receipts"]["sample-1"][arm][role] + retry = copy.deepcopy(wrapper["executionReceipts"][0]) + retry["invocationId"] = f"{role}-{arm}-retry-2" + wrapper["executionReceipts"].append(retry) + wrapper["executionReceiptSha256"].append(canonical_sha256(retry)) + self._rehash_wrapper(wrapper) + detector = bundle["detector_reports"]["sample-1"][arm] + detector["modelReceiptSha256"] = bundle["execution_receipts"]["sample-1"][arm][ + "semantic_detector" + ]["executionReceiptSha256"][-1] + detector["reportSha256"] = canonical_sha256( + {key: value for key, value in detector.items() if key != "reportSha256"} + ) + + gate_input = build_input(bundle) + + self.assertFalse(gate_input["samples"][0]["systemFailure"], gate_input["samples"][0]) + + def test_nonfinal_known_cost_api_error_receipt_can_precede_detector_success(self): + """前置 API 失败必须保留审计;只有末笔成功且报告绑定末笔时可进入 Gate。""" + + bundle = source_bundle(1, gate="A") + wrapper = bundle["execution_receipts"]["sample-1"]["A"]["semantic_detector"] + transient = copy.deepcopy(wrapper["executionReceipts"][0]) + transient.update( + { + "invocationId": "semantic-A-transient-api-error", + "actualModelId": None, + "modelMatch": False, + "totalCostUsd": "0.000000", + "terminalReason": "api_error", + "isError": True, + "apiErrorStatus": None, + "exitCode": 1, + "structuredOutputSha256": None, + } + ) + wrapper["executionReceipts"].insert(0, transient) + wrapper["executionReceiptSha256"].insert(0, canonical_sha256(transient)) + self._rehash_wrapper(wrapper) + + gate_input = build_input(bundle) + + self.assertFalse(gate_input["samples"][0]["systemFailure"], gate_input["samples"][0]) + + def test_final_api_error_receipt_remains_system_failure(self): + """API 错误只能出现在最终成功前;把它放在末笔不得借重试合同放行。""" + + bundle = source_bundle(1, gate="A") + wrapper = bundle["execution_receipts"]["sample-1"]["A"]["semantic_detector"] + final_error = copy.deepcopy(wrapper["executionReceipts"][0]) + final_error.update( + { + "actualModelId": None, + "modelMatch": False, + "totalCostUsd": "0.000000", + "terminalReason": "api_error", + "isError": True, + "apiErrorStatus": None, + "exitCode": 1, + "structuredOutputSha256": None, + } + ) + wrapper["executionReceipts"] = [final_error] + wrapper["executionReceiptSha256"] = [canonical_sha256(final_error)] + self._rehash_wrapper(wrapper) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertTrue(sample["systemFailure"]) + self.assertIn( + "A_semantic_detector_inner_receipt_not_successful", + sample["systemFailureReasons"], + ) + + def test_four_writer_receipts_fail_closed(self): + """有界修订最多三次,第四条回执不得借审计链绕过。""" + + bundle = source_bundle(1, gate="A") + wrapper = bundle["execution_receipts"]["sample-1"]["A"]["writer"] + for invocation in range(2, 6): + retry = copy.deepcopy(wrapper["executionReceipts"][0]) + retry["invocationId"] = f"writer-A-retry-{invocation}" + wrapper["executionReceipts"].append(retry) + wrapper["executionReceiptSha256"].append(canonical_sha256(retry)) + self._rehash_wrapper(wrapper) + + gate_input = build_input(bundle) + + self.assertTrue(gate_input["samples"][0]["systemFailure"]) + self.assertIn( + "A_writer_inner_receipt_count_invalid", + gate_input["samples"][0]["systemFailureReasons"], + ) + + def test_judge_score_tampering_without_original_hash_resign_fails_closed(self): + """篡改稳定报告分数但保留生产原始 hash 时不得进入 Gate 分数恢复。""" + + bundle = source_bundle(1, gate="A") + report = bundle["judge_reports"]["sample-1"] + original_report_hash = report["reportSha256"] + report["candidateScores"][0]["scores"]["setting_entity_fidelity"] = 999 + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertEqual(report["reportSha256"], original_report_hash) + self.assertTrue(sample["systemFailure"]) + self.assertFalse(sample["schemaValid"]) + self.assertTrue( + any("judgeReports.sample-1.reportSha256 不一致" in reason for reason in sample["systemFailureReasons"]) + ) + + def test_judge_report_resign_cannot_replace_original_panel_receipt_binding(self): + """攻击者重签报告自哈希后,仍不能替换生产 panel 的原始模型回执集合。""" + + bundle = source_bundle(1, gate="A") + report = bundle["judge_reports"]["sample-1"] + report["modelReceiptSha256"] = "sha256:" + "e" * 64 + report["reportSha256"] = canonical_sha256( + {key: value for key, value in report.items() if key != "reportSha256"} + ) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertTrue(sample["systemFailure"]) + self.assertFalse(sample["schemaValid"]) + self.assertTrue( + any("未绑定原始 panel 回执" in reason for reason in sample["systemFailureReasons"]) + ) + + def test_judge_report_cannot_swap_preregistered_scenario_policy(self): + bundle = source_bundle(1, gate="A") + panel = bundle["judge_reports"]["sample-1"] + panel["reviewerReports"][0]["scenario"] = "character_dialogue" + panel["reportSha256"] = canonical_sha256( + {key: value for key, value in panel.items() if key != "reportSha256"} + ) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertTrue(sample["systemFailure"]) + self.assertFalse(sample["schemaValid"]) + self.assertTrue( + any( + "未绑定当前样本场景评分策略" in reason + for reason in sample["systemFailureReasons"] + ) + ) + + def test_legal_score_tampering_and_report_resign_still_fails_receipt_binding(self): + """合法 9.5 分即使重签 panel 自哈希,也必须与模型原始 structured output 冲突。""" + + bundle = source_bundle(1, gate="A") + report = bundle["judge_reports"]["sample-1"] + report["candidateScores"][0]["scores"]["setting_entity_fidelity"] = 9.5 + report["reportSha256"] = canonical_sha256( + {key: value for key, value in report.items() if key != "reportSha256"} + ) + + gate_input = build_input(bundle) + + sample = gate_input["samples"][0] + self.assertTrue(sample["systemFailure"]) + self.assertFalse(sample["schemaValid"]) + self.assertTrue( + any("模型原始 structured output" in reason for reason in sample["systemFailureReasons"]) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py b/.claude/skills/evaluate-frozen-replay/scripts/test_load_writer_reference_work.py similarity index 97% rename from .claude/skills/replay-eval/scripts/test_load_writer_reference_work.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_load_writer_reference_work.py index 61c1cce..873a194 100644 --- a/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_load_writer_reference_work.py @@ -9,14 +9,15 @@ import pathlib import stat import sys import tempfile +import types import unittest from datetime import datetime, timedelta, timezone from unittest.mock import patch SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -READ_CONTEXT_SCRIPTS = SCRIPT_DIR.parents[1] / "read-context" / "scripts" -DETECT_SCRIPTS = SCRIPT_DIR.parents[1] / "detect" / "scripts" -CONTINUATION_SCRIPTS = SCRIPT_DIR.parents[1] / "continuation" / "scripts" +READ_CONTEXT_SCRIPTS = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" +DETECT_SCRIPTS = SCRIPT_DIR.parents[1] / "check-content-consistency" / "scripts" +CONTINUATION_SCRIPTS = SCRIPT_DIR.parents[1] / "write-next-chapter" / "scripts" for _path in (SCRIPT_DIR, READ_CONTEXT_SCRIPTS, DETECT_SCRIPTS, CONTINUATION_SCRIPTS): sys.path.insert(0, str(_path)) @@ -47,7 +48,7 @@ FILE_HASH = "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4" SOURCE_HASH = f"sha256:{FILE_HASH}" SOURCE_VERSION = f"raw-file-v1:{SOURCE_HASH}" EXPECTED_CLAUDE_EXECUTABLE_SHA256 = ( - "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629" + "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c" ) @@ -374,6 +375,36 @@ class LoadWriterReferenceWorkTest(unittest.TestCase): _refresh_self_hash(budget) self.selectors = json.loads(SELECTOR_PATH.read_text(encoding="utf-8")) + def test_default_pattern_searcher_binds_loader_dsn_and_tenant(self): + """公共范式检索不得退回 search 模块的默认连接常量。""" + + calls = [] + + def fake_search_cards(intent, **kwargs): + calls.append((intent, kwargs)) + return [] + + fake_module = types.SimpleNamespace(search_cards=fake_search_cards) + with patch.dict(sys.modules, {"search": fake_module}): + searcher = loader._default_pattern_card_searcher( + dsn="postgresql://test/isolated", + tenant_id=9, + ) + self.assertEqual(searcher("转折", ttype="craft", top=2), []) + + self.assertEqual(calls[0][0], "转折") + self.assertEqual( + calls[0][1], + { + "scope": "public_pattern", + "ttype": "craft", + "purpose": "generation", + "top": 2, + "dsn": "postgresql://test/isolated", + "tenant_id": 9, + }, + ) + def test_resolves_exact_canonical_name_and_alias(self): """canonical name 与精确 alias 都必须唯一解析,不能模糊挑卡。""" @@ -623,7 +654,7 @@ class LoadWriterReferenceWorkTest(unittest.TestCase): self.assertEqual(budget["status"], "approved") self.assertEqual( budget["plannedCalls"], - {"writer": 45, "semantic_detector": 15, "blind_judge": 15}, + {"writer": 60, "semantic_detector": 24, "blind_judge": 45}, ) def test_executable_hash_must_remain_full_and_bound(self): @@ -664,7 +695,7 @@ class LoadWriterReferenceWorkTest(unittest.TestCase): # (b) 带时区的 ISO,能 fromisoformat 解析且 tzinfo 非空 parsed = datetime.fromisoformat(stamped["retainUntil"]) self.assertIsNotNone(parsed.tzinfo) - # (c) 解析后在未来、且距 now 不超过 24 小时(实际约等于 6 小时) + # (c) 解析后在未来、且距 now 不超过 24 小时(实际约等于 23 小时) now = datetime.now(timezone.utc) self.assertGreater(parsed, now) self.assertLessEqual(parsed - now, timedelta(hours=24)) diff --git a/.claude/skills/replay-eval/scripts/test_pattern_reference_injection.py b/.claude/skills/evaluate-frozen-replay/scripts/test_pattern_reference_injection.py similarity index 97% rename from .claude/skills/replay-eval/scripts/test_pattern_reference_injection.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_pattern_reference_injection.py index 7baa11f..607cf35 100644 --- a/.claude/skills/replay-eval/scripts/test_pattern_reference_injection.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_pattern_reference_injection.py @@ -15,12 +15,19 @@ import unittest SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -READ_CONTEXT_SCRIPTS = SKILLS_DIR / "read-context" / "scripts" -# 端到端链路测试要导入回放包(run_writer_replay.sample),其依赖散布在 runtime 与 -# quality-gate 两个 skill 的 scripts 目录,路径口径与 test_run_writer_replay 保持一致。 -RUNTIME_SCRIPTS = SKILLS_DIR / "runtime" / "scripts" -QUALITY_GATE_SCRIPTS = SKILLS_DIR / "quality-gate" / "scripts" -for _path in (SCRIPT_DIR, READ_CONTEXT_SCRIPTS, RUNTIME_SCRIPTS, QUALITY_GATE_SCRIPTS): +READ_CONTEXT_SCRIPTS = SKILLS_DIR / "assemble-context" / "scripts" +# 端到端链路测试要导入回放包(run_writer_replay.sample),其依赖执行、证据与 +# 评分三个 Skill 的 scripts 目录,路径口径与 test_run_writer_replay 保持一致。 +EXECUTION_SCRIPTS = SKILLS_DIR / "execute-claude-task" / "scripts" +EVIDENCE_SCRIPTS = SKILLS_DIR / "record-run-evidence" / "scripts" +QUALITY_GATE_SCRIPTS = SKILLS_DIR / "score-content-quality" / "scripts" +for _path in ( + SCRIPT_DIR, + READ_CONTEXT_SCRIPTS, + EXECUTION_SCRIPTS, + EVIDENCE_SCRIPTS, + QUALITY_GATE_SCRIPTS, +): sys.path.insert(0, str(_path)) import load_writer_reference_work as loader # noqa: E402 diff --git a/.claude/skills/replay-eval/scripts/test_refresh_runtime_probe.py b/.claude/skills/evaluate-frozen-replay/scripts/test_refresh_runtime_probe.py similarity index 99% rename from .claude/skills/replay-eval/scripts/test_refresh_runtime_probe.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_refresh_runtime_probe.py index cd2e27d..3dd010d 100644 --- a/.claude/skills/replay-eval/scripts/test_refresh_runtime_probe.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_refresh_runtime_probe.py @@ -22,12 +22,12 @@ from typing import Any, Mapping from unittest import mock SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" +EXECUTION_DIR = SCRIPT_DIR.parents[1] / "execute-claude-task" / "scripts" +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" if str(SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(SCRIPT_DIR)) -if str(RUNTIME_DIR) not in sys.path: - sys.path.insert(0, str(RUNTIME_DIR)) +if str(EXECUTION_DIR) not in sys.path: + sys.path.insert(0, str(EXECUTION_DIR)) if str(QUALITY_GATE_DIR) not in sys.path: sys.path.insert(0, str(QUALITY_GATE_DIR)) @@ -327,7 +327,7 @@ class ProbeGateIntegrationTest(unittest.TestCase): self.assertIsNone(blocked_status) self.assertIsNotNone(gate_result) # 门放行后回传的计划/上限来自配置预算合同。 - self.assertEqual(gate_result["plannedCalls"]["writer"], 45) + self.assertEqual(gate_result["plannedCalls"]["writer"], 60) self.assertEqual(gate_result["maxCalls"]["writer"], 150) def test_stale_config_still_blocked_by_gate_before_refresh(self): diff --git a/.claude/skills/replay-eval/scripts/test_run_replay.py b/.claude/skills/evaluate-frozen-replay/scripts/test_run_replay.py similarity index 99% rename from .claude/skills/replay-eval/scripts/test_run_replay.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_run_replay.py index 9c8dcfb..d08f207 100644 --- a/.claude/skills/replay-eval/scripts/test_run_replay.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_run_replay.py @@ -12,7 +12,7 @@ import unittest from unittest.mock import patch SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" sys.path.insert(0, str(SCRIPT_DIR)) sys.path.insert(0, str(QUALITY_GATE_DIR)) from run_replay import _parse_args, _planner_prompt, run_replay as _run_replay # noqa: E402 diff --git a/.claude/skills/replay-eval/scripts/test_run_writer_replay.py b/.claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py similarity index 99% rename from .claude/skills/replay-eval/scripts/test_run_writer_replay.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py index d2ad179..43fb931 100644 --- a/.claude/skills/replay-eval/scripts/test_run_writer_replay.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py @@ -21,10 +21,17 @@ from datetime import datetime, timedelta, timezone SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" -RUNTIME_DIR = SKILLS_DIR / "runtime" / "scripts" -QUALITY_GATE_DIR = SKILLS_DIR / "quality-gate" / "scripts" -for import_path in (SCRIPT_DIR, READ_CONTEXT_DIR, RUNTIME_DIR, QUALITY_GATE_DIR): +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" +EXECUTION_DIR = SKILLS_DIR / "execute-claude-task" / "scripts" +EVIDENCE_DIR = SKILLS_DIR / "record-run-evidence" / "scripts" +QUALITY_GATE_DIR = SKILLS_DIR / "score-content-quality" / "scripts" +for import_path in ( + SCRIPT_DIR, + READ_CONTEXT_DIR, + EXECUTION_DIR, + EVIDENCE_DIR, + QUALITY_GATE_DIR, +): sys.path.insert(0, str(import_path)) import run_writer_replay as replay_module # noqa: E402 @@ -1750,14 +1757,14 @@ class WriterReplayDryRunTest(unittest.TestCase): ) self.assertEqual(gate_config["commonControls"]["modelVersion"], "claude-opus-4-8[1m]") self.assertIn("writer-runtime-v1", gate_config["commonControls"]["adapterVersion"]) - self.assertIn("2.1.211", gate_config["commonControls"]["adapterVersion"]) + self.assertIn("2.1.231", gate_config["commonControls"]["adapterVersion"]) executable_sha256 = gate_config["executionAuthorization"]["runtimeProbe"][ "claudeExecutableSha256" ] self.assertEqual(len(executable_sha256), 64) self.assertEqual( executable_sha256, - "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", + "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c", ) self.assertIn(executable_sha256, gate_config["commonControls"]["adapterVersion"]) self.assertEqual( diff --git a/.claude/skills/replay-eval/scripts/test_writer_eval_preregister.py b/.claude/skills/evaluate-frozen-replay/scripts/test_writer_eval_preregister.py similarity index 100% rename from .claude/skills/replay-eval/scripts/test_writer_eval_preregister.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_writer_eval_preregister.py diff --git a/.claude/skills/replay-eval/scripts/test_writer_gate.py b/.claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py similarity index 92% rename from .claude/skills/replay-eval/scripts/test_writer_gate.py rename to .claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py index c55ee91..7e56d70 100644 --- a/.claude/skills/replay-eval/scripts/test_writer_gate.py +++ b/.claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py @@ -11,9 +11,9 @@ import tempfile import unittest SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -for _import_dir in (SCRIPT_DIR, QUALITY_GATE_DIR, RUNTIME_DIR): +QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "score-content-quality" / "scripts" +EVIDENCE_DIR = SCRIPT_DIR.parents[1] / "record-run-evidence" / "scripts" +for _import_dir in (SCRIPT_DIR, QUALITY_GATE_DIR, EVIDENCE_DIR): if str(_import_dir) not in sys.path: sys.path.insert(0, str(_import_dir)) @@ -24,7 +24,12 @@ from writer_gate import ( issue_gate_report_and_receipt, verify_writer_gate_receipt, ) -from writer_rubric import DIMENSIONS, adjudicate_structured_reviews +from writer_rubric import ( + DIMENSIONS, + RUBRIC_POLICY_VERSION, + SCENARIO_DIMENSION, + adjudicate_structured_reviews, +) SCENARIOS = ( @@ -84,18 +89,21 @@ def _reviewer_raw_report( *, run_id: str, sample_id: str, + scenario: str, reviewer: int, order: list[str], candidates: dict[str, str], ) -> dict: - """构造 adapter 已绑定身份与证据位置的 blind judge v3 报告。""" + """构造 adapter 已绑定场景策略、身份与证据位置的 blind judge v4 报告。""" blind_to_arm = {"blind-1": "A", "blind-2": "B", "blind-3": "C"} base_scores = {"A": 7.0, "B": 2.0, "C": 7.5} return { - "schemaVersion": "blind-judge-report-v3", + "schemaVersion": "blind-judge-report-v4", "runId": run_id, "sampleId": sample_id, + "scenario": scenario, + "rubricPolicyVersion": RUBRIC_POLICY_VERSION, "reviewerInvocationId": f"{sample_id}-reviewer-{reviewer}", "blindInputSha256": canonical_sha256( {"sampleId": sample_id, "reviewer": reviewer, "order": order} @@ -221,11 +229,12 @@ def source_bundle(sample_count: int = 10, *, gate: str = "B") -> dict: arm: canonical_sha256({"sampleId": sample_id, "arm": arm}) for arm in ("A", "B", "C") } + scenario = SCENARIOS[index % len(SCENARIOS)] manifest_samples.append( { "sampleId": sample_id, "workId": 1 if index < 5 else 2, - "scenario": SCENARIOS[index % len(SCENARIOS)], + "scenario": scenario, "newCharacterRatio": (0.0, 0.25, 0.75, None)[index % 4], "newCharacterBasis": {"absentBeforeAsOf": []}, "comparableAssertionIds": ["assertion-1"], @@ -319,6 +328,7 @@ def source_bundle(sample_count: int = 10, *, gate: str = "B") -> dict: _reviewer_raw_report( run_id=run_id, sample_id=sample_id, + scenario=scenario, reviewer=1, order=["blind-1", "blind-2", "blind-3"], candidates=candidates, @@ -326,6 +336,7 @@ def source_bundle(sample_count: int = 10, *, gate: str = "B") -> dict: _reviewer_raw_report( run_id=run_id, sample_id=sample_id, + scenario=scenario, reviewer=2, order=["blind-3", "blind-2", "blind-1"], candidates=candidates, @@ -388,6 +399,7 @@ def source_bundle(sample_count: int = 10, *, gate: str = "B") -> dict: # WHY: 模型原始 draft 按 reviewer 顺序与 reviewerReports 一一对应,随报告进 builder, # 供 builder 只读复核 canonical(draft)==回执 structuredOutputSha256,恢复评分内容↔原始输出绑定。 "reviewerStructuredOutputs": copy.deepcopy(reviewer_drafts), + "reviewerFinalReceiptIndexes": [0, 1], "modelReceiptSha256": canonical_sha256( execution_receipts[sample_id]["A"]["blind_judge"][ "executionReceiptSha256" @@ -504,6 +516,7 @@ def _rebuild_judge_source(bundle: dict, sample_id: str, mutate) -> None: **panel, "reviewerReports": bound_reports, "reviewerStructuredOutputs": reviewer_drafts, + "reviewerFinalReceiptIndexes": [0, 1], "modelReceiptSha256": canonical_sha256(receipt_hashes), } bundle["judge_reports"][sample_id] = { @@ -514,7 +527,7 @@ def _rebuild_judge_source(bundle: dict, sample_id: str, mutate) -> None: class WriterGateTest(unittest.TestCase): def test_manual_gate_input_is_rejected(self): - with self.assertRaisesRegex(ValueError, "writer-gate-input-v2"): + with self.assertRaisesRegex(ValueError, "writer-gate-input-v3"): decide_gate({"gate": "A", "samples": []}) def test_system_failure_precedes_insufficient_evidence(self): @@ -569,6 +582,39 @@ class WriterGateTest(unittest.TestCase): self.assertEqual(decision["status"], "failed") self.assertIn("zero_style_consistency_regression", decision["reasons"]) + def test_scenario_regression_cannot_be_hidden_by_other_scenario_types(self): + bundle = source_bundle() + for sample in bundle["manifest"]["samples"]: + if sample["scenario"] != "battle": + continue + candidate_c = sample["arms"]["C"]["candidateSha256"] + + def lower_battle_execution(reviewer): + for row in reviewer["candidateScores"]: + if row["candidateSha256"] == candidate_c: + row["scores"][SCENARIO_DIMENSION]["score"] = 6.0 + + _rebuild_judge_source( + bundle, + sample["sampleId"], + lower_battle_execution, + ) + + decision = decide_gate(build_input(bundle)) + + self.assertEqual(decision["status"], "failed") + self.assertIn("battle_scenario_execution_regression", decision["reasons"]) + battle = decision["metrics"]["scenarioExecutionByType"]["battle"] + self.assertEqual(battle["sampleCount"], 2) + self.assertEqual(battle["stableSampleCount"], 2) + self.assertEqual(battle["cMinusAAverageDelta"], -1.0) + self.assertEqual( + decision["metrics"]["scenarioExecutionByType"]["character_dialogue"][ + "cMinusAAverageDelta" + ], + 0.5, + ) + def test_non_passed_gate_b_does_not_issue_receipt(self): bundle = source_bundle() for sample_id in list(bundle["judge_reports"]): diff --git a/.claude/skills/replay-eval/scripts/write_report.py b/.claude/skills/evaluate-frozen-replay/scripts/write_report.py similarity index 100% rename from .claude/skills/replay-eval/scripts/write_report.py rename to .claude/skills/evaluate-frozen-replay/scripts/write_report.py diff --git a/.claude/skills/replay-eval/scripts/writer_eval_preregister.py b/.claude/skills/evaluate-frozen-replay/scripts/writer_eval_preregister.py similarity index 100% rename from .claude/skills/replay-eval/scripts/writer_eval_preregister.py rename to .claude/skills/evaluate-frozen-replay/scripts/writer_eval_preregister.py diff --git a/.claude/skills/execute-claude-task/SKILL.md b/.claude/skills/execute-claude-task/SKILL.md new file mode 100644 index 0000000..2d65c9b --- /dev/null +++ b/.claude/skills/execute-claude-task/SKILL.md @@ -0,0 +1,33 @@ +--- +name: execute-claude-task +description: 以冻结执行配置在 fresh 沙箱子进程中调用 Claude CLI,并校验模型、期限、结构化输出和联合执行回执。Writer、detector 或 judge 需要一次受控 Claude 调用时使用;不负责保存 raw、登记运行或裁决内容。 +disable-model-invocation: true +--- + +# 执行受控 Claude 任务 + +本 Skill 只拥有 Claude CLI 子进程执行。运行证据由 `record-run-evidence` 保存;候选、检测和评分分别由上层业务 Skill 处理。 + +## 入口 + +`scripts/claude_runtime.py` 只接受显式冻结的 `ExecutionProfile`,负责 fresh process、沙箱、最小环境、硬 deadline、结构化输出和联合执行回执。任何模型、预算、schema、prompt、退出状态或输出绑定不完整都失败关闭;错误对象不得携带 stderr 原文。 + +真实调用必须持有 `Popen` 进程句柄并使用独立进程组。deadline、SIGTERM、SIGINT 或父进程异常发生时,先终止并等待整个模型进程组,再向调用方返回失败回执,禁止遗留继续运行或计费的子进程。 + +## 输入与输出 + +- 输入:冻结的可执行文件身份、Claude CLI 版本、完整模型 ID、effort、单次预算、deadline、最大上下文、system prompt、JSON Schema 和业务输入。 +- 输出:经过 schema 与业务校验的结构化结果,以及绑定执行 profile、模型、成本、用量、退出状态和输出哈希的 `ExecutionReceipt`。 +- 持久化回调是可注入依赖;默认真实调用按需转交 `record-run-evidence`,离线 fake 不产生数据库副作用。 + +## 红线 + +- 不从全局 Claude 会话继承业务状态,不静默换模型或供应商。 +- 不自行决定候选是否通过,不写 Candidate、Quality 或 Canonical 数据。 +- 不拥有 CAS、raw vault、运行登记或回执修复实现。 + +## 离线验证 + +```bash +.venv/bin/python .claude/skills/execute-claude-task/scripts/test_claude_runtime.py +``` diff --git a/.claude/skills/runtime/scripts/claude_runtime.py b/.claude/skills/execute-claude-task/scripts/claude_runtime.py similarity index 97% rename from .claude/skills/runtime/scripts/claude_runtime.py rename to .claude/skills/execute-claude-task/scripts/claude_runtime.py index a9529a7..e4db92d 100644 --- a/.claude/skills/runtime/scripts/claude_runtime.py +++ b/.claude/skills/execute-claude-task/scripts/claude_runtime.py @@ -68,6 +68,9 @@ USAGE_COUNT_MAP_FIELDS = { ), } USAGE_STRING_FIELDS = frozenset({"service_tier", "speed", "inference_geo"}) +# modelUsage 每模型条目里的非计数信息字段(CLI 2.1.231 起附带 canonicalModel/provider)。 +# 前向兼容:单独校验形状(非空字符串)后排除出严格数字树;计数字段与 costUSD 核账不放宽。 +MODEL_USAGE_INFO_FIELDS = frozenset({"canonicalModel", "provider"}) class ClaudeRuntimeError(RuntimeError): @@ -847,12 +850,16 @@ def _receipt( def _default_persist_call(event: Mapping[str, Any]): - """按需加载 runtime raw 写入器,保持离线 fake runner 无数据库副作用。""" + """按需加载运行证据写入器,保持离线 fake runner 无数据库副作用。""" import sys - runtime_dir = pathlib.Path(__file__).resolve().parent - if str(runtime_dir) not in sys.path: - sys.path.insert(0, str(runtime_dir)) + evidence_dir = ( + pathlib.Path(__file__).resolve().parents[2] + / "record-run-evidence" + / "scripts" + ) + if str(evidence_dir) not in sys.path: + sys.path.insert(0, str(evidence_dir)) from persist_llm_call import persist_call return persist_call(dict(event)) @@ -1062,11 +1069,19 @@ def run_claude( ): raise ValueError("modelUsage 结构非法") for model_id in model_ids: + for info_key in MODEL_USAGE_INFO_FIELDS: + info_value = model_usage[model_id].get(info_key) + if info_value is not None and ( + not isinstance(info_value, str) or not info_value + ): + raise ValueError( + f"modelUsage.{model_id}.{info_key} 必须是非空字符串" + ) _validate_number_tree( { key: item for key, item in model_usage[model_id].items() - if key != "costUSD" + if key != "costUSD" and key not in MODEL_USAGE_INFO_FIELDS }, f"modelUsage.{model_id}", ) diff --git a/.claude/skills/runtime/scripts/test_claude_runtime.py b/.claude/skills/execute-claude-task/scripts/test_claude_runtime.py similarity index 94% rename from .claude/skills/runtime/scripts/test_claude_runtime.py rename to .claude/skills/execute-claude-task/scripts/test_claude_runtime.py index 006b49c..9ffeed0 100644 --- a/.claude/skills/runtime/scripts/test_claude_runtime.py +++ b/.claude/skills/execute-claude-task/scripts/test_claude_runtime.py @@ -376,6 +376,54 @@ class ClaudeRuntimeTest(unittest.TestCase): self.assertEqual(result.receipt.as_dict()["totalCostUsd"], "0.120000") self.assertEqual(result.receipt.as_dict()["usage"], realistic_usage()) + def test_model_usage_info_fields_are_forward_compatible(self): + """CLI 2.1.231 起 modelUsage 附带 canonicalModel/provider 信息字段:形状合法即放行。""" + + envelope = success_envelope() + envelope["modelUsage"] = { + FULL_MODEL_ID: { + "inputTokens": 10, + "outputTokens": 2, + "costUSD": "0.120000", + "canonicalModel": "claude-opus-4-8", + "provider": "firstParty", + } + } + result = run_claude( + execution_profile(), + {"request": "x"}, + runner=runner_for(envelope), + binding_verifier=lambda _profile: None, + ) + self.assertEqual(result.structured_output, {"value": "ok"}) + self.assertEqual(result.receipt.as_dict()["totalCostUsd"], "0.120000") + + def test_model_usage_info_fields_with_bad_shape_fail_closed(self): + """信息字段形状非法(数字/空串)仍然失败关闭;计数字段不放宽。""" + + for label, canonical, provider in ( + ("canonicalModel 非字符串", 42, "firstParty"), + ("provider 空串", "claude-opus-4-8", ""), + ): + envelope = success_envelope() + envelope["modelUsage"] = { + FULL_MODEL_ID: { + "inputTokens": 10, + "outputTokens": 2, + "costUSD": "0.120000", + "canonicalModel": canonical, + "provider": provider, + } + } + with self.subTest(label=label), self.assertRaises(ClaudeRuntimeError) as raised: + run_claude( + execution_profile(), + {"request": "x"}, + runner=runner_for(envelope), + binding_verifier=lambda _profile: None, + ) + self.assertEqual(raised.exception.primary_code, "WRITER_RECEIPT_INVALID") + def test_usage_rejects_bad_counts_metadata_and_non_json_iterations(self): """usage 的必需计数、已知映射和 metadata 破坏时必须失败关闭。""" diff --git a/.claude/skills/expansion/SKILL.md b/.claude/skills/expand-scene/SKILL.md similarity index 73% rename from .claude/skills/expansion/SKILL.md rename to .claude/skills/expand-scene/SKILL.md index 2fe7cc2..7cd4524 100644 --- a/.claude/skills/expansion/SKILL.md +++ b/.claude/skills/expand-scene/SKILL.md @@ -1,6 +1,6 @@ --- -name: expansion -description: 扩写的功能合同(scenario: expansion,写作槽位)。在既有场景内扩动作链/潜台词/感官细节,不灌水不加设定。 +name: expand-scene +description: 在既有场景内补充动作链、对话潜台词、感官细节和环境互动。用户认为某个场景过薄并明确要求扩写时使用;不新开场景、不改落点、不夹带新设定。 disable-model-invocation: true --- @@ -10,7 +10,7 @@ disable-model-invocation: true ## 元数据驱动 -- 同 `continuation` 的字段合同与字段值约束;扩写后场景仍须满足 `scene.yaml` 的结构合同(目标/推进/收束完整)。 +- 同 `write-next-chapter` 的字段合同与字段值约束;扩写后场景仍须满足 `scene.yaml` 的结构合同(目标/推进/收束完整)。 - 「信息密度」是质量元数据(专题-04 非关键维 information_density):扩写以它为自查尺。 ## 功能约束 diff --git a/.claude/skills/extract-knowledge/SKILL.md b/.claude/skills/extract-chapter-knowledge/SKILL.md similarity index 77% rename from .claude/skills/extract-knowledge/SKILL.md rename to .claude/skills/extract-chapter-knowledge/SKILL.md index 2005e2d..af3bd66 100644 --- a/.claude/skills/extract-knowledge/SKILL.md +++ b/.claude/skills/extract-chapter-knowledge/SKILL.md @@ -1,6 +1,6 @@ --- -name: extract-knowledge -description: 章后抽取的功能合同(scenario: extraction,分析槽位)。采纳后的新章→实体/关系/事件草稿+既有卡更新+状态提议;字段合同就是抽取 checklist。 +name: extract-chapter-knowledge +description: 从已接受章节抽取实体、关系、事件和叙事状态草稿,并提出既有知识更新。正文进入 Canonical 后需要章后增量知识时使用;只产草稿,不自动确认知识。 disable-model-invocation: true --- @@ -8,7 +8,7 @@ disable-model-invocation: true 何时用:章被采纳(正文已确认)后,从新章更新作品知识面——U4 知识确认旅程的抽取端(C5)。**采纳正文≠确认知识**:本功能产出全是草稿。 -另一用场:**parse-book(全书解析)的逐章内环**——存量章为输入、前 N-1 章积累的细纲与实体为上下文,字段 checklist 机制完全相同,外加一项:逆推本章细纲(章目标/关键事件/出场/伏笔动作/钩子)。 +另一用场:**deconstruct-book(全书解析)的逐章内环**——存量章为输入、前 N-1 章积累的细纲与实体为上下文,字段 checklist 机制完全相同,外加一项:逆推本章细纲(章目标/关键事件/出场/伏笔动作/钩子)。 ## 元数据驱动(本功能的核心机制) diff --git a/.claude/skills/extract-knowledge/scripts/extract_knowledge.py b/.claude/skills/extract-chapter-knowledge/scripts/extract_knowledge.py similarity index 98% rename from .claude/skills/extract-knowledge/scripts/extract_knowledge.py rename to .claude/skills/extract-chapter-knowledge/scripts/extract_knowledge.py index 804f27c..d946e2d 100644 --- a/.claude/skills/extract-knowledge/scripts/extract_knowledge.py +++ b/.claude/skills/extract-chapter-knowledge/scripts/extract_knowledge.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """从已接受章节抽取作品知识草稿。 -本入口只负责“正文 -> draft/shadow”这一段,不自动确认知识;确认由 confirm skill -单独触发。模型调用经 llm skill,所有输入输出挂到本次 run_id。 +本入口只负责“正文 -> draft/shadow”这一段,不自动确认知识;确认由 +decide-candidate 单独触发。模型调用经 call-content-model,所有输入输出挂到本次 run_id。 """ import hashlib import json @@ -18,8 +18,8 @@ from psycopg.types.json import Jsonb HERE = pathlib.Path(__file__).resolve().parent SKILLS = HERE.parents[1] for import_path in ( - SKILLS / "llm" / "scripts", - SKILLS / "runtime" / "scripts", + SKILLS / "call-content-model" / "scripts", + SKILLS / "record-run-evidence" / "scripts", ): if str(import_path) not in sys.path: sys.path.insert(0, str(import_path)) @@ -29,7 +29,7 @@ from record_failed_run import record_failure # noqa: E402 from run_registry import finish_run, new_run_id, start_run # noqa: E402 -DB_SCRIPTS = SKILLS / "db" / "scripts" +DB_SCRIPTS = SKILLS / "access-database" / "scripts" if str(DB_SCRIPTS) not in sys.path: sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/extract-knowledge/scripts/test_extract_knowledge_offline.py b/.claude/skills/extract-chapter-knowledge/scripts/test_extract_knowledge_offline.py similarity index 100% rename from .claude/skills/extract-knowledge/scripts/test_extract_knowledge_offline.py rename to .claude/skills/extract-chapter-knowledge/scripts/test_extract_knowledge_offline.py diff --git a/.claude/skills/extract-work-knowledge/SKILL.md b/.claude/skills/extract-work-knowledge/SKILL.md new file mode 100644 index 0000000..f4696ce --- /dev/null +++ b/.claude/skills/extract-work-knowledge/SKILL.md @@ -0,0 +1,52 @@ +--- +name: extract-work-knowledge +description: 将已完成拆解的作品正文按窗口抽取为 upgrade_book 实体、关系与演变草稿。作品完成拆书后需要建立本书知识面、续跑抽取窗口或查看抽取进度时使用;不负责备份、重置、修复和存量迁移。 +disable-model-invocation: true +--- + +# 按窗抽取作品知识 + +何时用:`deconstruct-book` 已产出章节细纲与实体脚手架后,把本书正文按窗抽成 `upgrade_book` 来源的实体、关系和演变草稿。 + +## 输入与执行 + +- 输入是同一作品的 Canonical 章节正文、活动字段合同、已有 `upgrade_book` 草稿和窗口状态。 +- `scripts/upgrade.py windows` 机械切窗,`run` 按窗顺序抽取,`status` 只读显示进度。 +- 内容模型通过 `call-content-model` 调用;嵌入通过 `embed-knowledge` 写入。 +- 同一租户、同一作品的 `windows/run` 通过 `upgrade_work_lock.py` 持有 PostgreSQL session advisory lock;取锁失败时不得执行任何业务 SQL、模型或嵌入调用。 + +## 抽取合同 + +正文按窗抽取 `upgrade_book` 实体卡。模型只返回新实体和已知实体的语义变化;名称、别名、出场章和窗口产物由确定性代码校验并落库。每窗采用短事务:模型和嵌入调用期间不持业务连接;实体与关系分别提交 `processing` marker,最终嵌入完整成功后才与 `done` 同事务提交。 + +新卡 `INSERT` 必须使用数据库实际返回的 revision;同窗后续更新绑定该 revision。关系首次同 pair 冲突只允许一次模型修复;未知额外键、字段异值、嵌入 owner 冲突或 payload 漂移均失败关闭。启动时只恢复可精确撤销的 `processing`;同一窗口本次第二次失败立即停止作品并留下 `retryable-clean` 断点。 + +名称只能取首次登场章正文中唯一命中的合法规范名或别名。多个合法名称同时命中、只命中通用称谓或无法唯一确定时,退化为中性类型描述,禁止借未来规范名补全早期台阶。 + +## 命令 + +```bash +# 幂等切窗 +.venv/bin/python .claude/skills/extract-work-knowledge/scripts/upgrade.py windows --work-id 8 + +# 顺序抽取并断点续跑 +.venv/bin/python .claude/skills/extract-work-knowledge/scripts/upgrade.py run --work-id 8 \ + [--max-windows K] [--max-calls M] [--semantic-dedup] + +# 只读进度 +.venv/bin/python .claude/skills/extract-work-knowledge/scripts/upgrade.py status --work-id 8 +``` + +## 数据与失败边界 + +- 读取 Canonical 章节、活动 schema、已有升格草稿及窗口状态。 +- 写入 `muse_knowledge_draft`、`example_upgrade_window`、`example_upgrade_alias`、`example_upgrade_presence`、`example_upgrade_card_state`、`example_upgrade_audit` 和 `example_knowledge_embedding`。 +- `SOURCE_TYPE="upgrade_book"`、`upgrade-reset` 等是数据库历史兼容值,不随 Skill 改名。 +- 不提供修复、重置、备份、恢复或存量迁移入口;这些动作统一由 `maintain-work-extraction` 承担。 + +## 离线验证 + +```bash +.venv/bin/python .claude/skills/extract-work-knowledge/scripts/test_upgrade_work_lock_offline.py +.venv/bin/python .claude/skills/extract-work-knowledge/scripts/test_parse_upgrade_offline.py +``` diff --git a/.claude/skills/upgrade/scripts/test_parse_upgrade_offline.py b/.claude/skills/extract-work-knowledge/scripts/test_parse_upgrade_offline.py similarity index 99% rename from .claude/skills/upgrade/scripts/test_parse_upgrade_offline.py rename to .claude/skills/extract-work-knowledge/scripts/test_parse_upgrade_offline.py index 1ded5a9..523ac00 100644 --- a/.claude/skills/upgrade/scripts/test_parse_upgrade_offline.py +++ b/.claude/skills/extract-work-knowledge/scripts/test_parse_upgrade_offline.py @@ -13,7 +13,7 @@ 本测试**不触碰真实服务**——质量修复命令只通过 fake DB 验证 preview、CAS 和原子写入, 不在离线自测中执行真实 work/window。 -跑法:仓根 `.venv/bin/python .claude/skills/upgrade/scripts/test_parse_upgrade_offline.py` +跑法:仓根 `.venv/bin/python .claude/skills/extract-work-knowledge/scripts/test_parse_upgrade_offline.py` """ import json import hashlib @@ -5179,7 +5179,7 @@ def test_compensation_tombstone_domains_are_separate(): def test_recover_legacy_failed_cli_contract(): """legacy failed 只允许清理副作用为零的窗口,只有安全 reset 墓碑可豁免。""" - help_result = CliRunner().invoke(pu.cli, ["recover-legacy-failed", "--help"]) + help_result = CliRunner().invoke(pu.maintenance_cli, ["recover-legacy-failed", "--help"]) check( "recover-legacy-failed-help", help_result.exit_code == 0 @@ -5193,7 +5193,7 @@ def test_recover_legacy_failed_cli_contract(): conn = _LegacyRecoveryConn() with patch.object(pu.psycopg, "connect", return_value=conn): preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) check("recover-legacy-failed-preview成功", preview.exit_code == 0, detail=preview.output) preview_data = json.loads(preview.output) @@ -5215,7 +5215,7 @@ def test_recover_legacy_failed_cli_contract(): with patch.object(pu.psycopg, "connect", return_value=conn), \ patch.object(pu, "upgrade_work_lock", return_value=nullcontext()): execute = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--execute", "--confirmation-sha", preview_data["confirmation_sha"], @@ -5234,7 +5234,7 @@ def test_recover_legacy_failed_cli_contract(): with patch.object(pu.psycopg, "connect", return_value=drift_conn), \ patch.object(pu, "upgrade_work_lock", return_value=nullcontext()): drift = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--execute", "--confirmation-sha", preview_data["confirmation_sha"], @@ -5248,7 +5248,7 @@ def test_recover_legacy_failed_cli_contract(): active_conn = _LegacyRecoveryConn(active_artifact=artifact) with patch.object(pu.psycopg, "connect", return_value=active_conn): active_preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) active_data = json.loads(active_preview.output) check( @@ -5266,7 +5266,7 @@ def test_recover_legacy_failed_cli_contract(): with patch.object(pu.psycopg, "connect", return_value=active_conn), \ patch.object(pu, "upgrade_work_lock", return_value=nullcontext()): active_execute = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--execute", "--confirmation-sha", active_data["confirmation_sha"], @@ -5283,7 +5283,7 @@ def test_recover_legacy_failed_cli_contract(): non_reset_conn = _LegacyRecoveryConn(non_reset_deleted=artifact) with patch.object(pu.psycopg, "connect", return_value=non_reset_conn): non_reset_preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) non_reset_data = json.loads(non_reset_preview.output) key = {"draft": "new_cards", "embedding-draft": "embeddings", @@ -5299,7 +5299,7 @@ def test_recover_legacy_failed_cli_contract(): undo_conn = _LegacyRecoveryConn(non_reset_deleted=artifact) with patch.object(pu.psycopg, "connect", return_value=undo_conn): undo_preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) undo_data = json.loads(undo_preview.output) key = {"draft-undo": "new_cards", "embedding-draft-undo": "embeddings", @@ -5313,7 +5313,7 @@ def test_recover_legacy_failed_cli_contract(): deleted_presence_conn = _LegacyRecoveryConn(active_artifact="presence-deleted") with patch.object(pu.psycopg, "connect", return_value=deleted_presence_conn): deleted_presence_preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) deleted_presence_data = json.loads(deleted_presence_preview.output) check( @@ -5327,7 +5327,7 @@ def test_recover_legacy_failed_cli_contract(): embedding_conn = _LegacyRecoveryConn(active_artifact=artifact) with patch.object(pu.psycopg, "connect", return_value=embedding_conn): embedding_preview = CliRunner().invoke( - pu.cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] + pu.maintenance_cli, ["recover-legacy-failed", "--work-id", "8", "--window-no", "7", "--preview"] ) embedding_data = json.loads(embedding_preview.output) check( @@ -5546,7 +5546,7 @@ def _quality_repair_preview(state): conn = _QualityRepairConn(state) with patch.object(pu.psycopg, "connect", return_value=conn): result = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--preview", @@ -5617,7 +5617,7 @@ def test_quality_repair_execute_atomically_updates_payload_and_embedding(): patch.object(pu, "_embed_session", return_value=object()), \ patch.object(pu, "embed_texts", return_value=([[0.9] * pu.EMBED_DIM], set())): result = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5674,7 +5674,7 @@ def test_quality_repair_rejects_stale_confirmation_before_embedding(): pu, "embed_texts", side_effect=lambda *args: embed_calls.append(args) or ([[0.9]], set()) ): result = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5700,7 +5700,7 @@ def test_quality_repair_rejects_final_snapshot_drift_and_bad_embedding(): patch.object(pu, "_embed_session", return_value=object()), \ patch.object(pu, "embed_texts", return_value=([[0.9] * pu.EMBED_DIM], set())): drift = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5720,7 +5720,7 @@ def test_quality_repair_rejects_final_snapshot_drift_and_bad_embedding(): patch.object(pu, "_embed_session", return_value=object()), \ patch.object(pu, "embed_texts", return_value=([], {0})): bad = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5740,7 +5740,7 @@ def test_quality_repair_rejects_final_snapshot_drift_and_bad_embedding(): patch.object(pu, "_embed_session", return_value=object()), \ patch.object(pu, "embed_texts", return_value=(None, None)): malformed = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5773,7 +5773,7 @@ def test_quality_repair_rejects_invalid_vector_dimension_and_values(): patch.object(pu, "_embed_session", return_value=object()), \ patch.object(pu, "embed_texts", return_value=([vector], set())): result = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--execute", "--confirmation-sha", preview["confirmation_sha"], @@ -5834,7 +5834,7 @@ def test_quality_repair_rejects_alias_and_presence_duplicates(): conn = _QualityRepairConn(state) with patch.object(pu.psycopg, "connect", return_value=conn): result = CliRunner().invoke( - pu.cli, + pu.maintenance_cli, [ "repair-card-quality", "--work-id", "8", "--draft-id", "25360", "--preview", @@ -5952,7 +5952,7 @@ def _presence_dedupe_cli(state, args): conn = _PresenceDedupeConn(state) with patch.object(pu.psycopg, "connect", return_value=conn), \ patch.object(pu, "upgrade_work_lock", return_value=nullcontext()): - result = CliRunner().invoke(pu.cli, ["repair-presence-duplicates"] + args) + result = CliRunner().invoke(pu.maintenance_cli, ["repair-presence-duplicates"] + args) return result, conn diff --git a/.claude/skills/upgrade/scripts/test_upgrade_work_lock_offline.py b/.claude/skills/extract-work-knowledge/scripts/test_upgrade_work_lock_offline.py similarity index 100% rename from .claude/skills/upgrade/scripts/test_upgrade_work_lock_offline.py rename to .claude/skills/extract-work-knowledge/scripts/test_upgrade_work_lock_offline.py diff --git a/.claude/skills/upgrade/scripts/upgrade.py b/.claude/skills/extract-work-knowledge/scripts/upgrade.py similarity index 99% rename from .claude/skills/upgrade/scripts/upgrade.py rename to .claude/skills/extract-work-knowledge/scripts/upgrade.py index e382976..0e1715a 100644 --- a/.claude/skills/upgrade/scripts/upgrade.py +++ b/.claude/skills/extract-work-knowledge/scripts/upgrade.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""upgrade skill:作品面升格执行器(方案 v6,创始人 2026-07-14 认可)。 +"""extract-work-knowledge Skill:作品面知识抽取执行器(方案 v6,创始人 2026-07-14 认可)。 把参考书自己的实体/关系/大纲按「全实体统一生长」机制抽进正式候选层: 一实体一卡(不分全卡/轻卡),每窗流程= @@ -40,14 +40,14 @@ import psycopg # 拆分后 upgrade 与 parse-book 分属两个 skill:parse_llm(敏感降级链+llm 入口+DSN/TENANT)留 # parse-book 字节不动,本脚本单向跨 skill 引用(upgrade→parse-book,合法);upgrade_work_lock 与本脚本同目录。 sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "parse-book" / "scripts")) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "deconstruct-book" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from parse_llm import m3_json, SensitiveHardStop, IDENTITY, TENANT, DSN # noqa: E402 from upgrade_work_lock import UpgradeWorkLockUnavailable, upgrade_work_lock # noqa: E402 -# 语义判重(P1)复用 embed skill 的嵌入通道(同模型同维、与检索端语义对齐)—— +# 语义判重(P1)复用 embed-knowledge 的嵌入通道(同模型同维、与检索端语义对齐)—— # 只在开启 --semantic-dedup 时才真调,默认关(试跑期嵌入延后,见文件头注释); # build_embed_text/MODEL/DIM/ACTOR 供最终嵌入短事务复用检索端同源文本构造器与列常量。 -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed-knowledge" / "scripts")) from embed_drafts import (_session as _embed_session, embed_texts, build_embed_text, # noqa: E402 MODEL as EMBED_MODEL, DIM as EMBED_DIM, ACTOR as EMBED_ACTOR) @@ -3260,7 +3260,7 @@ def _resolve_canonical_name(name_map, aliases_by_draft, matched_name): def _entity_embed_text(ent): """判重查询侧嵌入文本:【型】名称:摘要 + 关键字段摘选。 - 与存储侧 build_embed_text(embed skill)格式同构同源——查询向量与库内卡向量落同一语义空间才可比; + 与存储侧 build_embed_text(embed-knowledge)格式同构同源——查询向量与库内卡向量落同一语义空间才可比; 差异仅在此处吃候选新实体 ent(键:型/名称/一句话摘要/字段),故下划线内部键(如里程碑 _win)在此 显式排除不进文本(build_embed_text 侧里程碑值 str() 后带 _win 噪音是已知递延项,两侧对称、本次不修)。""" t = ent.get("型") or ent.get("type") or "" @@ -3924,7 +3924,12 @@ def embed_touched_cards(sess, work_id, touched): @click.group() def cli(): - """作品面升格(正文窗直抽 · 全实体统一生长)""" + """从已拆作品按正文窗抽取作品知识。""" + + +@click.group() +def maintenance_cli(): + """维护既有作品抽取状态;公开入口由维护 Skill 提供。""" @cli.command() @@ -4427,7 +4432,7 @@ def _echo_legacy_recovery_snapshot(snapshot, mode): }, ensure_ascii=False, sort_keys=True)) -@cli.command("recover-legacy-failed") +@maintenance_cli.command("recover-legacy-failed") @click.option("--work-id", type=int, required=True, help="目标作品 ID") @click.option("--window-no", type=int, required=True, help="目标 legacy failed 窗号") @click.option("--preview", is_flag=True, help="只读输出窗口、产物计数、processing 数和确认 SHA") @@ -4492,7 +4497,7 @@ def recover_legacy_failed(work_id, window_no, preview, execute, confirmation_sha raise click.ClickException(str(exc)) from exc -@cli.command("repair-card-quality") +@maintenance_cli.command("repair-card-quality") @click.option("--work-id", type=int, required=True, help="目标作品 ID") @click.option("--draft-id", type=int, required=True, help="目标升格 draft ID") @click.option("--preview", is_flag=True, help="只读输出目标映射、关联快照和确认 SHA") @@ -4710,7 +4715,7 @@ def _presence_dedupe_output(snapshot, mode, deleted_ids=None): } -@cli.command("repair-presence-duplicates") +@maintenance_cli.command("repair-presence-duplicates") @click.option("--work-id", type=int, required=True, help="固定目标作品 ID(仅支持 work=8)") @click.option("--preview", is_flag=True, help="RR 只读输出十组冗余的行摘要和确认 SHA") @click.option("--execute", is_flag=True, help="按确认 SHA 软删十个精确冗余行") diff --git a/.claude/skills/upgrade/scripts/upgrade_work_lock.py b/.claude/skills/extract-work-knowledge/scripts/upgrade_work_lock.py similarity index 100% rename from .claude/skills/upgrade/scripts/upgrade_work_lock.py rename to .claude/skills/extract-work-knowledge/scripts/upgrade_work_lock.py diff --git a/.claude/skills/snapshot/SKILL.md b/.claude/skills/freeze-context/SKILL.md similarity index 62% rename from .claude/skills/snapshot/SKILL.md rename to .claude/skills/freeze-context/SKILL.md index 1920615..e31aa72 100644 --- a/.claude/skills/snapshot/SKILL.md +++ b/.claude/skills/freeze-context/SKILL.md @@ -1,12 +1,12 @@ --- -name: snapshot -description: 冻结快照共用能力层——冻结快照纯函数(build_snapshot)+ fail-closed 校验(check_snapshot)+ 内容级泄漏审计(audit_leakage)+ 只读装载冻结原文/回放配置(load_reference_work)。被 read-context 与 replay-eval 复用,只依赖 stdlib + psycopg,不依赖任何业务 skill。 +name: freeze-context +description: 将截至指定章号的来源冻结为可重复清单,并校验授权、版本、未来信息泄漏和只读原文装载。上下文组装或回放评测需要稳定时间边界时使用;不调用模型、不写正式数据。 disable-model-invocation: true --- # 冻结快照能力层(共用能力,箭头只向下) -把「截至第 N 章」冻结成可重复、可审计的来源清单,并对冻结快照做授权/来源/泄漏校验与只读装载的共用能力。被 `read-context`(冻结原文回读与回放仓储门禁)和 `replay-eval`(回放编排)复用;本 skill 只依赖 stdlib 与 psycopg,**不 import 任何业务 skill**,不访问模型、不写正式数据。 +把「截至第 N 章」冻结成可重复、可审计的来源清单,并对冻结快照做授权/来源/泄漏校验与只读装载的共用能力。被 `assemble-context`(冻结原文回读与回放仓储门禁)和 `evaluate-frozen-replay`(回放编排)复用;本 Skill 只依赖 stdlib 与 psycopg,**不 import 任何业务 Skill**,不访问模型、不写正式数据。 ## scripts 入口 @@ -19,4 +19,4 @@ disable-model-invocation: true ## 依赖方向 -本 skill 是共用能力层,**只依赖 stdlib + psycopg,不 import 任何业务 skill**。上层(`read-context`、`replay-eval`)单向向下引用本 skill 的 scripts,不反向依赖。 +本 Skill 是共用能力层,**只依赖 stdlib + psycopg,不 import 任何业务 Skill**。上层(`assemble-context`、`evaluate-frozen-replay`)单向向下引用本 Skill 的 scripts,不反向依赖。 diff --git a/.claude/skills/snapshot/scripts/audit_leakage.py b/.claude/skills/freeze-context/scripts/audit_leakage.py similarity index 100% rename from .claude/skills/snapshot/scripts/audit_leakage.py rename to .claude/skills/freeze-context/scripts/audit_leakage.py diff --git a/.claude/skills/snapshot/scripts/build_snapshot.py b/.claude/skills/freeze-context/scripts/build_snapshot.py similarity index 100% rename from .claude/skills/snapshot/scripts/build_snapshot.py rename to .claude/skills/freeze-context/scripts/build_snapshot.py diff --git a/.claude/skills/snapshot/scripts/check_snapshot.py b/.claude/skills/freeze-context/scripts/check_snapshot.py similarity index 100% rename from .claude/skills/snapshot/scripts/check_snapshot.py rename to .claude/skills/freeze-context/scripts/check_snapshot.py diff --git a/.claude/skills/snapshot/scripts/load_reference_work.py b/.claude/skills/freeze-context/scripts/load_reference_work.py similarity index 100% rename from .claude/skills/snapshot/scripts/load_reference_work.py rename to .claude/skills/freeze-context/scripts/load_reference_work.py diff --git a/.claude/skills/snapshot/scripts/test_audit_leakage.py b/.claude/skills/freeze-context/scripts/test_audit_leakage.py similarity index 100% rename from .claude/skills/snapshot/scripts/test_audit_leakage.py rename to .claude/skills/freeze-context/scripts/test_audit_leakage.py diff --git a/.claude/skills/snapshot/scripts/test_build_snapshot.py b/.claude/skills/freeze-context/scripts/test_build_snapshot.py similarity index 100% rename from .claude/skills/snapshot/scripts/test_build_snapshot.py rename to .claude/skills/freeze-context/scripts/test_build_snapshot.py diff --git a/.claude/skills/snapshot/scripts/test_check_snapshot.py b/.claude/skills/freeze-context/scripts/test_check_snapshot.py similarity index 100% rename from .claude/skills/snapshot/scripts/test_check_snapshot.py rename to .claude/skills/freeze-context/scripts/test_check_snapshot.py diff --git a/.claude/skills/snapshot/scripts/test_load_reference_work.py b/.claude/skills/freeze-context/scripts/test_load_reference_work.py similarity index 100% rename from .claude/skills/snapshot/scripts/test_load_reference_work.py rename to .claude/skills/freeze-context/scripts/test_load_reference_work.py diff --git a/.claude/skills/import/SKILL.md b/.claude/skills/import-book/SKILL.md similarity index 72% rename from .claude/skills/import/SKILL.md rename to .claude/skills/import-book/SKILL.md index aecbcf0..5f07167 100644 --- a/.claude/skills/import/SKILL.md +++ b/.claude/skills/import-book/SKILL.md @@ -1,21 +1,21 @@ --- -name: import -description: 参考书/旧稿 txt 导入解析——回目正则静态分章后落库(作品行+章行+正文 block+参考书档案),对账表即审查面。B1 参考书导入与 C8 用户旧稿复用同一通道;LLM 不参与,纯确定性工具。 +name: import-book +description: 将参考书或用户旧稿 txt 确定性分章并写入作品、章节、正文 block 和来源档案。首次导入完整书稿或明确重导时使用;不调用模型、不改写正文。 --- -# import —— 导入解析(静态分章入库) +# 导入完整书稿 -对应 muse API 面:导入解析旅程(产品-03 §3.7)。落库走 psycopg 直连(凭据同 db skill),写入约定见 [`db/表映射.md`](../../../db/表映射.md)。 +对应 muse API 面:导入解析旅程(产品-03 §3.7)。落库走 psycopg 直连(凭据同 `access-database`),写入约定见 [`db/表映射.md`](../../../db/表映射.md)。 ## 用法 ```bash # 预演:只解析打印对账,不落库 -.venv/bin/python .claude/skills/import/scripts/import_novel.py --dry-run "../小说清单/机动风暴_骷髅精灵.txt" +.venv/bin/python .claude/skills/import-book/scripts/import_novel.py --dry-run "../小说清单/机动风暴_骷髅精灵.txt" # 正式导入(已存在同名作品则拒绝,--force 软删旧行重导) -.venv/bin/python .claude/skills/import/scripts/import_novel.py "../小说清单/超神机械师_齐佩甲.txt" -.venv/bin/python .claude/skills/import/scripts/import_novel.py --force "../小说清单/机动风暴_骷髅精灵.txt" +.venv/bin/python .claude/skills/import-book/scripts/import_novel.py "../小说清单/超神机械师_齐佩甲.txt" +.venv/bin/python .claude/skills/import-book/scripts/import_novel.py --force "../小说清单/机动风暴_骷髅精灵.txt" ``` ## 分章规则(按覆盖的真实格式) @@ -39,7 +39,7 @@ description: 参考书/旧稿 txt 导入解析——回目正则静态分章后 ## 审查面 - `--dry-run` 与正式导入都打印**对账表**:声明章数 vs 导入章数、丢弃重复题数、尾部截断行、卷分隔数、章号异常数、总字数,另附首/中/末三章题目与正文首行抽样。 -- 导入后用 db skill 抽查:`query "SELECT order_no,title FROM muse_content_chapter WHERE work_id=… ORDER BY order_no LIMIT 5"`。 +- 导入后用 `access-database` 抽查:`query "SELECT order_no,title FROM muse_content_chapter WHERE work_id=… ORDER BY order_no LIMIT 5"`。 ## 红线 diff --git a/.claude/skills/import/scripts/import_novel.py b/.claude/skills/import-book/scripts/import_novel.py similarity index 99% rename from .claude/skills/import/scripts/import_novel.py rename to .claude/skills/import-book/scripts/import_novel.py index 3efaaa8..f71dd66 100644 --- a/.claude/skills/import/scripts/import_novel.py +++ b/.claude/skills/import-book/scripts/import_novel.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""import skill:参考书/旧稿 txt 静态分章导入(B1/C8 共用,LLM 不参与)。 +"""import-book Skill:参考书/旧稿 txt 静态分章导入(B1/C8 共用,LLM 不参与)。 分章与修复规则、落库映射见同 skill 的 SKILL.md;写入约定见 db/表映射.md。 失败原样抛错不静默(公约)。 diff --git a/.claude/skills/import/scripts/quality_report.py b/.claude/skills/import-book/scripts/quality_report.py similarity index 98% rename from .claude/skills/import/scripts/quality_report.py rename to .claude/skills/import-book/scripts/quality_report.py index 9e355a1..8a4c38b 100644 --- a/.claude/skills/import/scripts/quality_report.py +++ b/.claude/skills/import-book/scripts/quality_report.py @@ -11,7 +11,7 @@ DSN = ("postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse- "?keepalives=1&keepalives_idle=15&keepalives_interval=5&keepalives_count=3") TENANT = 1 -# 语义垃圾嫌疑模式(扫描计数用;真正删除走 clean skill 的 LLM 检测+代码执行) +# 语义垃圾嫌疑模式(扫描计数用;真正删除走 clean-book-text 的 LLM 检测+代码执行) JUNK_PATTERNS = { "网址残留": re.compile(r'www\.|http|\.(?:com|net|cc|org|info)\b', re.I), "一秒记住类": re.compile(r'一秒记住|天才.{0,3}记住|记住本站|首发域名|手机版阅读|无弹窗'), diff --git a/.claude/skills/maintain-work-extraction/SKILL.md b/.claude/skills/maintain-work-extraction/SKILL.md new file mode 100644 index 0000000..adc455f --- /dev/null +++ b/.claude/skills/maintain-work-extraction/SKILL.md @@ -0,0 +1,78 @@ +--- +name: maintain-work-extraction +description: 维护既有 upgrade_book 抽取状态,包括七域备份验证与恢复、全量重抽前重置、失败窗口恢复、定向数据修复和存量迁移。需要修复或重建作品抽取结果时使用;不负责正常按窗抽取。 +disable-model-invocation: true +--- + +# 维护作品知识抽取 + +何时用:作品的 `upgrade_book` 派生状态需要备份、恢复、重置、定向修复或存量迁移。正常 `windows/run/status` 属于 `extract-work-knowledge`。 + +## 依赖边界 + +维护命令复用 `extract-work-knowledge/scripts/upgrade.py` 的修复算法和 `upgrade_work_lock.py` 的同书锁,不复制第二套数据库补偿逻辑。`upgrade_book`、`upgrade-reset`、`parse_upgrade.py` 等字符串是存量数据库或备份清单的兼容键,不随 Skill 改名。 + +## 维护入口 + +| 入口 | 用途 | +|---|---| +| `scripts/backup_upgrade_work.py` | 七域一致性 `backup/verify/rehearse/restore`。 | +| `scripts/reset_upgrade_work.py` | 全量重抽前预览或重置本书派生状态。 | +| `scripts/maintenance.py` | 暴露 `recover-legacy-failed`、`repair-card-quality`、`repair-presence-duplicates`。 | +| `scripts/migrate_upgrade_windows.py` | 将存量 `[窗N]` 演变条目定位到真实章号,仅 dry-run。 | +| `scripts/parse_rewash.py` | 存量回洗工具;是否退役由现有数据状态决定。 | + +## 同书互斥 + +重置预览/执行、备份、演练、恢复和所有写修复均使用 `extract-work-knowledge` 的 PostgreSQL session advisory lock。取锁失败时必须在业务 SQL、文件验证、嵌入或模型调用前非零退出;`status` 不属于本 Skill。 + +## 备份与恢复合同 + +`reset --execute` 前必须用一个 `REPEATABLE READ READ ONLY` 事务导出本书七域状态,并从磁盘独立复验。备份目录必须是 `/private/tmp` 下尚不存在的新目录。清单绑定数据库 identity、Canonical 章节与 block、窗口边界、活动字段合同、计划参数和实际代码 fileSha。 + +备份清单中的 `parse_upgrade.py`、`parse-book/SKILL.md` 等逻辑键保持不变,以便存量备份继续离线验证;真实路径分别指向当前 `extract-work-knowledge` 与 `deconstruct-book` 文件。 + +恢复前固定执行离线 `verify`。`rehearse` 在真实表的单事务中应用并强制回滚;`restore --execute` 必须精确匹配 `backup_id + confirmationSha`,逐域读回后才提交。任何 work、tenant、数据库 identity、Canonical、窗口、合同或代码漂移均拒绝。 + +```bash +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py backup \ + --work-id 8 --expected-chapters 594 --expected-windows 116 +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py verify \ + --backup-dir /private/tmp/muse-upgrade-work8-before-reset +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py rehearse \ + --work-id 8 --backup-dir /private/tmp/muse-upgrade-work8-before-reset \ + --confirmation-sha +``` + +## 重置与定向修复 + +reset 软删目标 `upgrade_book` 草稿和无 Canonical owner 的活向量,窗口保留并回到 `pending`;别名、presence、card state 和 audit 按既有约束清理。执行时须持同书锁、复验备份和代码身份,并在固定表锁顺序内完成单事务重置;任一断言失败整体回滚。 + +```bash +# 只读预览 +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/reset_upgrade_work.py --work-id 8 + +# 真重置 +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/reset_upgrade_work.py \ + --work-id 8 --execute --backup-dir /private/tmp/muse-upgrade-work8-before-reset \ + --backup-id --confirmation-sha + +# 定向维护命令 +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/maintenance.py \ + recover-legacy-failed --work-id 8 --window-no 7 --preview +``` + +定向修复必须先 preview,execute 精确匹配 preview 的确认 SHA,并显式确认没有同书活进程。修复范围、行集合、revision、payload、owner 或关联状态任一漂移都失败关闭。 + +## 数据边界 + +- 备份与 preview 默认只读;execute 只修改目标租户、目标作品、`upgrade_book` 边界内的派生状态。 +- 不生成新抽取内容,不提供正常 `run/windows/status`。 +- `migrate_upgrade_windows.py` 当前只输出迁移计划,不落库;真实写入仍需另行 gate 与授权。 + +## 离线验证 + +```bash +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/test_backup_upgrade_work_offline.py +.venv/bin/python .claude/skills/maintain-work-extraction/scripts/test_reset_upgrade_work_offline.py +``` diff --git a/.claude/skills/upgrade/scripts/backup_upgrade_work.py b/.claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py similarity index 98% rename from .claude/skills/upgrade/scripts/backup_upgrade_work.py rename to .claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py index 67fa270..22455e2 100644 --- a/.claude/skills/upgrade/scripts/backup_upgrade_work.py +++ b/.claude/skills/maintain-work-extraction/scripts/backup_upgrade_work.py @@ -27,11 +27,12 @@ from typing import Any, Callable, Mapping, Sequence import click -# 拆分后本脚本随升格管线落在 upgrade skill;upgrade_work_lock 同目录,parse_llm(DSN/TENANT,见 -# _load_db_config)留 parse-book,单向跨 skill 引用(upgrade→parse-book,合法)。 +# 维护命令复用作品抽取 Skill 的同书锁和执行身份,不复制第二套互斥规则。 _HERE = pathlib.Path(__file__).resolve().parent +_EXTRACTION_SCRIPTS = _HERE.parents[1] / "extract-work-knowledge" / "scripts" sys.path.insert(0, str(_HERE)) -sys.path.insert(0, str(_HERE.parents[1] / "parse-book" / "scripts")) +sys.path.insert(0, str(_EXTRACTION_SCRIPTS)) +sys.path.insert(0, str(_HERE.parents[1] / "deconstruct-book" / "scripts")) from upgrade_work_lock import UpgradeWorkLockUnavailable, upgrade_work_lock @@ -379,20 +380,21 @@ def capture_code_identity(git_commit: str | None = None) -> dict[str, Any]: """绑定本次实际执行文件;未提交代码用 fileSha 表达,不借 HEAD 冒充。""" here = pathlib.Path(__file__).resolve().parent + extraction_scripts = here.parents[1] / "extract-work-knowledge" / "scripts" root = _repository_root() # 契约 key 是稳定逻辑名(拆分前后字节不变,存量备份永久可离线复验);value 只是拆分后的真实定位路径。 files = { "backup_upgrade_work.py": pathlib.Path(__file__).resolve(), "reset_upgrade_work.py": here / "reset_upgrade_work.py", # 升格执行器拆分后改名 upgrade.py;逻辑名 "parse_upgrade.py" 保持稳定。 - "parse_upgrade.py": here / "upgrade.py", + "parse_upgrade.py": extraction_scripts / "upgrade.py", # parse_llm 留 parse-book(字节不动);路径单向跨 skill 定位,逻辑名 "parse_llm.py" 不变。 - "parse_llm.py": here.parents[1] / "parse-book" / "scripts" / "parse_llm.py", - "embed_drafts.py": here.parents[1] / "embed" / "scripts" / "embed_drafts.py", - "upgrade_work_lock.py": here / "upgrade_work_lock.py", - "llm.py": here.parents[1] / "llm" / "scripts" / "llm.py", + "parse_llm.py": here.parents[1] / "deconstruct-book" / "scripts" / "parse_llm.py", + "embed_drafts.py": here.parents[1] / "embed-knowledge" / "scripts" / "embed_drafts.py", + "upgrade_work_lock.py": extraction_scripts / "upgrade_work_lock.py", + "llm.py": here.parents[1] / "call-content-model" / "scripts" / "llm.py", # 逻辑名保留 "parse-book/SKILL.md"(稳定契约键);随 __file__ 实际指向 upgrade/SKILL.md。 - "parse-book/SKILL.md": here.parent / "SKILL.md", + "parse-book/SKILL.md": extraction_scripts.parent / "SKILL.md", } commit = git_commit or _git_commit() file_shas = {name: _file_sha(path) for name, path in files.items()} diff --git a/.claude/skills/maintain-work-extraction/scripts/maintenance.py b/.claude/skills/maintain-work-extraction/scripts/maintenance.py new file mode 100644 index 0000000..f221dff --- /dev/null +++ b/.claude/skills/maintain-work-extraction/scripts/maintenance.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +"""作品抽取维护命令入口。 + +修复算法复用 ``extract-work-knowledge`` 的唯一实现;这个入口只暴露维护命令, +不把正常的 windows/run/status 混进维护 Skill。 +""" + +import pathlib +import sys + + +HERE = pathlib.Path(__file__).resolve().parent +EXTRACTION_SCRIPTS = HERE.parents[1] / "extract-work-knowledge" / "scripts" +if str(EXTRACTION_SCRIPTS) not in sys.path: + sys.path.insert(0, str(EXTRACTION_SCRIPTS)) + +from upgrade import maintenance_cli # noqa: E402 + + +if __name__ == "__main__": + maintenance_cli() diff --git a/.claude/skills/upgrade/scripts/migrate_upgrade_windows.py b/.claude/skills/maintain-work-extraction/scripts/migrate_upgrade_windows.py similarity index 98% rename from .claude/skills/upgrade/scripts/migrate_upgrade_windows.py rename to .claude/skills/maintain-work-extraction/scripts/migrate_upgrade_windows.py index 00f13fd..2cb7b6b 100644 --- a/.claude/skills/upgrade/scripts/migrate_upgrade_windows.py +++ b/.claude/skills/maintain-work-extraction/scripts/migrate_upgrade_windows.py @@ -30,12 +30,12 @@ import sys import click import psycopg -# 复用 upgrade 的守卫/工具(剥前缀、剥尾残、垃圾拦截、内嵌章号、生命周期推断、章号排序键、 +# 复用作品抽取器的守卫/工具(剥前缀、剥尾残、垃圾拦截、内嵌章号、生命周期推断、章号排序键、 # 窗源文加载、SOURCE_TYPE/TENANT/DSN)——迁移与抽取同一套清洗口径,不另立标准。 -# 拆分后 upgrade(原 parse_upgrade)与本脚本同在 upgrade skill;其 import 会顺带把 parse-book 加入 sys.path。 HERE = pathlib.Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) -sys.path.insert(0, str(HERE.parents[1] / "llm" / "scripts")) +sys.path.insert(0, str(HERE.parents[1] / "extract-work-knowledge" / "scripts")) +sys.path.insert(0, str(HERE.parents[1] / "call-content-model" / "scripts")) import upgrade as pu # noqa: E402 from llm import chat_governed, extract_json # noqa: E402 @@ -190,7 +190,8 @@ def llm_precise(work_id, title, win_ranges, win_text, pending): continue a, b = win_ranges[wno] prompt = llm_locate_prompt(title, a, b, win_text[wno], entries) - content, _usage, used = chat_governed(prompt, system=pu.IDENTITY) + content, _usage, used = chat_governed(prompt, system=pu.IDENTITY, + caller="upgrade-migrate") if used is None: # 全局降级链耗尽:本窗放弃精确化,条目留待人工 print(f" [窗{wno}] LLM 全链耗尽,该窗 {len(entries)} 条转待人工", file=sys.stderr) continue diff --git a/.claude/skills/upgrade/scripts/parse_rewash.py b/.claude/skills/maintain-work-extraction/scripts/parse_rewash.py similarity index 100% rename from .claude/skills/upgrade/scripts/parse_rewash.py rename to .claude/skills/maintain-work-extraction/scripts/parse_rewash.py diff --git a/.claude/skills/upgrade/scripts/reset_upgrade_work.py b/.claude/skills/maintain-work-extraction/scripts/reset_upgrade_work.py similarity index 97% rename from .claude/skills/upgrade/scripts/reset_upgrade_work.py rename to .claude/skills/maintain-work-extraction/scripts/reset_upgrade_work.py index e91d456..9699017 100644 --- a/.claude/skills/upgrade/scripts/reset_upgrade_work.py +++ b/.claude/skills/maintain-work-extraction/scripts/reset_upgrade_work.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""upgrade skill:升格全量重抽准备——清一本书的升格产出与串行状态(2026-07-18 批9c 收尾)。 +"""maintain-work-extraction Skill:升格全量重抽准备(2026-07-18 批9c 收尾)。 为什么需要它(锚点「全量重抽的管线坑」第 1 条):升格是**串行有状态**管线,直接重跑会被 旧状态卡死——窗全是 done 不再处理、预扫/判重底册装着旧代卡把新观察并进废卡、旧审计行会被 @@ -29,10 +29,14 @@ import click import psycopg from psycopg.rows import dict_row -# 复用管线的连接与租户常量(与 upgrade 同源)。拆分后 backup/upgrade_work_lock 同目录, -# parse_llm(DSN/TENANT)留 parse-book,单向跨 skill 引用(upgrade→parse-book,合法)。 +# 复用作品抽取 Skill 的连接、租户与同书锁;维护侧不另造兼容实现。 sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "parse-book" / "scripts")) +sys.path.insert(0, str( + pathlib.Path(__file__).resolve().parents[2] + / "extract-work-knowledge" + / "scripts" +)) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "deconstruct-book" / "scripts")) import backup_upgrade_work as backup # noqa: E402 from parse_llm import DSN, TENANT # noqa: E402 from upgrade_work_lock import UpgradeWorkLockUnavailable, upgrade_work_lock # noqa: E402 diff --git a/.claude/skills/upgrade/scripts/test_backup_upgrade_work_offline.py b/.claude/skills/maintain-work-extraction/scripts/test_backup_upgrade_work_offline.py similarity index 100% rename from .claude/skills/upgrade/scripts/test_backup_upgrade_work_offline.py rename to .claude/skills/maintain-work-extraction/scripts/test_backup_upgrade_work_offline.py diff --git a/.claude/skills/upgrade/scripts/test_reset_upgrade_work_offline.py b/.claude/skills/maintain-work-extraction/scripts/test_reset_upgrade_work_offline.py similarity index 99% rename from .claude/skills/upgrade/scripts/test_reset_upgrade_work_offline.py rename to .claude/skills/maintain-work-extraction/scripts/test_reset_upgrade_work_offline.py index aa915d1..083e23e 100644 --- a/.claude/skills/upgrade/scripts/test_reset_upgrade_work_offline.py +++ b/.claude/skills/maintain-work-extraction/scripts/test_reset_upgrade_work_offline.py @@ -13,6 +13,7 @@ from click.testing import CliRunner SCRIPT_DIR = pathlib.Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPT_DIR)) +sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "extract-work-knowledge" / "scripts")) import upgrade as parse # noqa: E402 import backup_upgrade_work as backup # noqa: E402 diff --git a/.claude/skills/eval/SKILL.md b/.claude/skills/optimize-content-quality/SKILL.md similarity index 64% rename from .claude/skills/eval/SKILL.md rename to .claude/skills/optimize-content-quality/SKILL.md index 0fa8987..cf2f534 100644 --- a/.claude/skills/eval/SKILL.md +++ b/.claude/skills/optimize-content-quality/SKILL.md @@ -1,6 +1,6 @@ --- -name: eval -description: 质量收敛环——judge 打分、锁最低维、单变量改进(prompt/上下文/设定)、重生成再评,n=5 收敛;达标样张固化 golden/。 +name: optimize-content-quality +description: 根据独立评分锁定最低质量维度,以单变量方式调整 prompt、上下文或设定并有限重生成。候选稳定不达标且需要定位改进来源时使用;不得放宽 rubric 或混改多个变量。 --- # 质量收敛环(n=5) @@ -12,9 +12,9 @@ description: 质量收敛环——judge 打分、锁最低维、单变量改进( 1. **评**:派 judge 对候选章打分(落 `评审/第NNN章-评分.md`)。 2. **诊**:取最低两维,归因到三层之一—— - prompt 层:写手指令缺陷 → 改 `.claude/agents/writer.md`; - - 上下文层:该给的没给/给多了 → 改 `read-context` 的选取规则或 schema 字段与 aiContext; + - 上下文层:该给的没给/给多了 → 改 `assemble-context` 的选取规则或 schema 字段与 aiContext; - 设定层:卡本身空洞(如说话方式全是空词)→ 改卡(改动走用户确认)。 -3. **改**:**每轮只改一个变量**,否则归因失效;框架层改动可直接提交(`框架: …`),设定层改动走 confirm。 +3. **改**:**每轮只改一个变量**,否则归因失效;框架层改动可直接提交(`框架: …`),设定层改动走 `decide-candidate`。 4. **产**:重新生成该章(新候选覆盖工作区),回到第 1 步。 5. **止**:叙事关键三维全部 ≥4.0,或连续两轮无提升,或满 5 轮(对齐专题-04「重写次数有限」语义,本环即实验版有限重写)。 @@ -29,4 +29,4 @@ description: 质量收敛环——judge 打分、锁最低维、单变量改进( ## 正文离线回放 -正文 A/B/C 回放使用独立 `writer_replay` profile,不复用本 skill 的在线 1-5 分收敛口径,也不覆盖 `fine_outline_replay`。评分尺与双盲聚合规则由 [quality-gate · 正文回放 profile (writer_replay)](../quality-gate/SKILL.md#正文回放-profilewriter_replay) 定义,本 skill 不重复。 +正文 A/B/C 回放使用独立 `writer_replay` profile,不复用本 Skill 的在线 1-5 分收敛口径,也不覆盖 `fine_outline_replay`。评分尺与双盲聚合规则由 [score-content-quality · 正文回放 profile (writer_replay)](../score-content-quality/SKILL.md#正文回放-profilewriter_replay) 定义,本 Skill 不重复。 diff --git a/.claude/skills/fine-outline/SKILL.md b/.claude/skills/plan-chapter/SKILL.md similarity index 93% rename from .claude/skills/fine-outline/SKILL.md rename to .claude/skills/plan-chapter/SKILL.md index dfab5b2..3f0ba02 100644 --- a/.claude/skills/fine-outline/SKILL.md +++ b/.claude/skills/plan-chapter/SKILL.md @@ -1,6 +1,6 @@ --- -name: fine-outline -description: 细纲规划合同。根据规划上下文和已授权的冻结事实,产出下一章的结构细纲草稿;不写正文、不读取目标章答案。 +name: plan-chapter +description: 根据已确认规划、叙事状态和冻结事实生成下一章结构细纲草稿。正文生成前需要明确章目标、事件因果、出场实体、伏笔动作和章末钩子时使用;不写正文、不读取目标章答案。 disable-model-invocation: true --- @@ -10,7 +10,7 @@ disable-model-invocation: true ## 输入合同 -上下文必须由 `read-context` 组装,至少包含四层中的以下部分: +上下文必须由 `assemble-context` 组装,至少包含四层中的以下部分: - **L0 当前任务**:目标章号、章节输出合同、用户意图和未知项纪律; - **L1 叙事现在时**:截至当前章的状态、活动线程和必要的近章结构摘要; diff --git a/.claude/skills/fine-outline/scripts/test_contract.py b/.claude/skills/plan-chapter/scripts/test_contract.py similarity index 97% rename from .claude/skills/fine-outline/scripts/test_contract.py rename to .claude/skills/plan-chapter/scripts/test_contract.py index 24b9e6a..e3f09b7 100644 --- a/.claude/skills/fine-outline/scripts/test_contract.py +++ b/.claude/skills/plan-chapter/scripts/test_contract.py @@ -10,7 +10,7 @@ import unittest ROOT = Path(__file__).resolve().parents[4] -SKILL = (ROOT / ".claude/skills/fine-outline/SKILL.md").read_text(encoding="utf-8") +SKILL = (ROOT / ".claude/skills/plan-chapter/SKILL.md").read_text(encoding="utf-8") PLANNER = (ROOT / ".claude/agents/planner.md").read_text(encoding="utf-8") CHAINS = (ROOT / "meta/chains/README.md").read_text(encoding="utf-8") SCHEMA = (ROOT / "meta/schemas/fine_outline.yaml").read_text(encoding="utf-8") diff --git a/.claude/skills/planning/SKILL.md b/.claude/skills/plan-story/SKILL.md similarity index 86% rename from .claude/skills/planning/SKILL.md rename to .claude/skills/plan-story/SKILL.md index 253d72a..7f1cad2 100644 --- a/.claude/skills/planning/SKILL.md +++ b/.claude/skills/plan-story/SKILL.md @@ -1,6 +1,6 @@ --- -name: planning -description: 规划的功能合同(scenario: planning,规划槽位)。立项设定包/大纲/知识卡/状态的产出结构=schema 字段清单本身;续作修订同用此合同。 +name: plan-story +description: 按正式 schema 生成或修订作品设定包、大纲、知识卡、状态和装配草稿。前期方向已由用户选定并进入正式规划,或续作需要调整规划时使用;不写正文,未确认产物不进入生成上下文。 disable-model-invocation: true --- @@ -38,12 +38,12 @@ disable-model-invocation: true ## 规划落库(shadow→confirmed) -1. 落库:`.venv/bin/python .claude/skills/planning/scripts/persist_planning.py write --work-id N --section-type [--target-chapter N] [--schema-type X] [--version N]`(planner 不碰库/git,主会话落库;先 `--dry-run` 试跑)。 +1. 落库:`.venv/bin/python .claude/skills/plan-story/scripts/persist_planning.py write --work-id N --section-type [--target-chapter N] [--schema-type X] [--version N]`(planner 不碰库/git,主会话落库;先 `--dry-run` 试跑)。 2. 确认:`persist_planning.py confirm `(用户确认后 shadow→confirmed;**确认后的规划才进生成上下文**)。 3. 规划执行的输入/输出必须可追溯:`record_planning_execution.py --section-id ` 为已落库 section 登记 `example_run`、prompt/response raw 和 `example_run_receipt`;该记录标明 `executionKind=deterministic_persist`,不伪装成模型调用。 历史回执若误把确定性执行写成 `model_match=true`,使用 `repair_deterministic_receipt.py --run-id ` 追加修正 revision;旧回执保持不变。 4. 公共范式选择使用 `select_patterns.py --work-id --intent "..."`:只从 `scope=public_pattern` 召回,结果写入 `assembly` 的 `shadow`,并把检索意图与候选投影落 raw/回执;未确认 assembly 不得进入正文上下文。 -5. 已确认细纲取用(read-context 改道读库,不再当内存参数): +5. 已确认细纲取用(assemble-context 改道读库,不再当内存参数): ```sql SELECT payload FROM example_planning_section WHERE work_id=? AND section_type='fine_outline' AND target_chapter=? AND state='confirmed' AND deleted=false diff --git a/.claude/skills/planning/scripts/persist_planning.py b/.claude/skills/plan-story/scripts/persist_planning.py similarity index 97% rename from .claude/skills/planning/scripts/persist_planning.py rename to .claude/skills/plan-story/scripts/persist_planning.py index 9cdc4e3..7758359 100644 --- a/.claude/skills/planning/scripts/persist_planning.py +++ b/.claude/skills/plan-story/scripts/persist_planning.py @@ -15,15 +15,15 @@ from pathlib import Path import yaml -# 复用 db skill 锁死的 DSN(.claude/skills/db/scripts) -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +# 复用 access-database Skill 锁死的 DSN +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 CREATOR = "planning" SECTION_TYPES = ("setting", "outline", "state", "assembly", "fine_outline") # 字段权威(git 侧设计稿):落库字段覆盖门禁按 schema_type 读这里的 <型>.yaml。 -# persist_planning.py 在 .claude/skills/planning/scripts/ 下,仓库根是 parents[4]。 +# persist_planning.py 在 .claude/skills/plan-story/scripts/ 下,仓库根是 parents[4]。 SCHEMAS_DIR = Path(__file__).resolve().parents[4] / "meta" / "schemas" diff --git a/.claude/skills/planning/scripts/record_planning_execution.py b/.claude/skills/plan-story/scripts/record_planning_execution.py similarity index 98% rename from .claude/skills/planning/scripts/record_planning_execution.py rename to .claude/skills/plan-story/scripts/record_planning_execution.py index b3859d6..2e87b12 100644 --- a/.claude/skills/planning/scripts/record_planning_execution.py +++ b/.claude/skills/plan-story/scripts/record_planning_execution.py @@ -13,7 +13,10 @@ import sys ROOT = pathlib.Path(__file__).resolve().parents[2] -for path in (ROOT / "db" / "scripts", ROOT / "runtime" / "scripts"): +for path in ( + ROOT / "access-database" / "scripts", + ROOT / "record-run-evidence" / "scripts", +): if str(path) not in sys.path: sys.path.insert(0, str(path)) from db import connect # noqa: E402 diff --git a/.claude/skills/planning/scripts/repair_deterministic_receipt.py b/.claude/skills/plan-story/scripts/repair_deterministic_receipt.py similarity index 97% rename from .claude/skills/planning/scripts/repair_deterministic_receipt.py rename to .claude/skills/plan-story/scripts/repair_deterministic_receipt.py index c7a226f..d066186 100644 --- a/.claude/skills/planning/scripts/repair_deterministic_receipt.py +++ b/.claude/skills/plan-story/scripts/repair_deterministic_receipt.py @@ -11,7 +11,10 @@ import sys ROOT = pathlib.Path(__file__).resolve().parents[2] -for path in (ROOT / "db" / "scripts", ROOT / "runtime" / "scripts"): +for path in ( + ROOT / "access-database" / "scripts", + ROOT / "record-run-evidence" / "scripts", +): if str(path) not in sys.path: sys.path.insert(0, str(path)) from db import connect # noqa: E402 diff --git a/.claude/skills/planning/scripts/select_patterns.py b/.claude/skills/plan-story/scripts/select_patterns.py similarity index 97% rename from .claude/skills/planning/scripts/select_patterns.py rename to .claude/skills/plan-story/scripts/select_patterns.py index 1a1bde8..51c5a0c 100644 --- a/.claude/skills/planning/scripts/select_patterns.py +++ b/.claude/skills/plan-story/scripts/select_patterns.py @@ -14,7 +14,7 @@ import click HERE = pathlib.Path(__file__).resolve().parent SKILLS = HERE.parents[1] -for path in (SKILLS / "planning" / "scripts", SKILLS / "search" / "scripts"): +for path in (SKILLS / "plan-story" / "scripts", SKILLS / "search-knowledge" / "scripts"): if str(path) not in sys.path: sys.path.insert(0, str(path)) from persist_planning import write_section # noqa: E402 diff --git a/.claude/skills/planning/scripts/test_field_coverage.py b/.claude/skills/plan-story/scripts/test_field_coverage.py similarity index 99% rename from .claude/skills/planning/scripts/test_field_coverage.py rename to .claude/skills/plan-story/scripts/test_field_coverage.py index 7ee5fd7..1d9a18d 100644 --- a/.claude/skills/planning/scripts/test_field_coverage.py +++ b/.claude/skills/plan-story/scripts/test_field_coverage.py @@ -11,7 +11,7 @@ import sys import unittest sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "db" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts")) from persist_planning import check_field_coverage, write_section, _load_schema # noqa: E402 diff --git a/.claude/skills/planning/scripts/test_record_planning_execution.py b/.claude/skills/plan-story/scripts/test_record_planning_execution.py similarity index 100% rename from .claude/skills/planning/scripts/test_record_planning_execution.py rename to .claude/skills/plan-story/scripts/test_record_planning_execution.py diff --git a/.claude/skills/planning/scripts/test_repair_deterministic_receipt.py b/.claude/skills/plan-story/scripts/test_repair_deterministic_receipt.py similarity index 100% rename from .claude/skills/planning/scripts/test_repair_deterministic_receipt.py rename to .claude/skills/plan-story/scripts/test_repair_deterministic_receipt.py diff --git a/.claude/skills/planning/scripts/test_select_patterns_offline.py b/.claude/skills/plan-story/scripts/test_select_patterns_offline.py similarity index 100% rename from .claude/skills/planning/scripts/test_select_patterns_offline.py rename to .claude/skills/plan-story/scripts/test_select_patterns_offline.py diff --git a/.claude/skills/polish/SKILL.md b/.claude/skills/polish-prose/SKILL.md similarity index 86% rename from .claude/skills/polish/SKILL.md rename to .claude/skills/polish-prose/SKILL.md index d6edbd8..2fc7320 100644 --- a/.claude/skills/polish/SKILL.md +++ b/.claude/skills/polish-prose/SKILL.md @@ -1,6 +1,6 @@ --- -name: polish -description: 润色的功能合同(scenario: polish,写作槽位;含纠错/去AI味/角色声音修正)。只动表达层,禁改情节事实与对白语义。 +name: polish-prose +description: 修正候选或既有正文的错字、语病、AI 味、节奏和角色声音。用户只要求改善表达层时使用;不得改变情节事实、场景结构或对白语义。 disable-model-invocation: true --- diff --git a/.claude/skills/record-run-evidence/SKILL.md b/.claude/skills/record-run-evidence/SKILL.md new file mode 100644 index 0000000..22ec8bd --- /dev/null +++ b/.claude/skills/record-run-evidence/SKILL.md @@ -0,0 +1,42 @@ +--- +name: record-run-evidence +description: 记录模型调用、运行登记、不可变回执、CAS revision 和受控 raw 证据。执行器或业务 Skill 需要持久化一次运行、追加失败证据、补回执引用或管理 raw 备份时使用;不负责调用模型或裁决内容质量。 +disable-model-invocation: true +--- + +# 记录运行证据 + +本 Skill 服务执行适配器和业务 Skill,只负责把一次运行的输入、输出、状态与证据保存成可核验记录。Claude 子进程执行属于 `execute-claude-task`,质量裁决属于 `score-content-quality`。 + +## scripts 入口 + +| 模块 | 职责 | +|---|---| +| `run_registry.py` | 创建和结束 `example_run`,维持运行状态与幂等边界。 | +| `persist_llm_call.py` | 把模型调用的 prompt、response、用量与 raw 指针原子登记。 | +| `persist_raw.py` | 将完整 raw 写入 `example_raw_lease` 与 `example_raw_content`,写前拦截凭据。 | +| `record_failed_run.py` | 为失败运行追加错误回执与隔离的质量结果。 | +| `repair_receipt_evidence.py` | 对成功回执追加 evidence revision,不更新旧回执。 | +| `file_cas.py` | 用内容哈希维护不可变 revision journal。 | +| `raw_vault.py` | 可选的仓外 raw 保险库与迁移回执,不是默认权威存储。 | + +## 合同 + +- raw 以 PostgreSQL 为主:完整 prompt、response、oracle 和供应商响应写入受控表;仓外 vault 仅作显式备份。 +- 证据只追加,不覆盖历史;内容哈希、owner、run_id 或回执绑定不完整时失败关闭。 +- 密钥、token 和凭据不得进入 raw 表、回执或错误对象。 +- `runtime` 等 creator/updater 值属于存量数据库审计标识,不因 Skill 名称变化而改写。 + +## 数据边界 + +- 读取运行、调用、raw 和回执当前状态,只为校验绑定与幂等。 +- 写入 `example_run`、`example_llm_call`、`example_raw_lease`、`example_raw_content`、`example_run_receipt` 和相应质量失败记录。 +- 不调用 Claude/New-API,不生成候选,不判断内容是否通过。 + +## 离线验证 + +```bash +.venv/bin/python .claude/skills/record-run-evidence/scripts/test_file_cas.py +.venv/bin/python .claude/skills/record-run-evidence/scripts/test_raw_vault.py +.venv/bin/python .claude/skills/record-run-evidence/scripts/test_run_registry.py +``` diff --git a/.claude/skills/runtime/scripts/file_cas.py b/.claude/skills/record-run-evidence/scripts/file_cas.py similarity index 100% rename from .claude/skills/runtime/scripts/file_cas.py rename to .claude/skills/record-run-evidence/scripts/file_cas.py diff --git a/.claude/skills/runtime/scripts/invariant_checks.py b/.claude/skills/record-run-evidence/scripts/invariant_checks.py similarity index 96% rename from .claude/skills/runtime/scripts/invariant_checks.py rename to .claude/skills/record-run-evidence/scripts/invariant_checks.py index 211cb4a..7212f8b 100644 --- a/.claude/skills/runtime/scripts/invariant_checks.py +++ b/.claude/skills/record-run-evidence/scripts/invariant_checks.py @@ -12,14 +12,14 @@ I3 评测候选的质量结果可被生产视图过滤(带评测标记,不 I4 oracle 读侧红线:oracle/标准答案以 kind='oracle' 标记,生产模型输入不得包含(read-context 生产路径保证;此处校验标记完整)。 I5 COMPLETED 轮次封存:一轮声明的 raw 集合 == 实际落库集合(lease 声明的 content_hashes 数 == 实际 raw_content 行数)。 -跑法:.venv/bin/python .claude/skills/runtime/scripts/invariant_checks.py [--run-id X] +跑法:.venv/bin/python .claude/skills/execute-claude-task/scripts/invariant_checks.py [--run-id X] """ import argparse import json import sys from pathlib import Path -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/runtime/scripts/persist_llm_call.py b/.claude/skills/record-run-evidence/scripts/persist_llm_call.py similarity index 100% rename from .claude/skills/runtime/scripts/persist_llm_call.py rename to .claude/skills/record-run-evidence/scripts/persist_llm_call.py diff --git a/.claude/skills/runtime/scripts/persist_raw.py b/.claude/skills/record-run-evidence/scripts/persist_raw.py similarity index 97% rename from .claude/skills/runtime/scripts/persist_raw.py rename to .claude/skills/record-run-evidence/scripts/persist_raw.py index 1d84f75..0aa4c34 100644 --- a/.claude/skills/runtime/scripts/persist_raw.py +++ b/.claude/skills/record-run-evidence/scripts/persist_raw.py @@ -13,8 +13,8 @@ import re import sys from pathlib import Path -# 复用 db skill 锁死的 DSN(.claude/skills/db/scripts) -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +# 复用 access-database Skill 锁死的 DSN +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/runtime/scripts/raw_vault.py b/.claude/skills/record-run-evidence/scripts/raw_vault.py similarity index 66% rename from .claude/skills/runtime/scripts/raw_vault.py rename to .claude/skills/record-run-evidence/scripts/raw_vault.py index 3fd2363..2312b32 100644 --- a/.claude/skills/runtime/scripts/raw_vault.py +++ b/.claude/skills/record-run-evidence/scripts/raw_vault.py @@ -21,6 +21,7 @@ from typing import Any, Mapping, Sequence HASH_PATTERN = __import__("re").compile(r"^sha256:[0-9a-f]{64}$") APPROVED_ROLES = frozenset({"用户", "创始人", "user", "founder"}) MAX_RETENTION = timedelta(hours=24) +ARCHIVE_PREFIX = "muse-raw-archive-" class RawVaultError(RuntimeError): @@ -173,6 +174,7 @@ class RawVaultManager: content_hashes: Sequence[str], purpose: str, retain_until: str, + min_remaining: timedelta | None = None, ) -> VaultLease: """先持久化 lease,再创建 0700 不可猜测 vault。""" @@ -193,6 +195,17 @@ class RawVaultManager: retention = _parse_time(retain_until, "retainUntil") if retention <= now or retention - now > MAX_RETENTION: raise RawVaultError("RAW_LEASE_INVALID", "raw 保留期限必须在未来 24 小时内") + # WHY: 调用方可声明「最低剩余租期」。若剩余窗口连一次最长调用加清理余量都覆盖不了 + # (例如只有 27 秒的租约),就必须在落 lease / 建 vault / 调模型之前稳定失败关闭, + # 而不是先创建再靠到期清理——那样会把敏感 raw 短暂暴露在授权窗口边缘。 + if min_remaining is not None: + if not isinstance(min_remaining, timedelta) or min_remaining < timedelta(0): + raise RawVaultError("RAW_LEASE_INVALID", "minRemaining 必须是非负时间间隔") + if retention - now < min_remaining: + raise RawVaultError( + "RAW_LEASE_INSUFFICIENT_RETENTION", + "raw 剩余租期不足以覆盖执行与清理", + ) vault_id = secrets.token_hex(16) lease = VaultLease( @@ -306,6 +319,10 @@ class RawVaultManager: """通过 openat/no-follow 在 vault 内排他创建 0600 raw 文件。""" self._assert_open_lease(lease) + # WHY: 租约到期后必须立即拒写,避免敏感字节在授权窗口外继续累积。清理/恢复路径 + # 只走 _assert_open_lease/_load_record,不经过这里,因此过期 lease 仍可被 cleanup/recover 收敛。 + if _parse_time(lease.retain_until, "retainUntil") <= _utc_now(): + raise RawVaultError("RAW_LEASE_EXPIRED", "raw 租约已过期,禁止写入") if not isinstance(content, bytes): raise RawVaultError("RAW_WRITE_FAILED", "raw 内容必须是 bytes") if not isinstance(relative_path, str): @@ -426,6 +443,166 @@ class RawVaultManager: raise RawVaultError("RAW_CLEANUP_FAILED", "raw vault 清理失败") from exc return self._close_lease(lease, "cleaned" if existed else "vault_missing") + def _vault_inventory(self, lease: VaultLease) -> tuple[str, int, int]: + """计算不公开文件名的 raw 内容树摘要,并拒绝软链接和非常规文件。""" + + self._assert_open_lease(lease) + root = self._vault_path(lease.vault_id) + entries: list[dict[str, Any]] = [] + + def visit(directory: pathlib.Path, relative: pathlib.PurePosixPath) -> None: + try: + children = sorted(os.scandir(directory), key=lambda item: item.name) + except OSError as exc: + raise RawVaultError("RAW_MIGRATION_FAILED", "raw vault 无法读取") from exc + for child in children: + child_relative = relative / child.name + try: + metadata = child.stat(follow_symlinks=False) + except OSError as exc: + raise RawVaultError("RAW_MIGRATION_FAILED", "raw 条目无法读取") from exc + if stat.S_ISLNK(metadata.st_mode): + raise RawVaultError("RAW_PATH_INVALID", "raw vault 含软链接") + if stat.S_ISDIR(metadata.st_mode): + visit(pathlib.Path(child.path), child_relative) + continue + if not stat.S_ISREG(metadata.st_mode): + raise RawVaultError("RAW_PATH_INVALID", "raw vault 含非常规文件") + digest = hashlib.sha256() + try: + descriptor = os.open(child.path, os.O_RDONLY | os.O_NOFOLLOW) + with os.fdopen(descriptor, "rb", closefd=True) as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + except OSError as exc: + raise RawVaultError("RAW_MIGRATION_FAILED", "raw 文件无法校验") from exc + entries.append( + { + "relativePath": child_relative.as_posix(), + "contentSha256": "sha256:" + digest.hexdigest(), + "sizeBytes": metadata.st_size, + } + ) + + visit(root, pathlib.PurePosixPath()) + archive_hash = "sha256:" + hashlib.sha256( + _canonical_json(entries).encode("utf-8") + ).hexdigest() + return archive_hash, len(entries), sum(item["sizeBytes"] for item in entries) + + def _archive_root(self, archive_root: str | pathlib.Path) -> pathlib.Path: + """准备 0700 的受控归档根,并要求与临时 vault 位于同一文件系统。""" + + raw_path = pathlib.Path(archive_root) + if not raw_path.is_absolute(): + raise RawVaultError("RAW_ARCHIVE_INVALID", "raw 归档根必须是绝对路径") + path = raw_path.absolute() + if path.is_symlink(): + raise RawVaultError("RAW_ARCHIVE_INVALID", "raw 归档根不能是软链接") + try: + path.mkdir(parents=True, exist_ok=True, mode=0o700) + if path.is_symlink() or not path.is_dir(): + raise OSError("archive root invalid") + os.chmod(path, 0o700, follow_symlinks=False) + if os.stat(path, follow_symlinks=False).st_dev != os.stat( + self.vault_root, follow_symlinks=False + ).st_dev: + raise RawVaultError( + "RAW_ARCHIVE_CROSS_DEVICE", + "raw 归档根必须与临时 vault 位于同一文件系统", + ) + except RawVaultError: + raise + except OSError as exc: + raise RawVaultError("RAW_ARCHIVE_INVALID", "raw 归档根不可用") from exc + return path + + def migrate( + self, lease: VaultLease, *, archive_root: str | pathlib.Path + ) -> dict[str, Any]: + """把完整 raw vault 原子迁移到受控归档;不删除正文,也不公开归档路径。""" + + self._assert_open_lease(lease) + archive = self._archive_root(archive_root) + archive_hash, file_count, total_bytes = self._vault_inventory(lease) + archive_id = lease.vault_id + destination_name = f"{ARCHIVE_PREFIX}{archive_id}" + destination = archive / destination_name + if destination.exists() or destination.is_symlink(): + raise RawVaultError("RAW_ARCHIVE_CONFLICT", "raw 归档 ID 冲突") + + migrating_record = self._lease_record(lease, status="migrating") + migrating_record.update( + { + "archiveId": archive_id, + "archiveSha256": archive_hash, + "fileCount": file_count, + "totalBytes": total_bytes, + } + ) + _atomic_write_json(self._lease_path(lease.vault_id), migrating_record) + + source_fd = os.open( + self.vault_root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW + ) + archive_fd = os.open(archive, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) + try: + os.rename( + self._vault_name(lease.vault_id), + destination_name, + src_dir_fd=source_fd, + dst_dir_fd=archive_fd, + ) + os.fsync(source_fd) + os.fsync(archive_fd) + except OSError as exc: + # WHY: rename 失败且源目录仍在时恢复 open journal,后续仍可重试迁移;若 rename + # 已发生但收口中断,则保留 migrating 记录和归档内容,绝不退回删除路径。 + if self._vault_path(lease.vault_id).exists(): + _atomic_write_json( + self._lease_path(lease.vault_id), + self._lease_record(lease, status="open"), + ) + raise RawVaultError("RAW_MIGRATION_FAILED", "raw vault 迁移失败") from exc + finally: + os.close(archive_fd) + os.close(source_fd) + + migrated_at = _utc_now().isoformat() + receipt = { + "schemaVersion": "raw-vault-migration-receipt-v1", + "vaultId": lease.vault_id, + "runId": lease.run_id, + "status": "migrated", + "archiveId": archive_id, + "archiveSha256": archive_hash, + "fileCount": file_count, + "totalBytes": total_bytes, + "migratedAt": migrated_at, + "retentionPolicy": "explicit_cleanup_required", + } + receipt_hash = "sha256:" + hashlib.sha256( + _canonical_json(receipt).encode("utf-8") + ).hexdigest() + try: + _atomic_write_json(destination / ".migration-receipt.json", receipt) + migrated_record = self._lease_record(lease, status="migrated") + migrated_record.update( + { + "archiveId": archive_id, + "archiveSha256": archive_hash, + "fileCount": file_count, + "totalBytes": total_bytes, + "migratedAt": migrated_at, + "retentionPolicy": "explicit_cleanup_required", + "migrationReceiptSha256": receipt_hash, + } + ) + _atomic_write_json(self._lease_path(lease.vault_id), migrated_record) + except OSError as exc: + raise RawVaultError("RAW_MIGRATION_FAILED", "raw 迁移回执写入失败") from exc + return receipt + def recover(self) -> dict[str, list[str]]: """扫描 open lease、缺失 vault 和同命名空间孤儿目录并安全收敛。""" @@ -438,6 +615,10 @@ class RawVaultManager: vault_id = lease_path.stem record = self._load_record(vault_id) known_vault_ids.add(vault_id) + if record.get("status") in {"migrating", "migrated"}: + # 迁移中的数据必须保留给人工恢复;已迁移数据的权威副本在受控归档根。 + # recover 不掌握归档根授权,因此绝不能把迁移状态降级成删除动作。 + continue if record.get("status") != "open": # closed lease 理论上不再有 raw;若崩溃或外部复制留下同名目录,恢复仍立即清理。 if self._delete_vault_name(self._vault_name(vault_id)): diff --git a/.claude/skills/runtime/scripts/record_failed_run.py b/.claude/skills/record-run-evidence/scripts/record_failed_run.py similarity index 98% rename from .claude/skills/runtime/scripts/record_failed_run.py rename to .claude/skills/record-run-evidence/scripts/record_failed_run.py index d196cf3..4810733 100644 --- a/.claude/skills/runtime/scripts/record_failed_run.py +++ b/.claude/skills/record-run-evidence/scripts/record_failed_run.py @@ -11,7 +11,7 @@ import pathlib import sys -DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/runtime/scripts/repair_receipt_evidence.py b/.claude/skills/record-run-evidence/scripts/repair_receipt_evidence.py similarity index 98% rename from .claude/skills/runtime/scripts/repair_receipt_evidence.py rename to .claude/skills/record-run-evidence/scripts/repair_receipt_evidence.py index 75cc991..5ad4502 100644 --- a/.claude/skills/runtime/scripts/repair_receipt_evidence.py +++ b/.claude/skills/record-run-evidence/scripts/repair_receipt_evidence.py @@ -10,7 +10,7 @@ import pathlib import sys -DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = pathlib.Path(__file__).resolve().parents[2] / "access-database" / "scripts" sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/runtime/scripts/run_registry.py b/.claude/skills/record-run-evidence/scripts/run_registry.py similarity index 98% rename from .claude/skills/runtime/scripts/run_registry.py rename to .claude/skills/record-run-evidence/scripts/run_registry.py index 8fe1758..d1f7538 100644 --- a/.claude/skills/runtime/scripts/run_registry.py +++ b/.claude/skills/record-run-evidence/scripts/run_registry.py @@ -13,7 +13,7 @@ import uuid from pathlib import Path -DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts" +DB_SCRIPTS = Path(__file__).resolve().parents[2] / "access-database" / "scripts" if str(DB_SCRIPTS) not in sys.path: sys.path.insert(0, str(DB_SCRIPTS)) from db import connect # noqa: E402 diff --git a/.claude/skills/runtime/scripts/test_file_cas.py b/.claude/skills/record-run-evidence/scripts/test_file_cas.py similarity index 100% rename from .claude/skills/runtime/scripts/test_file_cas.py rename to .claude/skills/record-run-evidence/scripts/test_file_cas.py diff --git a/.claude/skills/runtime/scripts/test_persist_raw.py b/.claude/skills/record-run-evidence/scripts/test_persist_raw.py similarity index 100% rename from .claude/skills/runtime/scripts/test_persist_raw.py rename to .claude/skills/record-run-evidence/scripts/test_persist_raw.py diff --git a/.claude/skills/record-run-evidence/scripts/test_raw_vault.py b/.claude/skills/record-run-evidence/scripts/test_raw_vault.py new file mode 100644 index 0000000..024a6a1 --- /dev/null +++ b/.claude/skills/record-run-evidence/scripts/test_raw_vault.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +"""raw vault 的 lease 顺序、权限、路径边界与恢复测试。""" + +from __future__ import annotations + +import json +import os +import pathlib +import stat +import sys +import tempfile +import unittest +from datetime import datetime, timedelta, timezone +from unittest import mock + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +sys.path.insert(0, str(SCRIPT_DIR)) + +import raw_vault # noqa: E402 +from raw_vault import RawVaultError, RawVaultManager # noqa: E402 + + +def future_time(hours: int = 1) -> str: + """生成不超过 24 小时的 UTC 保留截止时间。""" + + return (datetime.now(timezone.utc) + timedelta(hours=hours)).isoformat() + + +class RawVaultTest(unittest.TestCase): + """验证敏感字节只在有效 lease 覆盖下存在。""" + + def setUp(self) -> None: + """为每个测试创建独立 vault 根和非敏感 journal。""" + + self.temporary = tempfile.TemporaryDirectory() + root = pathlib.Path(self.temporary.name) + self.vault_root = root / "private-tmp" + self.journal_root = root / "journal" + self.vault_root.mkdir(mode=0o700) + self.manager = RawVaultManager(self.journal_root, vault_root=self.vault_root) + + def tearDown(self) -> None: + """删除测试产生的独立目录。""" + + self.temporary.cleanup() + + def _create(self): + """创建字段完整、审批角色合法的单次 lease。""" + + return self.manager.create_vault( + authorization_id="auth-1", + approved_by="创始人", + run_id="run-1", + source_version="source-v1", + content_hashes=("sha256:" + "a" * 64,), + purpose="offline_evaluation", + retain_until=future_time(), + ) + + def test_lease_precedes_vault_and_permissions_are_fixed(self): + """journal 必须先存在,随后 vault=0700、raw 文件=0600。""" + + lease = self._create() + lease_file = self.journal_root / "leases" / f"{lease.vault_id}.json" + self.assertTrue(lease_file.exists()) + self.assertEqual(stat.S_IMODE(lease_file.stat().st_mode), 0o600) + lease_json = json.loads(lease_file.read_text(encoding="utf-8")) + self.assertEqual(lease_json["status"], "open") + self.assertNotIn("vaultPath", lease_json) + + vaults = list(self.vault_root.iterdir()) + self.assertEqual(len(vaults), 1) + self.assertEqual(stat.S_IMODE(vaults[0].stat().st_mode), 0o700) + self.manager.write_bytes(lease, "responses/model.json", b"raw-secret") + raw_file = vaults[0] / "responses" / "model.json" + self.assertEqual(raw_file.read_bytes(), b"raw-secret") + self.assertEqual(stat.S_IMODE(raw_file.stat().st_mode), 0o600) + + def test_path_escape_absolute_path_and_symlink_are_rejected(self): + """调用方不能用绝对路径、.. 或软链接把 raw 写到 vault 外。""" + + lease = self._create() + for candidate in ("../escape", "/tmp/escape", "nested/../../escape"): + with self.subTest(candidate=candidate), self.assertRaises(RawVaultError) as raised: + self.manager.write_bytes(lease, candidate, b"secret") + self.assertEqual(raised.exception.code, "RAW_PATH_INVALID") + + vault = next(self.vault_root.iterdir()) + outside = pathlib.Path(self.temporary.name) / "outside" + outside.write_bytes(b"unchanged") + os.symlink(outside, vault / "link") + with self.assertRaises(RawVaultError) as raised: + self.manager.write_bytes(lease, "link", b"changed") + self.assertEqual(raised.exception.code, "RAW_PATH_INVALID") + self.assertEqual(outside.read_bytes(), b"unchanged") + + def test_cleanup_closes_lease_without_disclosing_raw_path(self): + """清理成功后 vault 消失、lease 关闭,回执不含本机 raw 路径。""" + + lease = self._create() + self.manager.write_bytes(lease, "input.json", b"secret") + receipt = self.manager.cleanup(lease) + + self.assertEqual(list(self.vault_root.iterdir()), []) + self.assertEqual(receipt["status"], "closed") + self.assertNotIn(str(self.vault_root), json.dumps(receipt, ensure_ascii=False)) + lease_json = json.loads( + (self.journal_root / "leases" / f"{lease.vault_id}.json").read_text(encoding="utf-8") + ) + self.assertEqual(lease_json["status"], "closed") + + def test_migrate_preserves_raw_and_writes_path_free_receipt(self): + """正式收口必须迁移完整 raw,源 vault 消失但受控归档内容和校验回执仍在。""" + + lease = self._create() + self.manager.write_bytes(lease, "inputs/prompt.json", b"prompt-secret") + self.manager.write_bytes(lease, "outputs/response.json", b"response-secret") + archive_root = pathlib.Path(self.temporary.name) / "archive" + + receipt = self.manager.migrate(lease, archive_root=archive_root) + + self.assertEqual(receipt["status"], "migrated") + self.assertEqual(receipt["fileCount"], 2) + self.assertEqual(receipt["retentionPolicy"], "explicit_cleanup_required") + self.assertNotIn(str(archive_root), json.dumps(receipt, ensure_ascii=False)) + self.assertEqual(list(self.vault_root.iterdir()), []) + archived = next(archive_root.glob(f"muse-raw-archive-{lease.vault_id}")) + self.assertEqual( + (archived / "inputs" / "prompt.json").read_bytes(), b"prompt-secret" + ) + self.assertEqual( + (archived / "outputs" / "response.json").read_bytes(), b"response-secret" + ) + self.assertTrue((archived / ".migration-receipt.json").is_file()) + self.assertEqual(stat.S_IMODE(archive_root.stat().st_mode), 0o700) + self.assertEqual(stat.S_IMODE(archived.stat().st_mode), 0o700) + lease_json = json.loads( + (self.journal_root / "leases" / f"{lease.vault_id}.json").read_text( + encoding="utf-8" + ) + ) + self.assertEqual(lease_json["status"], "migrated") + self.assertEqual(lease_json["archiveId"], lease.vault_id) + self.assertNotIn("archivePath", lease_json) + + def test_migrate_rejects_relative_archive_before_moving_raw(self): + """归档根不是绝对路径时必须失败关闭,原始 vault 与 open lease 保持可重试。""" + + lease = self._create() + self.manager.write_bytes(lease, "response.json", b"secret") + with self.assertRaises(RawVaultError) as raised: + self.manager.migrate(lease, archive_root="relative/archive") + self.assertEqual(raised.exception.code, "RAW_ARCHIVE_INVALID") + self.assertEqual(len(list(self.vault_root.iterdir())), 1) + lease_json = json.loads( + (self.journal_root / "leases" / f"{lease.vault_id}.json").read_text( + encoding="utf-8" + ) + ) + self.assertEqual(lease_json["status"], "open") + + def test_recover_never_deletes_migrating_vault(self): + """迁移中断必须保留 raw 供人工恢复,recover 不得退化为删除。""" + + lease = self._create() + self.manager.write_bytes(lease, "response.json", b"secret") + lease_path = self.journal_root / "leases" / f"{lease.vault_id}.json" + lease_json = json.loads(lease_path.read_text(encoding="utf-8")) + lease_json["status"] = "migrating" + raw_vault._atomic_write_json(lease_path, lease_json) + + recovery = self.manager.recover() + + self.assertEqual(recovery["cleanedVaultIds"], []) + self.assertEqual(len(list(self.vault_root.iterdir())), 1) + + def test_recovery_cleans_open_vault_and_closes_missing_lease(self): + """恢复扫描必须清理未关闭 vault,并补记已经缺失的 lease。""" + + existing = self._create() + self.manager.write_bytes(existing, "response.json", b"secret") + + missing = self.manager.create_vault( + authorization_id="auth-2", + approved_by="用户", + run_id="run-2", + source_version="source-v2", + content_hashes=("sha256:" + "b" * 64,), + purpose="offline_evaluation", + retain_until=future_time(), + ) + missing_dir = [path for path in self.vault_root.iterdir() if missing.vault_id in path.name][0] + missing_dir.rmdir() + + recovery = self.manager.recover() + + self.assertIn(existing.vault_id, recovery["cleanedVaultIds"]) + self.assertIn(missing.vault_id, recovery["closedMissingVaultIds"]) + self.assertEqual(list(self.vault_root.iterdir()), []) + + def test_recovery_removes_orphan_in_manager_namespace(self): + """同一管理器命名空间内没有 lease 的 raw 目录不能遗留。""" + + orphan = self.vault_root / f"{self.manager.vault_prefix}orphan" + orphan.mkdir(mode=0o700) + (orphan / "raw").write_bytes(b"secret") + os.chmod(orphan / "raw", 0o600) + + recovery = self.manager.recover() + + self.assertIn("orphan", recovery["cleanedOrphanIds"]) + self.assertFalse(orphan.exists()) + + def test_recovery_removes_raw_directory_left_after_closed_lease(self): + """lease 已关闭后若同名 raw 目录再次出现,恢复扫描仍必须删除。""" + + lease = self._create() + vault_name = next(self.vault_root.iterdir()).name + self.manager.cleanup(lease) + stale = self.vault_root / vault_name + stale.mkdir(mode=0o700) + (stale / "raw").write_bytes(b"secret") + os.chmod(stale / "raw", 0o600) + + recovery = self.manager.recover() + + self.assertIn(lease.vault_id, recovery["cleanedVaultIds"]) + self.assertFalse(stale.exists()) + + def test_create_rejects_insufficient_min_remaining_without_lease_or_vault(self): + """声明的最低剩余租期不足时必须在落 lease / 建 vault 前稳定失败关闭。""" + + short_retention = (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat() + with self.assertRaises(RawVaultError) as raised: + self.manager.create_vault( + authorization_id="auth-min", + approved_by="创始人", + run_id="run-min", + source_version="source-v1", + content_hashes=("sha256:" + "d" * 64,), + purpose="offline_evaluation", + retain_until=short_retention, + min_remaining=timedelta(seconds=90), + ) + self.assertEqual(raised.exception.code, "RAW_LEASE_INSUFFICIENT_RETENTION") + # 失败关闭不能留下任何 lease journal 或 raw vault 目录。 + self.assertEqual(list((self.journal_root / "leases").iterdir()), []) + self.assertEqual(list(self.vault_root.iterdir()), []) + + def test_create_accepts_sufficient_min_remaining(self): + """剩余租期覆盖最低要求时正常创建 lease 与 vault。""" + + lease = self.manager.create_vault( + authorization_id="auth-ok", + approved_by="创始人", + run_id="run-ok", + source_version="source-v1", + content_hashes=("sha256:" + "e" * 64,), + purpose="offline_evaluation", + retain_until=future_time(hours=1), + min_remaining=timedelta(seconds=90), + ) + self.assertTrue((self.journal_root / "leases" / f"{lease.vault_id}.json").exists()) + self.assertEqual(len(list(self.vault_root.iterdir())), 1) + + def test_write_rejected_after_expiry_but_cleanup_still_works(self): + """租约到期后 write 立即失败关闭,但 cleanup 仍能收敛过期 lease。""" + + base = datetime(2026, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + clock = {"now": base} + with mock.patch.object(raw_vault, "_utc_now", side_effect=lambda: clock["now"]): + lease = self.manager.create_vault( + authorization_id="auth-exp", + approved_by="创始人", + run_id="run-exp", + source_version="source-v1", + content_hashes=("sha256:" + "c" * 64,), + purpose="offline_evaluation", + retain_until=(base + timedelta(seconds=10)).isoformat(), + ) + self.manager.write_bytes(lease, "input.json", b"secret") + clock["now"] = base + timedelta(seconds=20) # 越过 retainUntil + with self.assertRaises(RawVaultError) as raised: + self.manager.write_bytes(lease, "late.json", b"secret") + self.assertEqual(raised.exception.code, "RAW_LEASE_EXPIRED") + # 过期不能阻断清理:cleanup 仍必须删除 vault 并关闭 lease。 + receipt = self.manager.cleanup(lease) + self.assertEqual(receipt["status"], "closed") + self.assertEqual(list(self.vault_root.iterdir()), []) + + def test_recover_cleans_expired_open_lease(self): + """恢复扫描必须能收敛已过期的 open lease,不因过期而卡住清理。""" + + base = datetime(2026, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + clock = {"now": base} + with mock.patch.object(raw_vault, "_utc_now", side_effect=lambda: clock["now"]): + lease = self.manager.create_vault( + authorization_id="auth-rec", + approved_by="用户", + run_id="run-rec", + source_version="source-v1", + content_hashes=("sha256:" + "f" * 64,), + purpose="offline_evaluation", + retain_until=(base + timedelta(seconds=10)).isoformat(), + ) + self.manager.write_bytes(lease, "response.json", b"secret") + clock["now"] = base + timedelta(seconds=20) # 越过 retainUntil + recovery = self.manager.recover() + self.assertIn(lease.vault_id, recovery["cleanedVaultIds"]) + self.assertEqual(list(self.vault_root.iterdir()), []) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/runtime/scripts/test_record_failed_run.py b/.claude/skills/record-run-evidence/scripts/test_record_failed_run.py similarity index 100% rename from .claude/skills/runtime/scripts/test_record_failed_run.py rename to .claude/skills/record-run-evidence/scripts/test_record_failed_run.py diff --git a/.claude/skills/runtime/scripts/test_repair_receipt_evidence.py b/.claude/skills/record-run-evidence/scripts/test_repair_receipt_evidence.py similarity index 100% rename from .claude/skills/runtime/scripts/test_repair_receipt_evidence.py rename to .claude/skills/record-run-evidence/scripts/test_repair_receipt_evidence.py diff --git a/.claude/skills/runtime/scripts/test_run_registry.py b/.claude/skills/record-run-evidence/scripts/test_run_registry.py similarity index 100% rename from .claude/skills/runtime/scripts/test_run_registry.py rename to .claude/skills/record-run-evidence/scripts/test_run_registry.py diff --git a/.claude/skills/replay-eval/scripts/test_gate_input_builder.py b/.claude/skills/replay-eval/scripts/test_gate_input_builder.py deleted file mode 100644 index 9b6fa8e..0000000 --- a/.claude/skills/replay-eval/scripts/test_gate_input_builder.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -"""GateInputBuilder 的安全输入与同 ID 混淆项测试。""" - -from __future__ import annotations - -import copy -import pathlib -import sys -import unittest - -SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -QUALITY_GATE_DIR = SCRIPT_DIR.parents[1] / "quality-gate" / "scripts" -for _import_dir in (SCRIPT_DIR, QUALITY_GATE_DIR): - if str(_import_dir) not in sys.path: - sys.path.insert(0, str(_import_dir)) - -from gate_input_builder import GateInputBuildError, GateInputBuilder, canonical_sha256 -from test_writer_gate import build_input, source_bundle - - -class GateInputBuilderTest(unittest.TestCase): - @staticmethod - def _rehash_wrapper(wrapper): - """只重签 wrapper,故意不替攻击者重签生产内层回执。""" - - wrapper["receiptSha256"] = canonical_sha256( - {key: value for key, value in wrapper.items() if key != "receiptSha256"} - ) - - def test_builder_emits_v2_and_complete_confounders(self): - gate_input = build_input(source_bundle()) - self.assertEqual(gate_input["schemaVersion"], "writer-gate-input-v2") - self.assertTrue(gate_input["builderReceiptSha256"].startswith("sha256:")) - self.assertEqual( - set(gate_input["samples"][0]["confounders"]), - { - "falseNegative", - "falsePositive", - "leakage", - "reviewerInstability", - "newCharactersWithoutCards", - }, - ) - - def test_sensitive_manifest_field_is_rejected(self): - bundle = source_bundle() - bundle["manifest"]["samples"][0]["candidateBody"] = "正文不得进入安全 manifest" - with self.assertRaisesRegex(GateInputBuildError, "敏感字段"): - GateInputBuilder().build(**bundle) - - def test_same_id_and_candidate_binding_are_mandatory(self): - bundle = source_bundle() - verdict = bundle["detector_reports"]["sample-1"]["A"]["assertionVerdicts"][0] - verdict["assertionId"] = "forged-id" - report = bundle["detector_reports"]["sample-1"]["A"] - from gate_input_builder import canonical_sha256 - report["reportSha256"] = canonical_sha256( - {key: value for key, value in report.items() if key != "reportSha256"} - ) - gate_input = build_input(bundle) - self.assertTrue(gate_input["samples"][0]["systemFailure"]) - self.assertFalse(gate_input["samples"][0]["schemaValid"]) - - def test_empty_wrong_role_and_unbound_inner_execution_receipts_fail_closed(self): - """wrapper 自哈希不能替代生产 runtime 原始 ExecutionReceipt 绑定。""" - - mutations = { - "empty": lambda wrapper: wrapper.update( - {"executionReceipts": [], "executionReceiptSha256": []} - ), - "wrong_role": lambda wrapper: wrapper["executionReceipts"][0].update( - {"adapterRole": "semantic_detector"} - ), - "unbound": lambda wrapper: wrapper["executionReceipts"][0].update( - {"inputSha256": "sha256:" + "f" * 64} - ), - } - expected_reason = { - "empty": "A_writer_inner_receipts_empty", - "wrong_role": "A_writer_inner_receipt_role_mismatch", - "unbound": "A_writer_inner_receipt_hash_mismatch", - } - for name, mutate in mutations.items(): - bundle = source_bundle(1, gate="A") - wrapper = bundle["execution_receipts"]["sample-1"]["A"]["writer"] - mutate(wrapper) - self._rehash_wrapper(wrapper) - - gate_input = build_input(bundle) - - sample = gate_input["samples"][0] - with self.subTest(name=name): - self.assertTrue(sample["systemFailure"]) - self.assertIn(expected_reason[name], sample["systemFailureReasons"]) - - def test_judge_score_tampering_without_original_hash_resign_fails_closed(self): - """篡改稳定报告分数但保留生产原始 hash 时不得进入 Gate 分数恢复。""" - - bundle = source_bundle(1, gate="A") - report = bundle["judge_reports"]["sample-1"] - original_report_hash = report["reportSha256"] - report["candidateScores"][0]["scores"]["setting_entity_fidelity"] = 999 - - gate_input = build_input(bundle) - - sample = gate_input["samples"][0] - self.assertEqual(report["reportSha256"], original_report_hash) - self.assertTrue(sample["systemFailure"]) - self.assertFalse(sample["schemaValid"]) - self.assertTrue( - any("judgeReports.sample-1.reportSha256 不一致" in reason for reason in sample["systemFailureReasons"]) - ) - - def test_judge_report_resign_cannot_replace_original_panel_receipt_binding(self): - """攻击者重签报告自哈希后,仍不能替换生产 panel 的原始模型回执集合。""" - - bundle = source_bundle(1, gate="A") - report = bundle["judge_reports"]["sample-1"] - report["modelReceiptSha256"] = "sha256:" + "e" * 64 - report["reportSha256"] = canonical_sha256( - {key: value for key, value in report.items() if key != "reportSha256"} - ) - - gate_input = build_input(bundle) - - sample = gate_input["samples"][0] - self.assertTrue(sample["systemFailure"]) - self.assertFalse(sample["schemaValid"]) - self.assertTrue( - any("未绑定原始 panel 回执" in reason for reason in sample["systemFailureReasons"]) - ) - - def test_legal_score_tampering_and_report_resign_still_fails_receipt_binding(self): - """合法 9.5 分即使重签 panel 自哈希,也必须与模型原始 structured output 冲突。""" - - bundle = source_bundle(1, gate="A") - report = bundle["judge_reports"]["sample-1"] - report["candidateScores"][0]["scores"]["setting_entity_fidelity"] = 9.5 - report["reportSha256"] = canonical_sha256( - {key: value for key, value in report.items() if key != "reportSha256"} - ) - - gate_input = build_input(bundle) - - sample = gate_input["samples"][0] - self.assertTrue(sample["systemFailure"]) - self.assertFalse(sample["schemaValid"]) - self.assertTrue( - any("模型原始 structured output" in reason for reason in sample["systemFailureReasons"]) - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/.claude/skills/review-cards/SKILL.md b/.claude/skills/review-knowledge-cards/SKILL.md similarity index 86% rename from .claude/skills/review-cards/SKILL.md rename to .claude/skills/review-knowledge-cards/SKILL.md index 842d7ac..bf0c7cd 100644 --- a/.claude/skills/review-cards/SKILL.md +++ b/.claude/skills/review-knowledge-cards/SKILL.md @@ -1,9 +1,9 @@ --- -name: review-cards -description: 公共知识卡三角色审核(番茄作家/起点作家/主编)——拆书流程的常设步骤,MiniMax-M3 执行;检查内容成立性、AI 创作可用性、跨书可参考性,判定 pass/revise/reject 写回卡片。fable/opus 只做起量前校准与起量后总审核。 +name: review-knowledge-cards +description: 以三种编辑视角审核公共知识卡的成立性、创作可用性和跨书复用性,并写回 pass、revise 或 reject。拆书产出公共卡后使用;不负责生成卡或确认作品私有知识。 --- -# review-cards —— 公共知识卡三角色审核(M3 常设) +# 审核公共知识卡 创始人拍板(2026-07-13):审核是**流程内可复用资产**,不是每批都请 claude 角色扮演—— - **常设执行**:MiniMax-M3 扮三角色(番茄作家=爽点商业性 / 起点作家=结构工艺 / 主编=资产 vs 噪声),每批拆书产出的卡过一遍; @@ -14,10 +14,10 @@ description: 公共知识卡三角色审核(番茄作家/起点作家/主编 ```bash # 审核(全部或指定书的活卡;三角色各一次 M3 调用) -.venv/bin/python .claude/skills/review-cards/scripts/review_cards.py review --batch <批次> [--work-id N] [--dry-run] +.venv/bin/python .claude/skills/review-knowledge-cards/scripts/review_cards.py review --batch <批次> [--work-id N] [--dry-run] # 校准:与 golden/scores.json(opus 金标准)对照(换模型/改 prompt 后必跑) -.venv/bin/python .claude/skills/review-cards/scripts/review_cards.py calibrate --batch <批次> +.venv/bin/python .claude/skills/review-knowledge-cards/scripts/review_cards.py calibrate --batch <批次> ``` 判定阈值:三角色九维均分 ≥3.5 pass / ≥2.5 revise / 其余 reject。 diff --git a/.claude/skills/review-cards/golden/scores-cal002-m3旧尺子对照.json b/.claude/skills/review-knowledge-cards/golden/scores-cal002-m3旧尺子对照.json similarity index 100% rename from .claude/skills/review-cards/golden/scores-cal002-m3旧尺子对照.json rename to .claude/skills/review-knowledge-cards/golden/scores-cal002-m3旧尺子对照.json diff --git a/.claude/skills/review-cards/golden/scores-cal002.json b/.claude/skills/review-knowledge-cards/golden/scores-cal002.json similarity index 100% rename from .claude/skills/review-cards/golden/scores-cal002.json rename to .claude/skills/review-knowledge-cards/golden/scores-cal002.json diff --git a/.claude/skills/review-cards/golden/scores.json b/.claude/skills/review-knowledge-cards/golden/scores.json similarity index 100% rename from .claude/skills/review-cards/golden/scores.json rename to .claude/skills/review-knowledge-cards/golden/scores.json diff --git a/.claude/skills/review-cards/golden/金标准-主编-2026-07-13.md b/.claude/skills/review-knowledge-cards/golden/金标准-主编-2026-07-13.md similarity index 100% rename from .claude/skills/review-cards/golden/金标准-主编-2026-07-13.md rename to .claude/skills/review-knowledge-cards/golden/金标准-主编-2026-07-13.md diff --git a/.claude/skills/review-cards/golden/金标准-番茄作家-2026-07-13.md b/.claude/skills/review-knowledge-cards/golden/金标准-番茄作家-2026-07-13.md similarity index 100% rename from .claude/skills/review-cards/golden/金标准-番茄作家-2026-07-13.md rename to .claude/skills/review-knowledge-cards/golden/金标准-番茄作家-2026-07-13.md diff --git a/.claude/skills/review-cards/golden/金标准-起点作家-2026-07-13.md b/.claude/skills/review-knowledge-cards/golden/金标准-起点作家-2026-07-13.md similarity index 100% rename from .claude/skills/review-cards/golden/金标准-起点作家-2026-07-13.md rename to .claude/skills/review-knowledge-cards/golden/金标准-起点作家-2026-07-13.md diff --git a/.claude/skills/review-cards/scripts/review_cards.py b/.claude/skills/review-knowledge-cards/scripts/review_cards.py similarity index 98% rename from .claude/skills/review-cards/scripts/review_cards.py rename to .claude/skills/review-knowledge-cards/scripts/review_cards.py index 89f516e..06fa5f6 100644 --- a/.claude/skills/review-cards/scripts/review_cards.py +++ b/.claude/skills/review-knowledge-cards/scripts/review_cards.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""review-cards skill:公共知识卡三角色审核(拆书流程常设步骤,MiniMax-M3 执行)。 +"""review-knowledge-cards Skill:公共知识卡三角色审核(拆书流程常设步骤,MiniMax-M3 执行)。 创始人拍板(2026-07-13):审核是流程内可复用资产——放量时每批卡由 M3 扮演 番茄作家/起点作家/主编三角色审核;fable/opus 只在起量前校准本 skill 质量 @@ -16,7 +16,7 @@ import sys import click import psycopg -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "llm" / "scripts")) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "call-content-model" / "scripts")) from llm import chat_governed, extract_json, BUDGET_CHAIN # noqa: E402 DSN = ("postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example" @@ -78,7 +78,7 @@ REVIEW_PROMPT = """{criteria} def chat_degrade_review(prompt, system, model): """薄封装 chat_governed(全局统一降级链 + 额度治理)。 返回 (content, usage, used_model);全链耗尽返回 (None, None, None) 交上层标 blocked(不崩事务)。""" - return chat_governed(prompt, model=model, system=system) + return chat_governed(prompt, model=model, system=system, caller="review-cards") def load_cards(conn, work_id=None): diff --git a/.claude/skills/rewrite/SKILL.md b/.claude/skills/rewrite-selection/SKILL.md similarity index 76% rename from .claude/skills/rewrite/SKILL.md rename to .claude/skills/rewrite-selection/SKILL.md index d26a76e..697d9af 100644 --- a/.claude/skills/rewrite/SKILL.md +++ b/.claude/skills/rewrite-selection/SKILL.md @@ -1,6 +1,6 @@ --- -name: rewrite -description: 改写的功能合同(scenario: rewrite,写作槽位)。按用户意见重写指定范围,范围锁定+事实锚+版本核对。 +name: rewrite-selection +description: 按用户意见重写指定段落或场景,并校验范围、事实锚和版本。用户明确点名修改范围时使用;范围外原文不动,不顺手改规划或知识卡。 disable-model-invocation: true --- @@ -10,7 +10,7 @@ disable-model-invocation: true ## 元数据驱动 -- 章结构与行为约束同 `continuation`(chapter/scene 字段合同、style/character/power_system 字段值);改写不豁免任何字段约束。 +- 章结构与行为约束同 `write-next-chapter`(chapter/scene 字段合同、style/character/power_system 字段值);改写不豁免任何字段约束。 - 操作对象带 expectedRevision(L0):由派发方(主会话)动笔前核对目标文件即当前版本,不一致先报再改;写手只接收核对后的范围文本与 expectedRevision,不读写工作区。 ## 功能约束 diff --git a/.claude/skills/runtime/SKILL.md b/.claude/skills/runtime/SKILL.md deleted file mode 100644 index ee07eec..0000000 --- a/.claude/skills/runtime/SKILL.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: runtime -description: 受控 Claude CLI 子进程调用运行时(fresh/sandbox/deadline/structured_output/联合执行回执)+ 不可变 CAS revision journal(file_cas)+ 租约 raw vault 底座。被 detect/quality-gate/continuation/replay-eval 复用,不依赖任何 skill。 -disable-model-invocation: true ---- - -# 受控运行时底座(三层架构最底层,箭头只向下) - -Claude CLI 受控子进程调用(fresh/sandbox/deadline/structured_output/回执)+ 不可变 CAS revision journal + 租约 raw vault 底座;被 detect/quality-gate/continuation/replay-eval 复用,不依赖任何 skill。 - -## scripts 入口 - -| 模块 | 职责 | -|---|---| -| `claude_runtime.py` | 受控 Claude CLI 调用运行时:只接受显式冻结的执行 profile,负责 fresh process、sandbox、最小环境、硬 deadline、structured_output 唯一业务出口和联合执行回执;任何不完整证据失败关闭,错误对象永不携带 stderr 原文。服务生产与评测两侧,不只评测。 | -| `file_cas.py` | 不可变内容寻址(CAS)revision journal:按内容哈希落盘,冲突即失败关闭,支撑 Gate receipt 与逐层回执的不可篡改。 | -| `raw_vault.py` | 租约式 raw 物料保险库:原始候选/物料先写 0700 临时 vault,正式评测结束后同盘原子迁移到显式仓外受控归档;安全回执不携带 raw 路径。**raw 落库改写后降为可选备份**(见下)。 | -| `persist_raw.py` | raw 落库(2026-07-30 改写):完整 prompt/response/oracle/原书全文/供应商响应落 `example_raw_lease` + `example_raw_content`(访问控制 + append-only),写入前过滤密钥,看板可看全文。 | -| `repair_receipt_evidence.py` | 仅对成功回执追加不可变 receipt revision,把已存在的模型调用 raw 指针补回回执;失败回执不参与补证,不更新旧账。 | -| `record_failed_run.py` | 为失败运行追加 `is_error=true` 的回执和按 sample 隔离的 `runtime-failure-v1` 质量结果,保留安全失败类型与 raw 指针。 | - -## 依赖方向 - -本 skill 是底座,**不 import 任何其它 skill**。上层(detect/quality-gate/continuation/replay-eval)单向向下引用本 skill 的 scripts。 - -## raw lease 合同 - -- 会持有 raw 物料进行长时执行的调用方必须声明最低剩余租期;raw vault 在 lease journal 落盘前拒绝剩余时间不足的租约。短时离线测试可沿用默认值。 -- `retainUntil` 到期后禁止继续写入 raw;过期且尚未迁移的 lease 仍允许执行显式 `cleanup` 和 `recover`,以便收敛 open 状态。 -- 正式评测正常、质量失败或受控终止均调用 `migrate`,把整个 vault 原子迁往调用方显式提供的绝对归档根;归档根固定 0700、必须与临时 vault 同一文件系统,lease/report 只记录 `archiveId`、内容树哈希、文件数、字节数和迁移回执哈希,不记录路径。 -- 迁移终态是 `migrated`,`retentionPolicy=explicit_cleanup_required`;`recover` 不得删除 `migrating/migrated` 数据。`cleanup` 只用于另行明确授权的删除或尚未形成可迁移轮次的过期残留,不能作为正式 Gate A 的默认收口。 -- 默认真实 Claude 调用必须持有 `Popen` 进程句柄并使用独立进程组;deadline、SIGTERM/SIGINT 转换后的受控异常或其它父进程异常发生时,必须先终止并等待整个模型进程组,再进入预算、raw 和 manifest 收口,禁止留下继续运行或计费的孤立子进程。 - -## raw 落库合同(2026-07-30 改写,创始人批准) - -- **raw 以落库为主**(`persist_raw.py`):`example_raw_lease`(元数据)+ `example_raw_content`(全文),访问控制 + append-only;看板可看全文。默认不进普通检索,看全文须显式指定。 -- **仓外 vault 降为可选备份**:`raw_vault.py` 的 lease/migrate 机制保留作可选备份,不再是合同要求。 -- **废时效语义**:`retain_until` 只留档溯源,不触发自动删除;append-only 保证证据不可变。 -- **密钥/token/凭据绝不入表**:写入前过滤(`persist_raw.py` 标记检测)+ 列级 CHECK 双禁。 -- **后续(重活,需评测测试)**:replay-eval 的 COMPLETED 判据从"migrate 成功"改为"已落库"、raw_vault 接线相应调整——影响评测 harness,须单独一步带测试做,不在本轮。 diff --git a/.claude/skills/runtime/scripts/test_raw_vault.py b/.claude/skills/runtime/scripts/test_raw_vault.py deleted file mode 100644 index 46b116e..0000000 --- a/.claude/skills/runtime/scripts/test_raw_vault.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 -"""raw vault 的 lease 顺序、权限、路径边界与恢复测试。""" - -from __future__ import annotations - -import json -import os -import pathlib -import stat -import sys -import tempfile -import unittest -from datetime import datetime, timedelta, timezone - -SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -sys.path.insert(0, str(SCRIPT_DIR)) - -from raw_vault import RawVaultError, RawVaultManager # noqa: E402 - - -def future_time(hours: int = 1) -> str: - """生成不超过 24 小时的 UTC 保留截止时间。""" - - return (datetime.now(timezone.utc) + timedelta(hours=hours)).isoformat() - - -class RawVaultTest(unittest.TestCase): - """验证敏感字节只在有效 lease 覆盖下存在。""" - - def setUp(self) -> None: - """为每个测试创建独立 vault 根和非敏感 journal。""" - - self.temporary = tempfile.TemporaryDirectory() - root = pathlib.Path(self.temporary.name) - self.vault_root = root / "private-tmp" - self.journal_root = root / "journal" - self.vault_root.mkdir(mode=0o700) - self.manager = RawVaultManager(self.journal_root, vault_root=self.vault_root) - - def tearDown(self) -> None: - """删除测试产生的独立目录。""" - - self.temporary.cleanup() - - def _create(self): - """创建字段完整、审批角色合法的单次 lease。""" - - return self.manager.create_vault( - authorization_id="auth-1", - approved_by="创始人", - run_id="run-1", - source_version="source-v1", - content_hashes=("sha256:" + "a" * 64,), - purpose="offline_evaluation", - retain_until=future_time(), - ) - - def test_lease_precedes_vault_and_permissions_are_fixed(self): - """journal 必须先存在,随后 vault=0700、raw 文件=0600。""" - - lease = self._create() - lease_file = self.journal_root / "leases" / f"{lease.vault_id}.json" - self.assertTrue(lease_file.exists()) - self.assertEqual(stat.S_IMODE(lease_file.stat().st_mode), 0o600) - lease_json = json.loads(lease_file.read_text(encoding="utf-8")) - self.assertEqual(lease_json["status"], "open") - self.assertNotIn("vaultPath", lease_json) - - vaults = list(self.vault_root.iterdir()) - self.assertEqual(len(vaults), 1) - self.assertEqual(stat.S_IMODE(vaults[0].stat().st_mode), 0o700) - self.manager.write_bytes(lease, "responses/model.json", b"raw-secret") - raw_file = vaults[0] / "responses" / "model.json" - self.assertEqual(raw_file.read_bytes(), b"raw-secret") - self.assertEqual(stat.S_IMODE(raw_file.stat().st_mode), 0o600) - - def test_path_escape_absolute_path_and_symlink_are_rejected(self): - """调用方不能用绝对路径、.. 或软链接把 raw 写到 vault 外。""" - - lease = self._create() - for candidate in ("../escape", "/tmp/escape", "nested/../../escape"): - with self.subTest(candidate=candidate), self.assertRaises(RawVaultError) as raised: - self.manager.write_bytes(lease, candidate, b"secret") - self.assertEqual(raised.exception.code, "RAW_PATH_INVALID") - - vault = next(self.vault_root.iterdir()) - outside = pathlib.Path(self.temporary.name) / "outside" - outside.write_bytes(b"unchanged") - os.symlink(outside, vault / "link") - with self.assertRaises(RawVaultError) as raised: - self.manager.write_bytes(lease, "link", b"changed") - self.assertEqual(raised.exception.code, "RAW_PATH_INVALID") - self.assertEqual(outside.read_bytes(), b"unchanged") - - def test_cleanup_closes_lease_without_disclosing_raw_path(self): - """清理成功后 vault 消失、lease 关闭,回执不含本机 raw 路径。""" - - lease = self._create() - self.manager.write_bytes(lease, "input.json", b"secret") - receipt = self.manager.cleanup(lease) - - self.assertEqual(list(self.vault_root.iterdir()), []) - self.assertEqual(receipt["status"], "closed") - self.assertNotIn(str(self.vault_root), json.dumps(receipt, ensure_ascii=False)) - lease_json = json.loads( - (self.journal_root / "leases" / f"{lease.vault_id}.json").read_text(encoding="utf-8") - ) - self.assertEqual(lease_json["status"], "closed") - - def test_recovery_cleans_open_vault_and_closes_missing_lease(self): - """恢复扫描必须清理未关闭 vault,并补记已经缺失的 lease。""" - - existing = self._create() - self.manager.write_bytes(existing, "response.json", b"secret") - - missing = self.manager.create_vault( - authorization_id="auth-2", - approved_by="用户", - run_id="run-2", - source_version="source-v2", - content_hashes=("sha256:" + "b" * 64,), - purpose="offline_evaluation", - retain_until=future_time(), - ) - missing_dir = [path for path in self.vault_root.iterdir() if missing.vault_id in path.name][0] - missing_dir.rmdir() - - recovery = self.manager.recover() - - self.assertIn(existing.vault_id, recovery["cleanedVaultIds"]) - self.assertIn(missing.vault_id, recovery["closedMissingVaultIds"]) - self.assertEqual(list(self.vault_root.iterdir()), []) - - def test_recovery_removes_orphan_in_manager_namespace(self): - """同一管理器命名空间内没有 lease 的 raw 目录不能遗留。""" - - orphan = self.vault_root / f"{self.manager.vault_prefix}orphan" - orphan.mkdir(mode=0o700) - (orphan / "raw").write_bytes(b"secret") - os.chmod(orphan / "raw", 0o600) - - recovery = self.manager.recover() - - self.assertIn("orphan", recovery["cleanedOrphanIds"]) - self.assertFalse(orphan.exists()) - - def test_recovery_removes_raw_directory_left_after_closed_lease(self): - """lease 已关闭后若同名 raw 目录再次出现,恢复扫描仍必须删除。""" - - lease = self._create() - vault_name = next(self.vault_root.iterdir()).name - self.manager.cleanup(lease) - stale = self.vault_root / vault_name - stale.mkdir(mode=0o700) - (stale / "raw").write_bytes(b"secret") - os.chmod(stale / "raw", 0o600) - - recovery = self.manager.recover() - - self.assertIn(lease.vault_id, recovery["cleanedVaultIds"]) - self.assertFalse(stale.exists()) - - -if __name__ == "__main__": - unittest.main() diff --git a/.claude/skills/quality-gate/SKILL.md b/.claude/skills/score-content-quality/SKILL.md similarity index 56% rename from .claude/skills/quality-gate/SKILL.md rename to .claude/skills/score-content-quality/SKILL.md index c93a52c..a158b3d 100644 --- a/.claude/skills/quality-gate/SKILL.md +++ b/.claude/skills/score-content-quality/SKILL.md @@ -1,6 +1,6 @@ --- -name: quality-gate -description: 质量评分的功能合同(scenario: quality_gate/writer_replay/fine_outline_replay,保护节点)。在线正文、正文回放和细纲回放 rubric 分开管理;rubric 与达标口径的单一来源。 +name: score-content-quality +description: 按独立 rubric 评分在线正文、正文回放或细纲回放,并产生逐维证据和 Gate 裁决输入。候选交付用户前或回放实验需要独立质量判断时使用;不改候选、不放宽阈值。 disable-model-invocation: true --- @@ -16,9 +16,9 @@ disable-model-invocation: true | `fine_outline_rubric.py` | 细纲 rubric 维度定义与报告校验(`fine_outline_replay`)。 | | `writer_gate.py` | Gate A/B 确定性唯一判定器:只消费逐样本脱敏结果,可信边界内计算全部统计值,产出 Gate 终态与 receipt。 | | `gate_input_builder.py` | 从不可变逐层回执唯一构建 `writer-gate-input`。 | -| `run_writer_blind_judge.py` | blind judge v3 匿名模型适配器与独立评审编排(向下引用 `runtime`)。 | +| `run_writer_blind_judge.py` | blind judge v4 输入/报告绑定与 v3 模型输出适配、独立评审编排(向下引用 `execute-claude-task`)。 | -依赖方向:本 skill 向下引用 `runtime`(受控运行时底座),向上被 `replay-eval` 编排引用。评分/裁决模块(rubric/gate)的单一归属在本 skill。 +依赖方向:本 Skill 向下引用 `execute-claude-task` 与 `record-run-evidence`,向上被 `evaluate-frozen-replay` 编排引用。评分/裁决模块(rubric/gate)的单一归属在本 Skill。 ## 元数据驱动 @@ -73,14 +73,24 @@ scores: ## 正文回放 profile(`writer_replay`) -正文回放与在线 `quality_gate`、细纲 `fine_outline_replay` 完全独立。每次模型调用只完成一个 fresh 匿名评审,返回 `blind-judge-draft-v3`;adapter 再绑定 reviewer 身份、候选 hash、字符 offset、输入/报告 hash 和模型回执。每维 0-10 分、0.5 步长;双评委独立无会话,第二评委严格反序。报告在去盲前只保留随机候选 ID,禁止出现 A/B/C 或证据策略名称。 +正文回放与在线 `quality_gate`、细纲 `fine_outline_replay` 完全独立。当前策略版本为 `writer-replay-rubric-v2`:四个通用维度跨章节保持同义,另一个兼容维度按预注册场景类型加载独立问题、判据和五档分数锚点。每位 fresh 匿名评委最多调用三次:首轮或后续已返回草稿但被本地稳定合同拒绝时,可携带原草稿与错误原因纠错最多两次;任一轮是带可信成本回执的瞬时 `API_ERROR` 时,可不带 correction 重发同一盲评输入,但与格式纠错共用三次总调用额度。认证、预算、本地合同或成本未知错误不重试,第三稿仍不合约则失败关闭;逐字引文、完整覆盖和盲评隔离等硬门不因重试而放宽。模型输入同时给出精确候选/维度/断言/约束顺序、期望数组数量、完整评分策略元数据,以及从当前候选确定性切出的逐字引文提示;adapter 仍独立重算笛卡尔积并逐字校验。失败安全诊断只记录错误码、尝试/评委/调用次数、数组计数及缺失/重复/越界计数,完整草稿只进入 raw 归档。adapter 保留全部调用回执,只允许有可信成本的 API 失败回执出现在最终成功之前,并把最终合约报告绑定到对应终稿回执,再绑定 reviewer 身份、候选 hash、字符 offset、输入/报告 hash。每维 0-10 分、0.5 步长;双评委独立无会话,第二评委严格反序。报告在去盲前只保留随机候选 ID,禁止出现 A/B/C 或证据策略名称。 -| 维度 ID | 评分问题 | +| 层 | 维度 ID | 评分问题 | +|---|---|---| +| 通用 | `setting_entity_fidelity` | 设定、实体、关系、能力边界和冻结时点状态是否保真 | +| 通用 | `fine_outline_fidelity` | 细纲硬事件、结果方向、伏笔动作、出场实体和章末钩子是否忠实 | +| 通用 | `style_consistency` | 与冻结历史原文的人物声音、动作习惯、句式和叙事质感是否一致 | +| 通用 | `prose_readability` | 语言、指代、空间、动作和转场是否准确、自然、清晰 | +| 场景 | `narrative_tension` | 兼容 ID;必须按当前场景策略评分,不得按抽象通用“紧张度”评分 | + +| 场景类型 | 场景评分问题 | |---|---| -| `setting_entity_fidelity` | 设定、实体、关系、能力边界和冻结时点状态是否保真 | -| `fine_outline_fidelity` | 细纲硬事件、结果方向、伏笔动作、出场实体和章末钩子是否忠实 | -| `style_consistency` | 与冻结历史原文的人物声音、动作习惯、句式和叙事质感是否一致 | -| `narrative_tension` | 场景推进、冲突升级、不可逆变化和章末驱动力是否成立 | -| `prose_readability` | 语言是否准确、自然、清晰且没有明显阅读阻力 | +| `battle` | 战斗的空间、攻防因果、策略升级、力量边界和结果代价是否成立 | +| `character_dialogue` | 人物声音、对话目标、潜台词、关系推进和信息交换是否成立 | +| `turning_point` | 转折是否有前因、触发、不可逆变化、人物反应和后续驱动力 | +| `information_reveal` | 信息来源、知情边界、揭示时机、清晰度和揭示后的影响是否成立 | +| `returning_character` | 角色状态与声音是否连续,回归动机、他人反应和剧情作用是否成立 | + +通用维度可以跨章节聚合;场景维度只能在相同场景类型内聚合。Gate B 必须分别报告五类场景的 `C-A`,任一已有稳定样本的场景平均退化超过 `0.25` 即失败,禁止用其他场景的增益抵消。 每维必须给出具体 `reason`、在对应候选中唯一可定位的 `candidateQuote` 和受控 `evidenceRefs`。模型证据类型只能是 `candidate`、`fine_outline`、`oracle_assertion` 或 `judge_inference`,引用 ID 必须来自当前匿名输入;盲评输入不含卡,禁止评委声称证据来自 `card_index`。模型还必须给出每维匿名候选完整排序,并逐候选覆盖全部 oracle assertion 与 hard constraint verdict。双评同维分差大于 0.5 时启动且只启动一次 fresh 第三评委。第三评后,任一评分对差值不大于 0.5 则取三者中位数;否则整章为 `invalid_unstable`,不得强行计算胜负或进入 Gate 增益统计。 diff --git a/.claude/skills/quality-gate/scripts/fine_outline_rubric.py b/.claude/skills/score-content-quality/scripts/fine_outline_rubric.py similarity index 100% rename from .claude/skills/quality-gate/scripts/fine_outline_rubric.py rename to .claude/skills/score-content-quality/scripts/fine_outline_rubric.py diff --git a/.claude/skills/quality-gate/scripts/gate_input_builder.py b/.claude/skills/score-content-quality/scripts/gate_input_builder.py similarity index 82% rename from .claude/skills/quality-gate/scripts/gate_input_builder.py rename to .claude/skills/score-content-quality/scripts/gate_input_builder.py index 1e1e82b..8f2b449 100644 --- a/.claude/skills/quality-gate/scripts/gate_input_builder.py +++ b/.claude/skills/score-content-quality/scripts/gate_input_builder.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""从不可变逐层回执唯一构建 writer-gate-input-v2。""" +"""从不可变逐层回执唯一构建 writer-gate-input-v3。""" from __future__ import annotations @@ -8,9 +8,14 @@ import hashlib import json import math from collections.abc import Mapping, Sequence +from decimal import Decimal, InvalidOperation from typing import Any -from writer_rubric import DIMENSIONS, adjudicate_structured_reviews +from writer_rubric import ( + DIMENSIONS, + RUBRIC_POLICY_VERSION, + adjudicate_structured_reviews, +) class GateInputBuildError(ValueError): @@ -170,12 +175,7 @@ def _inner_execution_receipt_hashes( continue if raw_receipt.get("adapterRole") != role: failures.append("inner_receipt_role_mismatch") - for hash_field in ( - "executionProfileSha256", - "inputSha256", - "structuredOutputSha256", - "jsonSchemaSha256", - ): + for hash_field in ("executionProfileSha256", "inputSha256", "jsonSchemaSha256"): try: _hash(raw_receipt.get(hash_field), f"{receipt_field}.{hash_field}") except GateInputBuildError: @@ -185,21 +185,45 @@ def _inner_execution_receipt_hashes( actual_model = raw_receipt.get("actualModelId") if not isinstance(invocation_id, str) or not invocation_id: failures.append("inner_receipt_binding_invalid") - if ( - not isinstance(requested_model, str) - or not requested_model - or actual_model != requested_model - or raw_receipt.get("modelMatch") is not True - ): + try: + cost = Decimal(str(raw_receipt.get("totalCostUsd"))) + cost_known = cost.is_finite() and cost >= 0 + except (InvalidOperation, ValueError): + cost_known = False + transient_api_error = ( + role in {"semantic_detector", "blind_judge"} + and index < len(raw_receipts) - 1 + and raw_receipt.get("isError") is True + and raw_receipt.get("terminalReason") == "api_error" + and raw_receipt.get("exitCode") not in {None, 0} + and raw_receipt.get("structuredOutputSha256") is None + and cost_known + ) + if not isinstance(requested_model, str) or not requested_model: failures.append("inner_receipt_model_mismatch") - if ( - raw_receipt.get("isError") is not False - or raw_receipt.get("apiErrorStatus") is not None - or raw_receipt.get("exitCode") != 0 - or not isinstance(raw_receipt.get("terminalReason"), str) - or not raw_receipt.get("terminalReason") - ): - failures.append("inner_receipt_not_successful") + if transient_api_error: + # WHY: 只允许有可信成本、位于最终成功之前的 API 瞬时失败;它没有实际模型与 + # structured output,不能伪装成成功,也不能作为最后一笔回执进入 Gate。 + if actual_model is not None or raw_receipt.get("modelMatch") is not False: + failures.append("inner_receipt_model_mismatch") + else: + if actual_model != requested_model or raw_receipt.get("modelMatch") is not True: + failures.append("inner_receipt_model_mismatch") + try: + _hash( + raw_receipt.get("structuredOutputSha256"), + f"{receipt_field}.structuredOutputSha256", + ) + except GateInputBuildError: + failures.append("inner_receipt_binding_invalid") + if ( + raw_receipt.get("isError") is not False + or raw_receipt.get("apiErrorStatus") is not None + or raw_receipt.get("exitCode") != 0 + or not isinstance(raw_receipt.get("terminalReason"), str) + or not raw_receipt.get("terminalReason") + ): + failures.append("inner_receipt_not_successful") duration = raw_receipt.get("durationMs") if isinstance(duration, bool) or not isinstance(duration, int) or duration < 0: failures.append("inner_receipt_binding_invalid") @@ -257,14 +281,18 @@ def _receipt_success( ) bindings[arm][role] = inner_hashes failures.extend(f"{arm}_{role}_{failure}" for failure in inner_failures) - expected_count = 1 if role in {"writer", "semantic_detector"} else None - if expected_count is not None and len(inner_hashes) != expected_count: + # WHY: writer 篇幅修订与 detector 结构纠错都允许总计 1..3 次受预算约束的 + # 调用;完整回执链必须保留审计,最终候选/报告则绑定最后一次成功调用。 + if role == "writer" and not (1 <= len(inner_hashes) <= 4): + failures.append(f"{arm}_{role}_inner_receipt_count_invalid") + if role == "semantic_detector" and not (1 <= len(inner_hashes) <= 3): failures.append(f"{arm}_{role}_inner_receipt_count_invalid") if all("blind_judge" in bindings.get(arm, {}) for arm in ("A", "B", "C")): expected_panel = bindings["A"]["blind_judge"] if any(bindings[arm]["blind_judge"] != expected_panel for arm in ("B", "C")): failures.append("blind_judge_panel_receipt_binding_mismatch") - if len(expected_panel) not in {2, 3}: + # 每位 reviewer 最多两次格式纠错/API 重试,因此双评最少 2 次,触发第三评时最多 9 次。 + if not (2 <= len(expected_panel) <= 9): failures.append("blind_judge_panel_receipt_count_invalid") return not failures, list(dict.fromkeys(failures)), bindings @@ -276,8 +304,9 @@ def _verdict_map( candidate_hash: str, expected_ids: set[str], field: str, + allow_unknown: bool = False, ) -> dict[str, str]: - """校验 verdict 完整覆盖同类预期 ID,禁止 unknown、重复和跨候选。""" + """校验 verdict 完整覆盖同类预期 ID,并拒绝重复和跨候选。""" result: dict[str, str] = {} for index, item in enumerate(_array(values, field)): @@ -288,8 +317,10 @@ def _verdict_map( if item.get("candidateSha256") != candidate_hash: raise GateInputBuildError(f"{field} 候选绑定不一致") verdict = item.get("verdict") - if verdict not in {"pass", "fail"}: - raise GateInputBuildError(f"{field} verdict 必须是 pass/fail") + allowed = {"pass", "fail", "unknown"} if allow_unknown else {"pass", "fail"} + if verdict not in allowed: + expected = "pass/fail/unknown" if allow_unknown else "pass/fail" + raise GateInputBuildError(f"{field} verdict 必须是 {expected}") result[stable_id] = verdict if set(result) != expected_ids: raise GateInputBuildError(f"{field} ID 集缺失、重复或越界") @@ -322,6 +353,7 @@ def _detector_arm( candidate_hash=candidate_hash, expected_ids=assertion_ids, field=f"{field}.assertionVerdicts", + allow_unknown=True, ) constraints = _verdict_map( report.get("hardConstraintVerdicts"), @@ -329,6 +361,7 @@ def _detector_arm( candidate_hash=candidate_hash, expected_ids=constraint_ids, field=f"{field}.hardConstraintVerdicts", + allow_unknown=True, ) findings = _array(report.get("findings"), f"{field}.findings") high_count = sum( @@ -353,6 +386,9 @@ def _detector_arm( def _judge_sample( report: Mapping[str, Any], *, + sample_id: str, + scenario: str, + rubric_policy_version: str, candidate_hashes: Mapping[str, str], assertion_ids: set[str], constraint_ids: set[str], @@ -376,41 +412,89 @@ def _judge_sample( report.get("reviewerStructuredOutputs"), f"{field}.reviewerStructuredOutputs", ) - if len(reviewer_reports_raw) != len(execution_receipts) or len(reviewer_reports_raw) not in {2, 3}: - raise GateInputBuildError(f"{field} reviewer 报告数量与回执不一致") - if len(reviewer_structured_outputs_raw) != len(reviewer_reports_raw): - raise GateInputBuildError(f"{field} reviewer 原始输出数量与报告不一致") - reviewer_reports: list[dict[str, Any]] = [] - for index, (raw_report, raw_output, execution_receipt) in enumerate( - zip(reviewer_reports_raw, reviewer_structured_outputs_raw, execution_receipts, strict=True) + reviewer_final_indexes_raw = _array( + report.get("reviewerFinalReceiptIndexes"), + f"{field}.reviewerFinalReceiptIndexes", + ) + if len(reviewer_reports_raw) not in {2, 3}: + raise GateInputBuildError(f"{field} reviewer 报告数量非法") + if len(reviewer_structured_outputs_raw) != len(execution_receipts): + raise GateInputBuildError(f"{field} 全量 reviewer 原始输出数量与回执不一致") + if len(reviewer_final_indexes_raw) != len(reviewer_reports_raw): + raise GateInputBuildError(f"{field} reviewer 最终回执索引数量与报告不一致") + reviewer_final_indexes: list[int] = [] + for index, value in enumerate(reviewer_final_indexes_raw): + if ( + isinstance(value, bool) + or not isinstance(value, int) + or not 0 <= value < len(execution_receipts) + or (reviewer_final_indexes and value <= reviewer_final_indexes[-1]) + ): + raise GateInputBuildError( + f"{field}.reviewerFinalReceiptIndexes[{index}] 非法" + ) + reviewer_final_indexes.append(value) + + normalized_outputs: list[dict[str, Any] | None] = [] + normalized_receipts: list[Mapping[str, Any]] = [] + for index, (raw_output, execution_receipt) in enumerate( + zip(reviewer_structured_outputs_raw, execution_receipts, strict=True) ): - raw_report = _mapping( - raw_report, - f"{field}.reviewerReports[{index}]", - ) - raw_output = _mapping( - raw_output, - f"{field}.reviewerStructuredOutputs[{index}]", - ) execution_receipt = _mapping( execution_receipt, f"{field}.executionReceipts[{index}]", ) - if raw_report.get("schemaVersion") != "blind-judge-report-v3": + expected_output_hash = execution_receipt.get("structuredOutputSha256") + if expected_output_hash is None: + if raw_output is not None: + raise GateInputBuildError( + f"{field}.reviewerStructuredOutputs[{index}] API 失败位置必须是 null" + ) + normalized_outputs.append(None) + else: + raw_output = _mapping( + raw_output, + f"{field}.reviewerStructuredOutputs[{index}]", + ) + if canonical_sha256(raw_output) != expected_output_hash: + raise GateInputBuildError( + f"{field}.reviewerStructuredOutputs[{index}] 未绑定模型原始 structured output" + ) + normalized_outputs.append(copy.deepcopy(dict(raw_output))) + normalized_receipts.append(execution_receipt) + + reviewer_reports: list[dict[str, Any]] = [] + for index, raw_report in enumerate(reviewer_reports_raw): + raw_report = _mapping( + raw_report, + f"{field}.reviewerReports[{index}]", + ) + final_receipt_index = reviewer_final_indexes[index] + raw_output = normalized_outputs[final_receipt_index] + execution_receipt = normalized_receipts[final_receipt_index] + if raw_output is None: + raise GateInputBuildError( + f"{field}.reviewerReports[{index}] 最终索引指向 API 失败回执" + ) + if raw_report.get("schemaVersion") != "blind-judge-report-v4": raise GateInputBuildError( f"{field}.reviewerReports[{index}] 版本非法" ) + if ( + raw_report.get("sampleId") != sample_id + or raw_report.get("scenario") != scenario + or raw_report.get("rubricPolicyVersion") != rubric_policy_version + ): + raise GateInputBuildError( + f"{field}.reviewerReports[{index}] 未绑定当前样本场景评分策略" + ) _verify_self_hash(raw_report, "reportSha256", f"{field}.reviewerReports[{index}]") if raw_report.get("modelReceiptSha256") != canonical_sha256(execution_receipt): raise GateInputBuildError(f"{field}.reviewerReports[{index}] 未绑定执行回执") - # WHY: 只读交叉校验,builder 不重签。报告里携带的原始输出必须与该 reviewer 执行回执 - # 生产时固化的 structuredOutputSha256 一致,恢复「报告评分内容 == 模型真实输出」绑定: - # 攻击者即便一致地篡改报告评分、重签 reportSha256、按同一 adjudicate_structured_reviews - # 重推导 panel 让 recomputed_panel == report 通过,也无法让伪造评分对应的原始输出 - # 哈希回真实回执,从而在此失败关闭;reportSha256 把本字段纳入签名,攻击者换不掉它。 + # 全量原始输出已逐笔与回执核对;这里再复核最终索引确实指向该 reviewer 终稿。 if canonical_sha256(raw_output) != execution_receipt.get("structuredOutputSha256"): raise GateInputBuildError( - f"{field}.reviewerStructuredOutputs[{index}] 未绑定模型原始 structured output" + f"{field}.reviewerReports[{index}] 最终输出绑定非法" ) reviewer_reports.append(copy.deepcopy(dict(raw_report))) recomputed_panel = adjudicate_structured_reviews( @@ -528,7 +612,7 @@ def _confounders( class GateInputBuilder: """可信编排器内唯一允许生成 Gate 输入的构建器。""" - VERSION = "writer-gate-input-builder-v1" + VERSION = "writer-gate-input-builder-v2" def build( self, @@ -639,7 +723,7 @@ class GateInputBuilder: constraint_ids=constraint_ids, model_receipt_sha256=( receipt_bindings.get(arm, {}) - .get("semantic_detector", [None])[0] + .get("semantic_detector", [None])[-1] if receipt_bindings.get(arm, {}).get("semantic_detector") else "" ), @@ -651,6 +735,9 @@ class GateInputBuilder: raise GateInputBuildError(f"{sample_id} oracle assertion ID 不覆盖 comparable 集") judge = _judge_sample( _by_sample(judge_reports, sample_id, "judgeReports"), + sample_id=sample_id, + scenario=str(sample.get("scenario") or ""), + rubric_policy_version=RUBRIC_POLICY_VERSION, candidate_hashes=candidate_hashes, assertion_ids=assertion_ids, constraint_ids=constraint_ids, @@ -741,8 +828,9 @@ class GateInputBuilder: samples.append(sample_result) payload = { - "schemaVersion": "writer-gate-input-v2", + "schemaVersion": "writer-gate-input-v3", "builderVersion": self.VERSION, + "rubricPolicyVersion": RUBRIC_POLICY_VERSION, "gate": gate, "runId": run_id, "evaluationSetVersion": evaluation_set_version, @@ -773,10 +861,12 @@ def validate_gate_input(value: Mapping[str, Any], expected_sha256: str | None = """供 writer_gate 复核 builder 身份和双层规范哈希。""" gate_input = dict(_mapping(value, "gate-input.json")) - if gate_input.get("schemaVersion") != "writer-gate-input-v2": - raise GateInputBuildError("只接受 writer-gate-input-v2") + if gate_input.get("schemaVersion") != "writer-gate-input-v3": + raise GateInputBuildError("只接受 writer-gate-input-v3") if gate_input.get("builderVersion") != GateInputBuilder.VERSION: raise GateInputBuildError("Gate 输入不是可信 builder 产物") + if gate_input.get("rubricPolicyVersion") != RUBRIC_POLICY_VERSION: + raise GateInputBuildError("Gate 输入未绑定当前正文评分策略") supplied_input_hash = _hash(gate_input.get("gateInputSha256"), "gateInputSha256") expected_input_hash = canonical_sha256( {key: item for key, item in gate_input.items() if key != "gateInputSha256"} diff --git a/.claude/skills/score-content-quality/scripts/lesson_registry.py b/.claude/skills/score-content-quality/scripts/lesson_registry.py new file mode 100644 index 0000000..0a9a2f7 --- /dev/null +++ b/.claude/skills/score-content-quality/scripts/lesson_registry.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""经验升格链登记(质量与复利 06)—— 教训/成功先留证,升格必须人批准。 + +合同要点: +- 运行中的 lesson(教训)与 win(成功经验)登记为证据,绑定 run_id 与候选哈希; +- 状态机 proposed → reviewing → promoted/rejected;DB 触发器拒绝跳过评审的自动升格, + promoted 必须指明目标类型(pattern/skill/tool)与落点; +- 本模块只做登记与状态流转(每步短事务、条件 UPDATE、失败关闭),**绝不自动升格**, + 也不把单章 n=1 结果当成普适结论。 +""" + +from __future__ import annotations + +import json +import pathlib +import sys +from typing import Any, Mapping + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +DB_DIR = SCRIPT_DIR.parents[1] / "access-database" / "scripts" +if str(DB_DIR) not in sys.path: + sys.path.insert(0, str(DB_DIR)) + +from db import connect # noqa: E402 + +KINDS = frozenset({"lesson", "win"}) +STATUSES = frozenset({"proposed", "reviewing", "promoted", "rejected"}) +TARGET_TYPES = frozenset({"pattern", "skill", "tool"}) +CREATOR = "quality" + + +class LessonError(RuntimeError): + """经验登记或流转非法——失败关闭。""" + + +def _bare_sha(value: str | None) -> str | None: + if not value: + return None + value = str(value) + return value[len("sha256:"):] if value.startswith("sha256:") else value + + +def propose_lesson( + *, + kind: str, + title: str, + detail: Mapping[str, Any], + work_id: int | None = None, + run_id: str | None = None, + candidate_sha256: str | None = None, + creator: str = CREATOR, +) -> dict[str, Any]: + """登记一条经验证据(status=proposed);不做任何升格判断。""" + + if kind not in KINDS: + raise LessonError(f"kind 必须属于 {sorted(KINDS)}") + if not isinstance(title, str) or not title.strip() or len(title) > 200: + raise LessonError("title 必须是非空且不超过 200 字符") + if not isinstance(detail, Mapping) or not detail: + raise LessonError("detail 必须是非空对象(情境/行动/结果/证据指针)") + bare_sha = _bare_sha(candidate_sha256) + with connect() as conn: + try: + row = conn.execute( + "INSERT INTO example_lesson(work_id, run_id, candidate_sha256, kind, title, " + "detail, status, creator) VALUES (%s,%s,%s,%s,%s,%s::jsonb,'proposed',%s) " + "RETURNING id", + (work_id, run_id, bare_sha, kind, title.strip(), + json.dumps(dict(detail), ensure_ascii=False), creator), + ).fetchone() + conn.commit() + except Exception: + conn.rollback() + raise + return {"status": "proposed", "lesson_id": row[0]} + + +def _transition(lesson_id: int, target_status: str, *, decided_by: str, rationale: str | None, + target_type: str | None, target_ref: str | None, expected_from: tuple) -> dict[str, Any]: + """条件 UPDATE 流转状态;decided_by 必填,来源状态不对即失败关闭。""" + + if not isinstance(decided_by, str) or not decided_by.strip(): + raise LessonError("decided_by 必填:升格/驳回必须可追责到人") + from_clause = ",".join(f"'{state}'" for state in expected_from) + params = [rationale, decided_by] + target_cols = "" + if target_status == "promoted": + if target_type not in TARGET_TYPES: + raise LessonError(f"promoted 必须指明 promotion_target_type ∈ {sorted(TARGET_TYPES)}") + if not isinstance(target_ref, str) or not target_ref.strip() or len(target_ref) > 120: + raise LessonError("promoted 必须指明 promotion_target_ref(升格落点)") + target_cols = ", promotion_target_type=%s, promotion_target_ref=%s" + params.extend([target_type, target_ref.strip()]) + params.extend([decided_by, lesson_id]) + with connect() as conn: + try: + row = conn.execute( + f"UPDATE example_lesson SET status='{target_status}', decision_rationale=%s, " + f"decided_by=%s{target_cols}, updater=%s " + f"WHERE id=%s AND status IN ({from_clause}) AND deleted=false RETURNING status", + params, + ).fetchone() + if row is None: + raise LessonError( + f"经验 {lesson_id} 当前状态不允许流转到 {target_status}(期望来源 {list(expected_from)})") + conn.commit() + except Exception: + conn.rollback() + raise + return {"status": target_status, "lesson_id": lesson_id} + + +def start_review(lesson_id: int, *, decided_by: str, rationale: str | None = None) -> dict[str, Any]: + """proposed → reviewing:进入人工评审。""" + + return _transition(lesson_id, "reviewing", decided_by=decided_by, rationale=rationale, + target_type=None, target_ref=None, expected_from=("proposed",)) + + +def promote(lesson_id: int, *, decided_by: str, target_type: str, target_ref: str, + rationale: str | None = None) -> dict[str, Any]: + """reviewing → promoted:人工批准后升格到范式/技能/工具。""" + + return _transition(lesson_id, "promoted", decided_by=decided_by, rationale=rationale, + target_type=target_type, target_ref=target_ref, expected_from=("reviewing",)) + + +def reject(lesson_id: int, *, decided_by: str, rationale: str | None = None) -> dict[str, Any]: + """proposed/reviewing → rejected:驳回(终态)。""" + + return _transition(lesson_id, "rejected", decided_by=decided_by, rationale=rationale, + target_type=None, target_ref=None, expected_from=("proposed", "reviewing")) + + +def list_lessons(*, work_id: int | None = None, status: str | None = None, + kind: str | None = None) -> list[dict[str, Any]]: + """只读查询经验登记(看板/评审用)。""" + + if status is not None and status not in STATUSES: + raise LessonError(f"status 必须属于 {sorted(STATUSES)}") + if kind is not None and kind not in KINDS: + raise LessonError(f"kind 必须属于 {sorted(KINDS)}") + clauses = ["deleted=false"] + params: list[Any] = [] + if work_id is not None: + clauses.append("work_id=%s") + params.append(work_id) + if status is not None: + clauses.append("status=%s") + params.append(status) + if kind is not None: + clauses.append("kind=%s") + params.append(kind) + with connect(readonly=True) as conn: + rows = conn.execute( + "SELECT id, work_id, run_id, candidate_sha256, kind, title, status, " + "promotion_target_type, promotion_target_ref, decided_by " + f"FROM example_lesson WHERE {' AND '.join(clauses)} ORDER BY id", + params, + ).fetchall() + return [ + {"id": r[0], "work_id": r[1], "run_id": r[2], "candidate_sha256": r[3], "kind": r[4], + "title": r[5], "status": r[6], "promotion_target_type": r[7], + "promotion_target_ref": r[8], "decided_by": r[9]} + for r in rows + ] + + +__all__ = [ + "KINDS", "STATUSES", "TARGET_TYPES", "LessonError", + "propose_lesson", "start_review", "promote", "reject", "list_lessons", +] diff --git a/.claude/skills/quality-gate/scripts/run_writer_blind_judge.py b/.claude/skills/score-content-quality/scripts/run_writer_blind_judge.py similarity index 71% rename from .claude/skills/quality-gate/scripts/run_writer_blind_judge.py rename to .claude/skills/score-content-quality/scripts/run_writer_blind_judge.py index 3ffb4b5..ad1eb6b 100644 --- a/.claude/skills/quality-gate/scripts/run_writer_blind_judge.py +++ b/.claude/skills/score-content-quality/scripts/run_writer_blind_judge.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""blind judge v3 的匿名模型适配器与独立评审编排。""" +"""blind judge v4 输入/报告绑定与 v3 模型输出适配、独立评审编排。""" from __future__ import annotations @@ -11,12 +11,18 @@ import sys from typing import Any, Mapping, Protocol, Sequence, runtime_checkable SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -for _import_dir in (SCRIPT_DIR, RUNTIME_DIR): +EXECUTION_DIR = SCRIPT_DIR.parents[1] / "execute-claude-task" / "scripts" +for _import_dir in (SCRIPT_DIR, EXECUTION_DIR): if str(_import_dir) not in sys.path: sys.path.insert(0, str(_import_dir)) -from writer_rubric import DIMENSIONS, adjudicate_structured_reviews # noqa: E402 +from writer_rubric import ( # noqa: E402 + DIMENSIONS, + RUBRIC_POLICY_VERSION, + SCENARIO_TYPES, + adjudicate_structured_reviews, + rubric_for_scenario, +) try: from claude_runtime import ExecutionProfile, contains_path_traversal, run_claude, sha256_json # type: ignore[import-not-found] # noqa: E402 @@ -27,9 +33,9 @@ except ImportError: sha256_json = None # type: ignore[assignment] -INPUT_VERSION = "writer-blind-input-v3" +INPUT_VERSION = "writer-blind-input-v4" MODEL_OUTPUT_VERSION = "blind-judge-draft-v3" -REPORT_VERSION = "blind-judge-report-v3" +REPORT_VERSION = "blind-judge-report-v4" ORACLE_VERSION = "oracle-truth-pack-v1" HASH_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$") REAL_ARM_NAMES = frozenset({"A", "B", "C"}) @@ -37,10 +43,11 @@ VERDICTS = frozenset({"pass", "fail"}) INPUT_FIELDS = frozenset({ "schemaVersion", "runId", "sampleId", "reviewerInvocationId", "candidateOrder", "candidates", "fineOutline", "oracleTruthPack", "oracleTruthPackSha256", - "authorizationSnapshotId", "blindInputSha256", + "authorizationSnapshotId", "scenario", "rubricPolicyVersion", "blindInputSha256", }) REPORT_FIELDS = frozenset({ - "schemaVersion", "runId", "sampleId", "reviewerInvocationId", "blindInputSha256", + "schemaVersion", "runId", "sampleId", "scenario", "rubricPolicyVersion", + "reviewerInvocationId", "blindInputSha256", "oracleTruthPackSha256", "candidateOrder", "candidateScores", "dimensionPreferences", "oracleAssertionVerdicts", "hardConstraintVerdicts", "modelReceiptSha256", "status", "reportSha256", @@ -156,6 +163,11 @@ def validate_blind_judge_input(value: Any) -> dict[str, Any]: raise BlindJudgeContractError(code, "输入版本非法") for field in ("runId", "sampleId", "reviewerInvocationId", "authorizationSnapshotId"): _string(payload[field], f"$.{field}", code=code) + scenario = _string(payload["scenario"], "$.scenario", code=code) + if scenario not in SCENARIO_TYPES: + raise BlindJudgeContractError(code, f"$.scenario 未登记: {scenario}") + if payload["rubricPolicyVersion"] != RUBRIC_POLICY_VERSION: + raise BlindJudgeContractError(code, "rubricPolicyVersion 与当前正文评分策略不一致") order = [_string(item, f"$.candidateOrder[{index}]", code=code) for index, item in enumerate(_array(payload["candidateOrder"], "$.candidateOrder", code=code))] if len(order) < 2 or len(order) != len(set(order)): raise BlindJudgeContractError(code, "candidateOrder 必须包含至少两个无重复匿名 ID") @@ -237,6 +249,13 @@ def build_blind_judge_model_input(value: Any) -> dict[str, Any]: payload = validate_blind_judge_input(value) outline = payload["fineOutline"] pack = payload["oracleTruthPack"] + candidate_order = list(payload["candidateOrder"]) + assertion_ids = [ + item["assertionId"] + for field in ("historicalAssertions", "targetAssertions") + for item in pack[field] + ] + constraint_ids = [item["constraintId"] for item in outline["hardConstraints"]] return { "candidates": [{"blindCandidateId": item["blindCandidateId"], "candidateBody": item["candidateBody"]} for item in payload["candidates"]], "fineOutline": { @@ -248,7 +267,28 @@ def build_blind_judge_model_input(value: Any) -> dict[str, Any]: "historicalAssertions": [{"assertionId": item["assertionId"], "text": item["text"]} for item in pack["historicalAssertions"]], "targetAssertions": [{"assertionId": item["assertionId"], "text": item["text"]} for item in pack["targetAssertions"]], }, - "rubric": {"dimensions": list(DIMENSIONS), "scoreRange": [0, 10], "scoreStep": 0.5}, + "rubric": rubric_for_scenario(payload["scenario"]), + # WHY: JSON Schema 只能约束单项形状,无法表达随当前样本变化的候选×断言完整集。 + # 把精确 ID、顺序和计数直接交给模型,避免模型凭空猜数组长度;适配器仍会独立 + # 重算并校验完整笛卡尔积,不把这份提示当作可信证据。 + "outputContract": { + "candidateOrder": candidate_order, + "dimensionOrder": list(DIMENSIONS), + "oracleAssertionIds": assertion_ids, + "hardConstraintIds": constraint_ids, + "expectedCounts": { + "candidateScores": len(candidate_order), + "dimensionPreferences": len(DIMENSIONS), + "oracleAssertionVerdicts": len(candidate_order) * len(assertion_ids), + "hardConstraintVerdicts": len(candidate_order) * len(constraint_ids), + }, + "verdictOrder": "candidate-major,先 candidateOrder,再按对应 ID 数组顺序", + "judgeInferenceSourceId": "评分用当前 dimension;断言/硬约束裁决用当前 assertionId/constraintId", + "instruction": "必须一次返回全部对象,不得省略、合并、增加或只返回修改片段", + }, + # 候选全文已在同一输入中;这里额外给出确定性逐字片段,降低长文复制时产生错字、 + # 拼接或标点漂移的概率。最终引文仍由适配器对候选正文逐字校验。 + "candidateQuoteHints": _valid_quote_hints(payload["_candidateBodies"]), } @@ -265,6 +305,106 @@ def _quote_location(body: str, quote: Any, path: str) -> tuple[str, int, int]: return text, start, start + len(text) +def _valid_quote_hints(candidate_bodies: Mapping[str, str]) -> dict[str, list[str]]: + """从候选正文切出少量可直接复制的合法引文,供纠错轮定位证据。""" + + hints: dict[str, list[str]] = {} + for blind_id, body in candidate_bodies.items(): + if not body: + hints[blind_id] = [] + continue + starts = (0, max(len(body) // 2 - 10, 0), max(len(body) - 20, 0)) + values: list[str] = [] + for start in starts: + excerpt = body[start : start + 20] + if excerpt and excerpt not in values: + values.append(excerpt) + hints[blind_id] = values + return hints + + +def _safe_draft_diagnostic( + draft: Any, + payload: Mapping[str, Any], + error: BlindJudgeContractError, +) -> dict[str, Any]: + """只记录结构、计数和稳定错误路径,不把理由、引文或正文带出 raw vault。""" + + expected_assertions = set(payload["_expectedAssertionIds"]) + expected_constraints = set(payload["_expectedConstraintIds"]) + expected_candidates = set(payload["candidateOrder"]) + + def array_count(field: str) -> int | None: + value = draft.get(field) if isinstance(draft, Mapping) else None + return len(value) if isinstance(value, list) else None + + def coverage(field: str, id_field: str, expected_ids: set[str]) -> dict[str, int | None]: + values = draft.get(field) if isinstance(draft, Mapping) else None + if not isinstance(values, list): + return { + "actualCount": None, + "expectedPairCount": len(expected_candidates) * len(expected_ids), + "duplicatePairCount": None, + "outOfSetPairCount": None, + "missingPairCount": None, + } + pairs: list[tuple[str, str]] = [] + out_of_set = 0 + for item in values: + if not isinstance(item, Mapping): + out_of_set += 1 + continue + pair = (str(item.get("blindCandidateId") or ""), str(item.get(id_field) or "")) + pairs.append(pair) + if pair[0] not in expected_candidates or pair[1] not in expected_ids: + out_of_set += 1 + expected_pairs = { + (candidate_id, stable_id) + for candidate_id in expected_candidates + for stable_id in expected_ids + } + return { + "actualCount": len(values), + "expectedPairCount": len(expected_pairs), + "duplicatePairCount": len(pairs) - len(set(pairs)), + "outOfSetPairCount": out_of_set, + "missingPairCount": len(expected_pairs - set(pairs)), + } + + expected_top = { + "schemaVersion", + "candidateScores", + "dimensionPreferences", + "oracleAssertionVerdicts", + "hardConstraintVerdicts", + } + top_fields = set(draft) if isinstance(draft, Mapping) else set() + return { + "schemaVersion": "blind-judge-safe-diagnostic-v1", + "errorCode": error.code, + "errorMessage": str(error), + "draftAvailable": isinstance(draft, Mapping), + "topLevelFieldCount": len(top_fields) if isinstance(draft, Mapping) else None, + "missingTopLevelFieldCount": len(expected_top - top_fields), + "extraTopLevelFieldCount": len(top_fields - expected_top), + "arrayCounts": { + field: array_count(field) + for field in ( + "candidateScores", + "dimensionPreferences", + "oracleAssertionVerdicts", + "hardConstraintVerdicts", + ) + }, + "oracleCoverage": coverage( + "oracleAssertionVerdicts", "assertionId", expected_assertions + ), + "hardConstraintCoverage": coverage( + "hardConstraintVerdicts", "constraintId", expected_constraints + ), + } + + def _model_evidence_refs( value: Any, path: str, @@ -322,6 +462,8 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, for index, raw in enumerate(_array(draft["candidateScores"], "$.candidateScores", code=code)): item = _object(raw, f"$.candidateScores[{index}]", frozenset({"blindCandidateId", "scores"}), code=code) blind_id = _string(item["blindCandidateId"], f"$.candidateScores[{index}].blindCandidateId", code=code) + if blind_id not in bodies: + raise BlindJudgeContractError(code, f"$.candidateScores[{index}] 候选 ID 越界") score_ids.append(blind_id) scores = _object(item["scores"], f"$.candidateScores[{index}].scores", frozenset(DIMENSIONS), code=code) bound_scores: dict[str, Any] = {} @@ -345,8 +487,14 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, ), } candidate_scores.append({"blindCandidateId": blind_id, "candidateSha256": hashes[blind_id], "scores": bound_scores}) - if score_ids != order: - raise BlindJudgeContractError(code, "candidateScores 必须与匿名候选顺序一致") + if ( + len(score_ids) != len(order) + or len(score_ids) != len(set(score_ids)) + or set(score_ids) != set(order) + ): + raise BlindJudgeContractError(code, "candidateScores 未覆盖完整匿名候选集") + score_by_id = {item["blindCandidateId"]: item for item in candidate_scores} + candidate_scores = [score_by_id[blind_id] for blind_id in order] preferences: list[dict[str, Any]] = [] preference_dimensions: list[str] = [] for index, raw in enumerate(_array(draft["dimensionPreferences"], "$.dimensionPreferences", code=code)): @@ -357,8 +505,15 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, if set(ranked) != set(order) or len(ranked) != len(order): raise BlindJudgeContractError(code, "dimensionPreferences 必须对全部匿名候选排序") preferences.append({"dimension": dimension, "orderedCandidateIds": ranked, "reason": _string(item["reason"], f"$.dimensionPreferences[{index}].reason", code=code)}) - if preference_dimensions != list(DIMENSIONS): - raise BlindJudgeContractError(code, "dimensionPreferences 必须按 rubric 维度完整返回") + expected_dimensions = list(DIMENSIONS) + if ( + len(preference_dimensions) != len(expected_dimensions) + or len(preference_dimensions) != len(set(preference_dimensions)) + or set(preference_dimensions) != set(expected_dimensions) + ): + raise BlindJudgeContractError(code, "dimensionPreferences 未覆盖完整 rubric 维度") + preference_by_dimension = {item["dimension"]: item for item in preferences} + preferences = [preference_by_dimension[dimension] for dimension in expected_dimensions] def verdicts(field: str, id_field: str, expected_ids: list[str]) -> list[dict[str, Any]]: result: list[dict[str, Any]] = [] @@ -367,6 +522,8 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, item = _object(raw, f"$.{field}[{index}]", frozenset({id_field, "blindCandidateId", "verdict", "reason", "candidateQuote", "evidenceRefs"}), code=code) stable_id = _string(item[id_field], f"$.{field}[{index}].{id_field}", code=code) blind_id = _string(item["blindCandidateId"], f"$.{field}[{index}].blindCandidateId", code=code) + if blind_id not in bodies: + raise BlindJudgeContractError(code, f"$.{field}[{index}] 候选 ID 越界") keys.append((blind_id, stable_id)) if item["verdict"] not in VERDICTS: raise BlindJudgeContractError(code, f"$.{field}[{index}].verdict 非法") @@ -376,7 +533,9 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, item["evidenceRefs"], f"$.{field}[{index}].evidenceRefs", blind_id=blind_id, - dimension=id_field, + # verdict 的 judge_inference 必须绑定当前稳定断言/约束 ID,而不是字段名 + # "assertionId"/"constraintId";后者不在输入证据命名空间中。 + dimension=stable_id, quote=quote, start=start, end=end, @@ -385,14 +544,20 @@ def _validate_model_output(value: Any, payload: Mapping[str, Any]) -> dict[str, constraint_ids=set(payload["_expectedConstraintIds"]), ) result.append({ - id_field: stable_id, "candidateSha256": hashes[blind_id], "verdict": item["verdict"], + id_field: stable_id, + "candidateSha256": hashes[blind_id], "verdict": item["verdict"], "reason": reason, "evidenceRefs": [{"sourceType": entry["sourceType"], "sourceRef": entry["sourceRef"]} for entry in evidence], }) expected_keys = [(blind_id, stable_id) for blind_id in order for stable_id in expected_ids] - if keys != expected_keys: - raise BlindJudgeContractError(code, f"$.{field} 必须覆盖候选与标准 ID 的完整笛卡尔积") - return result + if ( + len(keys) != len(expected_keys) + or len(keys) != len(set(keys)) + or set(keys) != set(expected_keys) + ): + raise BlindJudgeContractError(code, f"$.{field} 未覆盖候选与标准 ID 的完整笛卡尔积") + result_by_key = {key: item for key, item in zip(keys, result)} + return [result_by_key[key] for key in expected_keys] return { "candidateScores": candidate_scores, @@ -408,6 +573,7 @@ def build_blind_judge_report(value: Any, blind_input_value: Mapping[str, Any], * content = _validate_model_output(value, payload) report = { "schemaVersion": REPORT_VERSION, "runId": payload["runId"], "sampleId": payload["sampleId"], + "scenario": payload["scenario"], "rubricPolicyVersion": payload["rubricPolicyVersion"], "reviewerInvocationId": payload["reviewerInvocationId"], "blindInputSha256": payload["blindInputSha256"], "oracleTruthPackSha256": payload["oracleTruthPackSha256"], "candidateOrder": payload["candidateOrder"], **content, "modelReceiptSha256": receipt_hash, "status": "completed", @@ -424,6 +590,7 @@ def validate_blind_judge_report(value: Any, blind_input_value: Mapping[str, Any] raise BlindJudgeContractError(code, "报告版本非法") expected = { "runId": payload["runId"], "sampleId": payload["sampleId"], "reviewerInvocationId": payload["reviewerInvocationId"], + "scenario": payload["scenario"], "rubricPolicyVersion": payload["rubricPolicyVersion"], "blindInputSha256": payload["blindInputSha256"], "oracleTruthPackSha256": payload["oracleTruthPackSha256"], "candidateOrder": payload["candidateOrder"], } @@ -524,24 +691,116 @@ def _failure(error: BlindJudgeContractError, *, status: str = "failed", primary_ return result -def run_writer_blind_judge(blind_input_value: Mapping[str, Any], *, model_runner: ModelRunner) -> dict[str, Any]: +def run_writer_blind_judge( + blind_input_value: Mapping[str, Any], + *, + model_runner: ModelRunner, + max_corrections: int = 1, + max_runtime_retries: int = 1, +) -> dict[str, Any]: + """执行一位 fresh 评委;仅对已返回但不合约的草稿做一次有界纠错。""" + + attempt_count = 0 + runtime_retry_count = 0 + last_diagnostic: dict[str, Any] | None = None try: + if ( + isinstance(max_corrections, bool) + or not isinstance(max_corrections, int) + or not 0 <= max_corrections <= 2 + ): + raise BlindJudgeContractError( + "BLIND_JUDGE_RUNNER_INVALID", "max_corrections 必须是 0、1 或 2" + ) + if ( + isinstance(max_runtime_retries, bool) + or not isinstance(max_runtime_retries, int) + or not 0 <= max_runtime_retries <= 1 + ): + raise BlindJudgeContractError( + "BLIND_JUDGE_RUNNER_INVALID", "max_runtime_retries 必须是 0 或 1" + ) payload = validate_blind_judge_input(blind_input_value) public = {key: value for key, value in payload.items() if not key.startswith("_")} - try: - draft, receipt_hash = _invoke(model_runner, build_blind_judge_model_input(public)) - except BlindJudgeContractError: - raise - except Exception as exc: - code = getattr(exc, "primary_code", None) or getattr(exc, "code", None) - if isinstance(code, str) and code.startswith("BLIND_JUDGE_"): - raise BlindJudgeContractError(code, "模型运行底座失败", causes=getattr(exc, "causes", ())) from exc - raise BlindJudgeContractError("BLIND_JUDGE_RUNTIME_FAILED", f"模型运行失败: {type(exc).__name__}") from exc - report = build_blind_judge_report(draft, public, model_receipt_sha256=receipt_hash) - report = validate_blind_judge_report(report, public, model_receipt_sha256=receipt_hash) - return {"ok": True, "acceptanceEligible": False, "status": "completed", "report": report} + base_model_input = build_blind_judge_model_input(public) + correction: dict[str, Any] | None = None + for attempt in range(max_corrections + 1): + attempt_count = attempt + 1 + model_input = ( + base_model_input + if correction is None + else {**base_model_input, "correction": correction} + ) + draft: Any = None + try: + try: + draft, receipt_hash = _invoke(model_runner, model_input) + except BlindJudgeContractError: + raise + except Exception as exc: + code = getattr(exc, "primary_code", None) or getattr(exc, "code", None) + if isinstance(code, str) and code.startswith("BLIND_JUDGE_"): + raise BlindJudgeContractError( + code, + "模型运行底座失败", + causes=getattr(exc, "causes", ()), + ) from exc + raise BlindJudgeContractError( + "BLIND_JUDGE_RUNTIME_FAILED", + f"模型运行失败: {type(exc).__name__}", + ) from exc + report = build_blind_judge_report( + draft, public, model_receipt_sha256=receipt_hash + ) + report = validate_blind_judge_report( + report, public, model_receipt_sha256=receipt_hash + ) + return { + "ok": True, + "acceptanceEligible": False, + "status": "completed", + "report": report, + "attemptCount": attempt_count, + } + except BlindJudgeContractError as error: + last_diagnostic = _safe_draft_diagnostic(draft, payload, error) + # WHY: 只有模型已返回草稿时,回喂原稿和稳定合同错误才可能修正引文、 + # ID 顺序等格式问题。API/运行器失败没有草稿,继续调用会掩盖原始故障; + # 一次纠错仍不合约则保持失败关闭。 + if draft is None: + # WHY: API 瞬时错误可重发同一盲评输入一次,但与格式纠错共用两次总调用 + # 上限;认证、预算、本地合同等错误不重试。 + if ( + error.code == "BLIND_JUDGE_API_ERROR" + and runtime_retry_count < max_runtime_retries + and attempt < max_corrections + ): + runtime_retry_count += 1 + correction = None + continue + raise + if attempt >= max_corrections: + raise + correction = { + "previousDraft": draft, + "error": str(error), + "errorCode": error.code, + "safeDraftDiagnostic": last_diagnostic, + } + if error.code == "BLIND_JUDGE_QUOTE_NOT_FOUND": + # WHY: 引文失败时只提供从当前候选正文确定性切出的合法片段;适配器仍会 + # 对模型最终引文做精确命中校验,提示不会替代证据绑定或放宽失败关闭。 + correction["validCandidateQuoteHints"] = _valid_quote_hints( + payload["_candidateBodies"] + ) + raise BlindJudgeContractError( + "BLIND_JUDGE_RUNTIME_FAILED", "评委纠错环意外退出" + ) except BlindJudgeContractError as error: - return _failure(error) + result = {**_failure(error), "attemptCount": attempt_count} + if last_diagnostic is not None: + result["safeDiagnostic"] = last_diagnostic + return result def _pair_inputs(first_value: Mapping[str, Any], second_value: Mapping[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]: @@ -549,7 +808,14 @@ def _pair_inputs(first_value: Mapping[str, Any], second_value: Mapping[str, Any] second = validate_blind_judge_input(second_value) if first["reviewerInvocationId"] == second["reviewerInvocationId"]: raise BlindJudgeContractError("JUDGE_INVALID", "双评必须使用独立 reviewerInvocationId") - for field in ("runId", "sampleId", "oracleTruthPackSha256", "authorizationSnapshotId"): + for field in ( + "runId", + "sampleId", + "scenario", + "rubricPolicyVersion", + "oracleTruthPackSha256", + "authorizationSnapshotId", + ): if first[field] != second[field]: raise BlindJudgeContractError("JUDGE_INVALID", f"双评 {field} 不一致") if first["_candidateHashes"] != second["_candidateHashes"] or second["candidateOrder"] != list(reversed(first["candidateOrder"])): @@ -561,10 +827,18 @@ def run_writer_blind_judge_panel(first_input: Mapping[str, Any], second_input: M try: first, second = _pair_inputs(first_input, second_input) except BlindJudgeContractError as error: - return _failure(error, status="failed_judge_invalid", primary_code="JUDGE_INVALID", review_count=0) + # WHY: 单份输入自身的稳定合同错误必须原样保留,才能区分哈希、泄漏、字段等故障; + # _pair_inputs 只有真正的双评关系不一致才会主动抛 JUDGE_INVALID。 + return _failure(error, status="failed_judge_invalid", primary_code=error.code, review_count=0) reports: list[dict[str, Any]] = [] for review_count, payload in enumerate((first, second), start=1): - result = run_writer_blind_judge({key: value for key, value in payload.items() if not key.startswith("_")}, model_runner=model_runner) + # WHY: Gate A 合同给每位评委三次总调用(首轮 + 两次纠错/API 重试槽);这里显式 + # 传参,避免默认值变化或遗漏导致首位评委在两次调用后提前关闭整组盲评。 + result = run_writer_blind_judge( + {key: value for key, value in payload.items() if not key.startswith("_")}, + model_runner=model_runner, + max_corrections=2, + ) if not result["ok"]: return {**result, "reviewCount": review_count} reports.append(result["report"]) @@ -577,11 +851,28 @@ def run_writer_blind_judge_panel(first_input: Mapping[str, Any], second_input: M return _failure(BlindJudgeContractError("JUDGE_UNSTABLE", "双评不稳定且缺少第三评"), status="failed_judge_unstable", primary_code="JUDGE_UNSTABLE", review_count=2) try: third = validate_blind_judge_input(third_input) - if third["reviewerInvocationId"] in {first["reviewerInvocationId"], second["reviewerInvocationId"]} or third["_candidateHashes"] != first["_candidateHashes"]: + shared_fields = ( + "runId", + "sampleId", + "scenario", + "rubricPolicyVersion", + "oracleTruthPackSha256", + "authorizationSnapshotId", + ) + if ( + third["reviewerInvocationId"] + in {first["reviewerInvocationId"], second["reviewerInvocationId"]} + or third["_candidateHashes"] != first["_candidateHashes"] + or any(third[field] != first[field] for field in shared_fields) + ): raise BlindJudgeContractError("JUDGE_INVALID", "第三评身份或候选集合非法") except BlindJudgeContractError as error: return _failure(error, status="failed_judge_invalid", primary_code="JUDGE_INVALID", review_count=2) - third_result = run_writer_blind_judge({key: value for key, value in third.items() if not key.startswith("_")}, model_runner=model_runner) + third_result = run_writer_blind_judge( + {key: value for key, value in third.items() if not key.startswith("_")}, + model_runner=model_runner, + max_corrections=2, + ) if not third_result["ok"]: return {**third_result, "reviewCount": 3} reports.append(third_result["report"]) diff --git a/.claude/skills/score-content-quality/scripts/test_lesson_registry_db.py b/.claude/skills/score-content-quality/scripts/test_lesson_registry_db.py new file mode 100644 index 0000000..e13731f --- /dev/null +++ b/.claude/skills/score-content-quality/scripts/test_lesson_registry_db.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""经验升格链对 muse-example 真实库的集成测试。 + +覆盖: +- 登记校验:kind/title/detail 闭集与形状; +- 禁止自动升格:proposed 不得跳过评审直接 promoted(应用层 + DB 触发器双保险); +- 人工升格闭环:reviewing → promoted 必须指明目标类型与落点; +- 终态不可再流转;驳回可从 proposed/reviewing 发起。 + +测试行用 unittest-lesson- 前缀的 run_id 隔离,结束物理清理(本表可变)。 + +跑法(需 Tailscale 内网可达 muse-example): +.venv/bin/python .claude/skills/score-content-quality/scripts/test_lesson_registry_db.py +""" + +from __future__ import annotations + +import pathlib +import sys +import uuid + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from db import connect # noqa: E402 +import lesson_registry # noqa: E402 +from lesson_registry import ( # noqa: E402 + LessonError, list_lessons, promote, propose_lesson, reject, start_review, +) + +SUFFIX = uuid.uuid4().hex[:10] +RUN_PREFIX = f"unittest-lesson-{SUFFIX}" +_seq = {"n": 0} + + +def _run_id() -> str: + _seq["n"] += 1 + return f"{RUN_PREFIX}-{_seq['n']}" + + +def _cleanup() -> None: + with connect() as conn: + conn.execute("DELETE FROM example_lesson WHERE run_id LIKE %s", (f"{RUN_PREFIX}%",)) + conn.commit() + + +def _propose(**overrides): + kwargs = dict(kind="lesson", title=f"测试经验 {SUFFIX}", + detail={"situation": "机械门通过但语义未跑", "action": "补语义兜底", + "outcome": "接受通道失败关闭"}, run_id=_run_id()) + kwargs.update(overrides) + return propose_lesson(**kwargs) + + +def test_propose_validates_inputs() -> None: + out = _propose() + assert out["status"] == "proposed" and out["lesson_id"] >= 1 + win = _propose(kind="win", title="成功锚点命中") + assert win["status"] == "proposed" + for bad_kwargs in ( + dict(kind="epiphany"), + dict(title=""), + dict(title="x" * 201), + dict(detail={}), + ): + try: + _propose(**bad_kwargs) + raise AssertionError(f"非法输入必须被拒绝: {bad_kwargs}") + except AssertionError: + raise + except LessonError: + pass + + +def test_no_auto_promotion() -> None: + out = _propose() + # 应用层:proposed 直接 promote 被拒(期望来源是 reviewing) + try: + promote(out["lesson_id"], decided_by="founder", target_type="pattern", + target_ref="pattern:test") + raise AssertionError("不得跳过评审自动升格") + except AssertionError: + raise + except LessonError: + pass + # 绕过应用层直接 UPDATE 也被触发器拒绝 + try: + with connect() as conn: + conn.execute( + "UPDATE example_lesson SET status='promoted', promotion_target_type='pattern', " + "promotion_target_ref='pattern:test' WHERE id=%s", (out["lesson_id"],)) + conn.commit() + raise AssertionError("触发器必须拒绝自动升格") + except AssertionError: + raise + except Exception: + pass + rows = list_lessons(status="proposed") + assert any(row["id"] == out["lesson_id"] for row in rows) + + +def test_human_promotion_requires_target() -> None: + out = _propose(kind="win") + start_review(out["lesson_id"], decided_by="founder", rationale="复利候选") + # reviewing 升格必须指明目标 + try: + promote(out["lesson_id"], decided_by="founder", target_type="ritual", target_ref="x") + raise AssertionError("非法目标类型必须被拒绝") + except AssertionError: + raise + except LessonError: + pass + try: + promote(out["lesson_id"], decided_by="founder", target_type="pattern", target_ref="") + raise AssertionError("缺落点必须被拒绝") + except AssertionError: + raise + except LessonError: + pass + promoted = promote(out["lesson_id"], decided_by="founder", target_type="skill", + target_ref="write-next-chapter", rationale="篇幅自纠环有效") + assert promoted["status"] == "promoted" + # 终态不得再流转 + for action in (start_review, reject): + try: + action(out["lesson_id"], decided_by="founder") + raise AssertionError("promoted 终态不得再流转") + except AssertionError: + raise + except LessonError: + pass + + +def test_reject_paths() -> None: + direct = _propose() + assert reject(direct["lesson_id"], decided_by="founder", rationale="n=1 证据不足")["status"] == "rejected" + reviewed = _propose() + start_review(reviewed["lesson_id"], decided_by="founder") + assert reject(reviewed["lesson_id"], decided_by="founder")["status"] == "rejected" + # rejected 终态 + try: + start_review(reviewed["lesson_id"], decided_by="founder") + raise AssertionError("rejected 终态不得再流转") + except AssertionError: + raise + except LessonError: + pass + + +def test_missing_decided_by_rejected() -> None: + out = _propose() + try: + start_review(out["lesson_id"], decided_by="") + raise AssertionError("decided_by 必填") + except AssertionError: + raise + except LessonError: + pass + + +def main() -> None: + tests = ( + ("登记输入校验", test_propose_validates_inputs), + ("禁止自动升格", test_no_auto_promotion), + ("人工升格必须指明目标", test_human_promotion_requires_target), + ("驳回路径与终态", test_reject_paths), + ("decided_by 必填", test_missing_decided_by_rejected), + ) + try: + for name, test in tests: + test() + print(f"PASS: {name}") + print("PASS:经验升格链真实库集成测试全部通过") + finally: + _cleanup() + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/quality-gate/scripts/test_rubric.py b/.claude/skills/score-content-quality/scripts/test_rubric.py similarity index 100% rename from .claude/skills/quality-gate/scripts/test_rubric.py rename to .claude/skills/score-content-quality/scripts/test_rubric.py diff --git a/.claude/skills/quality-gate/scripts/test_run_writer_blind_judge.py b/.claude/skills/score-content-quality/scripts/test_run_writer_blind_judge.py similarity index 60% rename from .claude/skills/quality-gate/scripts/test_run_writer_blind_judge.py rename to .claude/skills/score-content-quality/scripts/test_run_writer_blind_judge.py index cde1eb1..ad2cee8 100644 --- a/.claude/skills/quality-gate/scripts/test_run_writer_blind_judge.py +++ b/.claude/skills/score-content-quality/scripts/test_run_writer_blind_judge.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -"""blind judge v3 的匿名输入、可信绑定和 fresh panel 测试。""" +"""blind judge v4 输入/报告、v3 模型输出和 fresh panel 测试。""" from __future__ import annotations import copy import hashlib +import json import pathlib import sys import unittest @@ -24,7 +25,12 @@ from run_writer_blind_judge import ( # noqa: E402 validate_blind_judge_input, validate_blind_judge_report, ) -from writer_rubric import DIMENSIONS # noqa: E402 +from writer_rubric import ( # noqa: E402 + COMMON_DIMENSIONS, + DIMENSIONS, + RUBRIC_POLICY_VERSION, + SCENARIO_DIMENSION, +) def _candidate_hash(body: str) -> str: @@ -54,9 +60,11 @@ def blind_input(reviewer_id: str, *, order: tuple[str, ...] = ("blind-1", "blind bodies = {"blind-1": "林澈守住城门。", "blind-2": "林澈退回城内。"} pack = _truth_pack() payload: dict[str, Any] = { - "schemaVersion": "writer-blind-input-v3", + "schemaVersion": "writer-blind-input-v4", "runId": "run-judge-1", "sampleId": "sample-489", + "scenario": "turning_point", + "rubricPolicyVersion": RUBRIC_POLICY_VERSION, "reviewerInvocationId": reviewer_id, "candidateOrder": list(order), "candidates": [{"blindCandidateId": item, "candidateSha256": _candidate_hash(bodies[item]), "candidateBody": bodies[item]} for item in order], @@ -103,13 +111,19 @@ def blind_draft(payload: Mapping[str, Any], *, score: float = 8.0) -> dict[str, class SequenceRunner: - def __init__(self, outputs: list[Mapping[str, Any]]) -> None: - self.outputs = [copy.deepcopy(dict(item)) for item in outputs] + def __init__(self, outputs: list[Any]) -> None: + self.outputs = [ + item if isinstance(item, BaseException) else copy.deepcopy(dict(item)) + for item in outputs + ] self.calls: list[dict[str, Any]] = [] def run(self, *, adapter_role: str, model_input: Mapping[str, Any], output_schema: Mapping[str, Any]) -> Mapping[str, Any]: self.calls.append({"role": adapter_role, "input": copy.deepcopy(dict(model_input)), "schema": output_schema}) - return {"structuredOutput": self.outputs[len(self.calls) - 1], "modelReceiptSha256": "sha256:" + str(len(self.calls)) * 64} + output = self.outputs[len(self.calls) - 1] + if isinstance(output, BaseException): + raise output + return {"structuredOutput": output, "modelReceiptSha256": "sha256:" + str(len(self.calls)) * 64} class BlindJudgeV3Test(unittest.TestCase): @@ -121,7 +135,11 @@ class BlindJudgeV3Test(unittest.TestCase): forged = blind_draft(blind_input("reviewer-1")) forged["candidateSha256"] = "sha256:" + "f" * 64 - result = run_writer_blind_judge(blind_input("reviewer-1"), model_runner=SequenceRunner([forged])) + result = run_writer_blind_judge( + blind_input("reviewer-1"), + model_runner=SequenceRunner([forged]), + max_corrections=0, + ) self.assertFalse(result["ok"]) self.assertEqual(result["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID") @@ -131,17 +149,73 @@ class BlindJudgeV3Test(unittest.TestCase): result = run_writer_blind_judge(payload, model_runner=runner) self.assertTrue(result["ok"]) report = result["report"] - self.assertEqual(report["schemaVersion"], "blind-judge-report-v3") + self.assertEqual(report["schemaVersion"], "blind-judge-report-v4") + self.assertEqual(report["scenario"], "turning_point") + self.assertEqual(report["rubricPolicyVersion"], RUBRIC_POLICY_VERSION) self.assertEqual(report["reviewerInvocationId"], "reviewer-1") self.assertEqual(report["candidateScores"][0]["candidateSha256"], payload["candidates"][0]["candidateSha256"]) self.assertEqual(report["reportSha256"], canonical_sha256({k: v for k, v in report.items() if k != "reportSha256"})) model_input = runner.calls[0]["input"] - self.assertEqual(set(model_input), {"candidates", "fineOutline", "oracleTruthPack", "rubric"}) + self.assertEqual( + set(model_input), + { + "candidates", + "fineOutline", + "oracleTruthPack", + "rubric", + "outputContract", + "candidateQuoteHints", + }, + ) + self.assertEqual( + model_input["outputContract"]["expectedCounts"], + { + "candidateScores": 2, + "dimensionPreferences": len(DIMENSIONS), + "oracleAssertionVerdicts": 2, + "hardConstraintVerdicts": 2, + }, + ) + self.assertEqual( + model_input["candidateQuoteHints"]["blind-1"], ["林澈守住城门。"] + ) + self.assertEqual(model_input["rubric"]["policyVersion"], RUBRIC_POLICY_VERSION) + self.assertEqual(model_input["rubric"]["scenarioType"], "turning_point") + self.assertEqual( + [item["dimensionId"] for item in model_input["rubric"]["commonDimensions"]], + list(COMMON_DIMENSIONS), + ) + self.assertEqual( + model_input["rubric"]["scenarioDimension"]["dimensionId"], + SCENARIO_DIMENSION, + ) + self.assertEqual( + model_input["rubric"]["scenarioDimension"]["displayName"], + "转折执行", + ) serialized = str(model_input) for forbidden in ("runId", "sampleId", "reviewerInvocationId", "candidateSha256", "blindInputSha256", "authorizationSnapshotId"): self.assertNotIn(forbidden, serialized) + def test_unregistered_scenario_and_policy_drift_fail_before_model_call(self) -> None: + for field, value in ( + ("scenario", "romance"), + ("rubricPolicyVersion", "writer-replay-rubric-v1"), + ): + payload = blind_input("reviewer-1") + payload[field] = value + payload["blindInputSha256"] = canonical_sha256( + {key: item for key, item in payload.items() if key != "blindInputSha256"} + ) + runner = SequenceRunner([]) + + result = run_writer_blind_judge(payload, model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "BLIND_JUDGE_INPUT_SCHEMA_INVALID") + self.assertEqual(runner.calls, []) + def test_adapter_computes_quote_offsets(self) -> None: payload = blind_input("reviewer-1") result = run_writer_blind_judge(payload, model_runner=SequenceRunner([blind_draft(payload)])) @@ -149,9 +223,139 @@ class BlindJudgeV3Test(unittest.TestCase): self.assertEqual(evidence["sourceRef"], "candidate:0-2") self.assertEqual(evidence["excerpt"], "林澈") - def test_old_v2_and_v1_reports_fail_closed(self) -> None: + def test_model_id_order_is_normalized_without_relaxing_coverage(self) -> None: + """完整 ID 集乱序时确定性重排,缺失/重复仍失败关闭。""" + payload = blind_input("reviewer-1") - for version in ("blind-judge-report-v2", "blind-judge-report-v1"): + draft = blind_draft(payload) + draft["candidateScores"].reverse() + draft["dimensionPreferences"].reverse() + draft["oracleAssertionVerdicts"].reverse() + draft["hardConstraintVerdicts"].reverse() + + result = run_writer_blind_judge(payload, model_runner=SequenceRunner([draft])) + + self.assertTrue(result["ok"], result) + self.assertEqual( + [item["blindCandidateId"] for item in result["report"]["candidateScores"]], + list(payload["candidateOrder"]), + ) + self.assertEqual( + [item["dimension"] for item in result["report"]["dimensionPreferences"]], + list(DIMENSIONS), + ) + + def test_invalid_quote_gets_one_bounded_correction(self) -> None: + """首轮编造引文时回喂原稿与错误,第二轮合法即可完成。""" + + payload = blind_input("reviewer-1") + invalid = blind_draft(payload) + invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][ + "candidateQuote" + ] = "候选正文中不存在的引文" + runner = SequenceRunner([invalid, blind_draft(payload)]) + + result = run_writer_blind_judge( + payload, + model_runner=runner, + max_corrections=1, + ) + + self.assertTrue(result["ok"], result) + self.assertEqual(result["attemptCount"], 2) + self.assertEqual(len(runner.calls), 2) + self.assertNotIn("correction", runner.calls[0]["input"]) + correction = runner.calls[1]["input"]["correction"] + self.assertEqual(correction["previousDraft"], invalid) + self.assertIn("引文未在对应候选中出现", correction["error"]) + self.assertIn("林澈守住城门。", correction["validCandidateQuoteHints"]["blind-1"]) + + def test_invalid_quote_correction_exhaustion_fails_closed(self) -> None: + """一次纠错仍编造引文时保留稳定错误码,不继续第三次调用。""" + + payload = blind_input("reviewer-1") + invalid = blind_draft(payload) + invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][ + "candidateQuote" + ] = "候选正文中不存在的引文" + runner = SequenceRunner([invalid, invalid]) + + result = run_writer_blind_judge( + payload, + model_runner=runner, + max_corrections=1, + ) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "BLIND_JUDGE_QUOTE_NOT_FOUND") + self.assertEqual(result["attemptCount"], 2) + self.assertEqual(len(runner.calls), 2) + diagnostic = result["safeDiagnostic"] + self.assertEqual(diagnostic["errorCode"], "BLIND_JUDGE_QUOTE_NOT_FOUND") + self.assertEqual(diagnostic["arrayCounts"]["candidateScores"], 2) + self.assertNotIn("候选正文中不存在的引文", json.dumps(diagnostic, ensure_ascii=False)) + + def test_judge_inference_refs_bind_to_current_dimension_or_stable_id(self) -> None: + """推断证据必须引用当前维度/断言/约束 ID,不能引用字段名占位符。""" + + payload = blind_input("reviewer-1") + draft = blind_draft(payload) + for candidate in draft["candidateScores"]: + for dimension in DIMENSIONS: + candidate["scores"][dimension]["evidenceRefs"] = [ + {"sourceType": "judge_inference", "sourceId": dimension} + ] + for verdict in draft["oracleAssertionVerdicts"]: + verdict["evidenceRefs"] = [ + {"sourceType": "judge_inference", "sourceId": verdict["assertionId"]} + ] + for verdict in draft["hardConstraintVerdicts"]: + verdict["evidenceRefs"] = [ + {"sourceType": "judge_inference", "sourceId": verdict["constraintId"]} + ] + + result = run_writer_blind_judge( + payload, model_runner=SequenceRunner([draft]), max_corrections=0 + ) + + self.assertTrue(result["ok"], result) + + invalid = copy.deepcopy(draft) + invalid["oracleAssertionVerdicts"][0]["evidenceRefs"][0]["sourceId"] = ( + "assertionId" + ) + failed = run_writer_blind_judge( + payload, model_runner=SequenceRunner([invalid]), max_corrections=0 + ) + self.assertFalse(failed["ok"]) + self.assertEqual(failed["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID") + + def test_transient_api_error_retries_same_blind_input_once(self) -> None: + """API 瞬时错误与格式纠错共用两次总调用上限,不伪造 correction。""" + + payload = blind_input("reviewer-1") + runner = SequenceRunner( + [ + BlindJudgeContractError("BLIND_JUDGE_API_ERROR", "瞬时 API 错误"), + blind_draft(payload), + ] + ) + + result = run_writer_blind_judge(payload, model_runner=runner) + + self.assertTrue(result["ok"], result) + self.assertEqual(result["attemptCount"], 2) + self.assertEqual(len(runner.calls), 2) + self.assertNotIn("correction", runner.calls[0]["input"]) + self.assertNotIn("correction", runner.calls[1]["input"]) + + def test_old_v3_v2_and_v1_reports_fail_closed(self) -> None: + payload = blind_input("reviewer-1") + for version in ( + "blind-judge-report-v3", + "blind-judge-report-v2", + "blind-judge-report-v1", + ): with self.subTest(version=version), self.assertRaises(BlindJudgeContractError): validate_blind_judge_report({"schemaVersion": version}, payload) @@ -196,7 +400,11 @@ class BlindJudgeV3Test(unittest.TestCase): payload = blind_input("reviewer-1") draft = blind_draft(payload) draft["oracleAssertionVerdicts"].pop() - result = run_writer_blind_judge(payload, model_runner=SequenceRunner([draft])) + result = run_writer_blind_judge( + payload, + model_runner=SequenceRunner([draft]), + max_corrections=0, + ) self.assertFalse(result["ok"]) self.assertEqual(result["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID") @@ -212,6 +420,31 @@ class BlindJudgeV3Test(unittest.TestCase): self.assertEqual(runner.calls[1]["input"]["candidates"][0]["blindCandidateId"], "blind-2") self.assertNotIn("previousReport", runner.calls[1]["input"]) + def test_panel_allows_two_corrections_per_reviewer(self) -> None: + """面板必须把 Gate A 的三次总调用合同传给每位评委。""" + + first = blind_input("reviewer-1") + second = blind_input("reviewer-2", order=("blind-2", "blind-1")) + invalid = blind_draft(first) + invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][ + "candidateQuote" + ] = "候选正文中不存在的引文" + runner = SequenceRunner([ + invalid, + invalid, + blind_draft(first), + blind_draft(second), + ]) + + result = run_writer_blind_judge_panel(first, second, model_runner=runner) + + self.assertTrue(result["ok"], result) + self.assertEqual(result["reviewCount"], 2) + self.assertEqual(len(runner.calls), 4) + self.assertIn("correction", runner.calls[1]["input"]) + self.assertIn("correction", runner.calls[2]["input"]) + self.assertNotIn("correction", runner.calls[3]["input"]) + def test_reviewer_identity_reuse_fails_before_call(self) -> None: first = blind_input("reviewer-1") reused = blind_input("reviewer-1", order=("blind-2", "blind-1")) @@ -221,6 +454,38 @@ class BlindJudgeV3Test(unittest.TestCase): self.assertEqual(result["primaryCode"], "JUDGE_INVALID") self.assertEqual(runner.calls, []) + def test_reviewers_with_different_scenarios_fail_before_call(self) -> None: + first = blind_input("reviewer-1") + second = blind_input("reviewer-2", order=("blind-2", "blind-1")) + second["scenario"] = "battle" + second["blindInputSha256"] = canonical_sha256( + {key: value for key, value in second.items() if key != "blindInputSha256"} + ) + runner = SequenceRunner([]) + + result = run_writer_blind_judge_panel(first, second, model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["primaryCode"], "JUDGE_INVALID") + self.assertEqual(runner.calls, []) + + def test_pre_call_input_failure_preserves_specific_contract_code(self) -> None: + first = blind_input("reviewer-1") + second = blind_input("reviewer-2", order=("blind-2", "blind-1")) + first["candidates"][0]["candidateBody"] += "篡改" + first["blindInputSha256"] = canonical_sha256( + {key: value for key, value in first.items() if key != "blindInputSha256"} + ) + runner = SequenceRunner([]) + + result = run_writer_blind_judge_panel(first, second, model_runner=runner) + + self.assertFalse(result["ok"]) + self.assertEqual(result["status"], "failed_judge_invalid") + self.assertEqual(result["primaryCode"], "BLIND_JUDGE_CANDIDATE_HASH_MISMATCH") + self.assertEqual(result["reviewCount"], 0) + self.assertEqual(runner.calls, []) + def test_score_instability_triggers_exactly_one_third_reviewer(self) -> None: first = blind_input("reviewer-1") second = blind_input("reviewer-2", order=("blind-2", "blind-1")) diff --git a/.claude/skills/quality-gate/scripts/test_writer_rubric.py b/.claude/skills/score-content-quality/scripts/test_writer_rubric.py similarity index 86% rename from .claude/skills/quality-gate/scripts/test_writer_rubric.py rename to .claude/skills/score-content-quality/scripts/test_writer_rubric.py index daf6144..bf5f00e 100644 --- a/.claude/skills/quality-gate/scripts/test_writer_rubric.py +++ b/.claude/skills/score-content-quality/scripts/test_writer_rubric.py @@ -10,11 +10,16 @@ import unittest sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from writer_rubric import ( # noqa: E402 + COMMON_DIMENSIONS, DIMENSIONS, RUBRIC_PROFILE, + RUBRIC_POLICY_VERSION, + SCENARIO_DIMENSION, + SCENARIO_TYPES, adjudicate_reviews, adjudicate_structured_reviews, deblind_reports, + rubric_for_scenario, validate_blind_pair, validate_report, validate_structured_report, @@ -70,6 +75,36 @@ class WriterRubricValidationTest(unittest.TestCase): bad[DIMENSIONS[0]]["score"] = 8.25 self.assertTrue(any("0.5" in error for error in validate_report(report("judge-1", bad)))) + def test_policy_combines_common_dimensions_with_registered_scenario_profile(self): + expected_bands = ["9-10", "7-8.5", "5-6.5", "3-4.5", "0-2.5"] + display_names: set[str] = set() + for scenario in SCENARIO_TYPES: + policy = rubric_for_scenario(scenario) + self.assertEqual(policy["policyVersion"], RUBRIC_POLICY_VERSION) + self.assertEqual(policy["scenarioType"], scenario) + self.assertEqual(policy["dimensions"], list(DIMENSIONS)) + self.assertEqual( + [item["dimensionId"] for item in policy["commonDimensions"]], + list(COMMON_DIMENSIONS), + ) + for item in policy["commonDimensions"]: + self.assertEqual( + [anchor["scoreBand"] for anchor in item["anchors"]], + expected_bands, + ) + scenario_dimension = policy["scenarioDimension"] + self.assertEqual(scenario_dimension["dimensionId"], SCENARIO_DIMENSION) + self.assertEqual( + [anchor["scoreBand"] for anchor in scenario_dimension["anchors"]], + expected_bands, + ) + self.assertGreaterEqual(len(scenario_dimension["criteria"]), 4) + display_names.add(scenario_dimension["displayName"]) + self.assertEqual(len(display_names), len(SCENARIO_TYPES)) + + with self.assertRaisesRegex(ValueError, "未登记场景类型"): + rubric_for_scenario("unknown") + def test_each_dimension_requires_typed_evidence(self): bad = scorecard() bad[DIMENSIONS[2]]["evidence"] = [] @@ -194,9 +229,11 @@ def structured_report( "blind-2": "sha256:" + "2" * 64, } return { - "schemaVersion": "blind-judge-report-v3", + "schemaVersion": "blind-judge-report-v4", "runId": "run-1", "sampleId": "sample-1", + "scenario": "battle", + "rubricPolicyVersion": RUBRIC_POLICY_VERSION, "reviewerInvocationId": reviewer_id, "blindInputSha256": "sha256:" + "3" * 64, "oracleTruthPackSha256": "sha256:" + "4" * 64, diff --git a/.claude/skills/quality-gate/scripts/writer_gate.py b/.claude/skills/score-content-quality/scripts/writer_gate.py similarity index 95% rename from .claude/skills/quality-gate/scripts/writer_gate.py rename to .claude/skills/score-content-quality/scripts/writer_gate.py index 237f038..7f460dc 100644 --- a/.claude/skills/quality-gate/scripts/writer_gate.py +++ b/.claude/skills/score-content-quality/scripts/writer_gate.py @@ -19,23 +19,15 @@ from pathlib import Path from typing import Any, Mapping, Sequence SCRIPT_DIR = Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -if str(RUNTIME_DIR) not in sys.path: - sys.path.insert(0, str(RUNTIME_DIR)) +EVIDENCE_DIR = SCRIPT_DIR.parents[1] / "record-run-evidence" / "scripts" +if str(EVIDENCE_DIR) not in sys.path: + sys.path.insert(0, str(EVIDENCE_DIR)) -from writer_rubric import DIMENSIONS +from writer_rubric import DIMENSIONS, SCENARIO_DIMENSION, SCENARIO_TYPES from file_cas import FileCasStore from gate_input_builder import canonical_sha256, validate_gate_input -REQUIRED_SCENARIOS = frozenset( - { - "battle", - "character_dialogue", - "turning_point", - "information_reveal", - "returning_character", - } -) +REQUIRED_SCENARIOS = frozenset(SCENARIO_TYPES) CONFOUND_LABELS = { "falseNegatives": "假阴", @@ -419,6 +411,10 @@ def _gate_b_metrics(samples: Sequence[Mapping[str, Any]]) -> dict[str, Any]: statuses: list[dict[str, Any]] = [] c_arms: list[dict[str, Any]] = [] deltas: dict[str, list[dict[str, float]]] = {"B-A": [], "C-A": []} + scenario_counts = {scenario: 0 for scenario in SCENARIO_TYPES} + scenario_execution_deltas: dict[str, list[float]] = { + scenario: [] for scenario in SCENARIO_TYPES + } seen_ids: set[str] = set() for index, sample in enumerate(samples): @@ -428,6 +424,7 @@ def _gate_b_metrics(samples: Sequence[Mapping[str, Any]]) -> dict[str, Any]: seen_ids.add(sample_id) work_counts[work_id] = work_counts.get(work_id, 0) + 1 scenarios.add(scenario) + scenario_counts[scenario] += 1 status = _sample_status(sample, index) statuses.append(status) c_arms.append(_sample_c_arm(sample, index)) @@ -440,6 +437,9 @@ def _gate_b_metrics(samples: Sequence[Mapping[str, Any]]) -> dict[str, Any]: for dimension in DIMENSIONS } ) + scenario_execution_deltas[scenario].append( + scores["C"][SCENARIO_DIMENSION] - scores["A"][SCENARIO_DIMENSION] + ) total_count = len(samples) stable_count = len(deltas["C-A"]) @@ -503,6 +503,21 @@ def _gate_b_metrics(samples: Sequence[Mapping[str, Any]]) -> dict[str, Any]: "dimensionDeclineOverHalfRatios": decline_ratios, "anyDimensionDeclineOverHalfRatio": any_decline_ratio, "fidelityPositiveSampleRatio": fidelity_positive_ratio, + "scenarioExecutionByType": { + scenario: { + "sampleCount": scenario_counts[scenario], + "stableSampleCount": len(values), + "cMinusAAverageDelta": ( + sum(values) / len(values) if values else None + ), + "declineOverHalfRatio": ( + sum(value < -0.5 for value in values) / len(values) + if values + else None + ), + } + for scenario, values in scenario_execution_deltas.items() + }, "strata": _stratified_metrics(samples), } @@ -610,18 +625,18 @@ def decide_gate_b(gate_input: Mapping[str, Any]) -> dict[str, Any]: averages = normalized["averageDeltas"]["C-A"] if averages["style_consistency"] < -0.25: quality_reasons.append("style_consistency_average_delta_below_minus_0_25") - if averages["narrative_tension"] < -0.25: - quality_reasons.append("narrative_tension_average_delta_below_minus_0_25") if normalized["anyDimensionDeclineOverHalfRatio"] > 0.20: quality_reasons.append("dimension_decline_over_half_ratio_above_20_percent") + for scenario, metrics in normalized["scenarioExecutionByType"].items(): + average = metrics["cMinusAAverageDelta"] + if average is not None and average < -0.25: + quality_reasons.append(f"{scenario}_scenario_execution_regression") for stratum, metrics in normalized["strata"].items(): if not metrics["sampleCount"]: continue deltas = metrics["cMinusAAverageDeltas"] if deltas["style_consistency"] < -0.25: quality_reasons.append(f"{stratum}_style_consistency_regression") - if deltas["narrative_tension"] < -0.25: - quality_reasons.append(f"{stratum}_narrative_tension_regression") if quality_reasons: return _report("B", "failed", quality_reasons, confounds, normalized) diff --git a/.claude/skills/quality-gate/scripts/writer_rubric.py b/.claude/skills/score-content-quality/scripts/writer_rubric.py similarity index 74% rename from .claude/skills/quality-gate/scripts/writer_rubric.py rename to .claude/skills/score-content-quality/scripts/writer_rubric.py index e6dbf4d..902b215 100644 --- a/.claude/skills/quality-gate/scripts/writer_rubric.py +++ b/.claude/skills/score-content-quality/scripts/writer_rubric.py @@ -8,13 +8,153 @@ from typing import Any, Mapping, Sequence RUBRIC_PROFILE = "writer_replay" -DIMENSIONS = ( +RUBRIC_POLICY_VERSION = "writer-replay-rubric-v2" +COMMON_DIMENSIONS = ( "setting_entity_fidelity", "fine_outline_fidelity", "style_consistency", - "narrative_tension", "prose_readability", ) +# WHY: 保留既有 ID,避免破坏 blind-judge v3 的输出 schema 与历史 Gate 汇总;从 v2 +# 策略起,它不再表示所有章节共用的抽象“紧张度”,而是按当前场景策略解释的执行分。 +SCENARIO_DIMENSION = "narrative_tension" +DIMENSIONS = (*COMMON_DIMENSIONS[:3], SCENARIO_DIMENSION, COMMON_DIMENSIONS[3]) +SCENARIO_TYPES = ( + "battle", + "character_dialogue", + "turning_point", + "information_reveal", + "returning_character", +) + +COMMON_DIMENSION_POLICIES: dict[str, dict[str, Any]] = { + "setting_entity_fidelity": { + "question": "设定、实体、关系、能力边界和冻结时点状态是否保真", + "anchors": [ + {"scoreBand": "9-10", "meaning": "核心与局部事实均保真,无实质冲突或错置"}, + {"scoreBand": "7-8.5", "meaning": "核心事实保真,仅有不影响理解的局部含混或轻微偏差"}, + {"scoreBand": "5-6.5", "meaning": "主关系可辨,但存在一处实质歧义、属性错置或边界不清"}, + {"scoreBand": "3-4.5", "meaning": "存在多处实质冲突,人物、实体或能力状态难以自洽"}, + {"scoreBand": "0-2.5", "meaning": "核心身份、阵营、状态或世界规则被反转或破坏"}, + ], + }, + "fine_outline_fidelity": { + "question": "细纲硬事件、结果方向、伏笔动作、出场实体和章末钩子是否忠实", + "anchors": [ + {"scoreBand": "9-10", "meaning": "全部硬骨架语义成立,顺序、伏笔状态和章末钩子均准确"}, + {"scoreBand": "7-8.5", "meaning": "硬约束全部成立,仅有不改变结果的节拍或展开偏差"}, + {"scoreBand": "5-6.5", "meaning": "主事件成立,但遗漏、提前兑现或弱化一个关键节拍或钩子"}, + {"scoreBand": "3-4.5", "meaning": "多个硬事件、结果方向或伏笔状态发生实质漂移"}, + {"scoreBand": "0-2.5", "meaning": "章节核心目标缺失、反转或与细纲方向相反"}, + ], + }, + "style_consistency": { + "question": "是否延续本作品的人物声音、动作习惯、句式和叙事质感", + "anchors": [ + {"scoreBand": "9-10", "meaning": "人物声音和叙事节奏稳定,整体可自然接入相邻章节"}, + {"scoreBand": "7-8.5", "meaning": "整体同风格,仅有少量局部措辞、比喻或节奏漂移"}, + {"scoreBand": "5-6.5", "meaning": "作品仍可辨认,但文体、人物声音或叙事密度明显变化"}, + {"scoreBand": "3-4.5", "meaning": "大部分段落与既有作品声音不一致或人物趋同"}, + {"scoreBand": "0-2.5", "meaning": "文体、人物声音或类型感与作品基线完全不相容"}, + ], + }, + "prose_readability": { + "question": "语言、指代、空间、动作和转场是否准确、自然、清晰", + "anchors": [ + {"scoreBand": "9-10", "meaning": "表达清楚流畅,指代、动作、空间和转场均无阅读阻力"}, + {"scoreBand": "7-8.5", "meaning": "整体顺畅,仅有少量局部重复、跳接或措辞生硬"}, + {"scoreBand": "5-6.5", "meaning": "可以读懂,但反复出现指代、空间、节奏或信息组织问题"}, + {"scoreBand": "3-4.5", "meaning": "频繁需要回读,动作主体、场景关系或因果难以确认"}, + {"scoreBand": "0-2.5", "meaning": "主要段落无法连贯理解或存在大面积语言结构故障"}, + ], + }, +} + +SCENARIO_POLICIES: dict[str, dict[str, Any]] = { + "battle": { + "displayName": "战斗执行", + "question": "战斗的空间、攻防因果、策略升级、力量边界和结果代价是否成立", + "criteria": ["空间与动作可追踪", "攻防与策略有因果", "力量边界不漂移", "升级与结果产生后果"], + "anchors": [ + {"scoreBand": "9-10", "meaning": "动作与空间清楚,攻防有策略因果,升级和代价共同推动局势"}, + {"scoreBand": "7-8.5", "meaning": "主战斗链成立,仅有局部空间、节拍或策略交代不足"}, + {"scoreBand": "5-6.5", "meaning": "胜负可读,但动作拼接、力量边界或升级逻辑有一处明显薄弱"}, + {"scoreBand": "3-4.5", "meaning": "多处攻防、空间或能力因果断裂,胜负主要靠宣告"}, + {"scoreBand": "0-2.5", "meaning": "战斗目标未形成,或核心规则与结果无法成立"}, + ], + }, + "character_dialogue": { + "displayName": "人物对话执行", + "question": "人物声音、对话目标、潜台词、关系推进和信息交换是否成立", + "criteria": ["声音可区分", "双方各有目标", "潜台词与反应自然", "关系或局面发生变化"], + "anchors": [ + {"scoreBand": "9-10", "meaning": "遮住名字仍能辨人,对话有目标与潜台词,并推动关系或局面"}, + {"scoreBand": "7-8.5", "meaning": "声音和目标基本清楚,仅有少量直白解释或反应不足"}, + {"scoreBand": "5-6.5", "meaning": "信息传达完成,但人物声音趋同、潜台词弱或关系推进有限"}, + {"scoreBand": "3-4.5", "meaning": "对白主要承担说明,人物目标和互动因果多处缺失"}, + {"scoreBand": "0-2.5", "meaning": "关键对话没有成立,人物声音或行为与既有角色严重冲突"}, + ], + }, + "turning_point": { + "displayName": "转折执行", + "question": "转折是否有前因、触发、不可逆变化、人物反应和后续驱动力", + "criteria": ["前置条件充分", "触发因果明确", "状态变化不可逆", "反应与章末驱动力成立"], + "anchors": [ + {"scoreBand": "9-10", "meaning": "转折由充分前因触发,变化不可逆,人物反应和后续钩子完整"}, + {"scoreBand": "7-8.5", "meaning": "核心转折可信,仅有铺垫、反应或后果的一处局部不足"}, + {"scoreBand": "5-6.5", "meaning": "转折发生,但前因不足、后果偏弱或章末过早兑现关键悬念"}, + {"scoreBand": "3-4.5", "meaning": "转折依赖突发宣告,人物反应或状态变化难以自洽"}, + {"scoreBand": "0-2.5", "meaning": "核心转折缺失、被反转或没有产生应有状态变化"}, + ], + }, + "information_reveal": { + "displayName": "信息揭示执行", + "question": "信息来源、知情边界、揭示时机、清晰度和揭示后的影响是否成立", + "criteria": ["来源与证据可追踪", "知情范围正确", "时机不过早不过晚", "揭示改变理解或行动"], + "anchors": [ + {"scoreBand": "9-10", "meaning": "来源、知情边界和时机准确,揭示清楚且有效改变理解或行动"}, + {"scoreBand": "7-8.5", "meaning": "揭示目标成立,仅有局部解释偏多、节奏偏直或影响不足"}, + {"scoreBand": "5-6.5", "meaning": "信息可以理解,但来源、时机、知情边界或戏剧影响有一处明显问题"}, + {"scoreBand": "3-4.5", "meaning": "信息缺少证据链、提前泄露、顺序混乱或大量依赖说明"}, + {"scoreBand": "0-2.5", "meaning": "核心信息未揭示、揭示错误或破坏作品的事实与知情边界"}, + ], + }, + "returning_character": { + "displayName": "老角色回归执行", + "question": "角色状态与声音是否连续,回归动机、他人反应和剧情作用是否成立", + "criteria": ["状态与声音连续", "回归动机可信", "识别与关系反应自然", "回归推动当前剧情"], + "anchors": [ + {"scoreBand": "9-10", "meaning": "角色状态、声音和关系连续,回归自然并实质推动当前剧情"}, + {"scoreBand": "7-8.5", "meaning": "回归可信有效,仅有局部反应、铺垫或剧情作用不足"}, + {"scoreBand": "5-6.5", "meaning": "角色能接回故事,但状态解释生硬、关系反应弱或作用偏工具化"}, + {"scoreBand": "3-4.5", "meaning": "角色声音或状态多处断裂,回归主要依赖说明或巧合"}, + {"scoreBand": "0-2.5", "meaning": "角色连续性被破坏,回归动机或身份关系无法成立"}, + ], + }, +} + + +def rubric_for_scenario(scenario: str) -> dict[str, Any]: + """返回完整且可序列化的通用指标 + 当前场景评分策略。""" + + if scenario not in SCENARIO_POLICIES: + raise ValueError(f"未登记场景类型: {scenario}") + return { + "policyVersion": RUBRIC_POLICY_VERSION, + "scenarioType": scenario, + "dimensions": list(DIMENSIONS), + "scoreRange": [0, 10], + "scoreStep": 0.5, + "commonDimensions": [ + {"dimensionId": dimension, **COMMON_DIMENSION_POLICIES[dimension]} + for dimension in COMMON_DIMENSIONS + ], + "scenarioDimension": { + "dimensionId": SCENARIO_DIMENSION, + "compatibilityNote": "此 ID 为兼容保留,必须按当前 scenarioType 的场景策略评分,不按抽象通用紧张度评分", + **SCENARIO_POLICIES[scenario], + }, + } EVIDENCE_SOURCE_TYPES = frozenset( {"fine_outline", "historical_prose", "judge_inference"} ) @@ -36,6 +176,8 @@ STRUCTURED_REPORT_FIELDS = frozenset( "schemaVersion", "runId", "sampleId", + "scenario", + "rubricPolicyVersion", "reviewerInvocationId", "blindInputSha256", "oracleTruthPackSha256", @@ -49,7 +191,7 @@ STRUCTURED_REPORT_FIELDS = frozenset( "reportSha256", } ) -STRUCTURED_REPORT_VERSION = "blind-judge-report-v3" +STRUCTURED_REPORT_VERSION = "blind-judge-report-v4" STRUCTURED_VERDICTS = frozenset({"pass", "fail", "unknown"}) @@ -199,18 +341,22 @@ def _validate_structured_verdicts(value: Any, path: str, id_field: str) -> list[ def validate_structured_report(report: Any) -> list[str]: - """校验 blind-judge-report-v3 的 closed-fields 与逐项结构。""" + """校验 blind-judge-report-v4 的 closed-fields 与逐项结构。""" if not isinstance(report, Mapping): - return ["v3 评委报告必须是对象"] + return ["v4 评委报告必须是对象"] errors: list[str] = [] if set(report) != STRUCTURED_REPORT_FIELDS: - errors.append("v3 评委报告存在额外字段或缺字段") + errors.append("v4 评委报告存在额外字段或缺字段") if report.get("schemaVersion") != STRUCTURED_REPORT_VERSION: errors.append(f"schemaVersion 必须是 {STRUCTURED_REPORT_VERSION}") for field in ("runId", "sampleId", "reviewerInvocationId"): if not isinstance(report.get(field), str) or not report[field].strip(): errors.append(f"{field} 必须是非空字符串") + if report.get("scenario") not in SCENARIO_TYPES: + errors.append("scenario 必须是已登记场景类型") + if report.get("rubricPolicyVersion") != RUBRIC_POLICY_VERSION: + errors.append("rubricPolicyVersion 必须是当前正文评分策略") for field in ( "blindInputSha256", "oracleTruthPackSha256", @@ -331,7 +477,13 @@ def validate_structured_pair(first: Any, second: Any) -> list[str]: return errors if first["reviewerInvocationId"] == second["reviewerInvocationId"]: errors.append("双评必须使用独立 reviewerInvocationId") - for field in ("runId", "sampleId", "oracleTruthPackSha256"): + for field in ( + "runId", + "sampleId", + "scenario", + "rubricPolicyVersion", + "oracleTruthPackSha256", + ): if first[field] != second[field]: errors.append(f"双评的 {field} 必须一致") if _structured_candidate_map(first) != _structured_candidate_map(second): @@ -477,7 +629,13 @@ def adjudicate_structured_reviews( "errors": ["第三评必须使用独立 reviewerInvocationId"], "reviewCount": 3, } - for field in ("runId", "sampleId", "oracleTruthPackSha256"): + for field in ( + "runId", + "sampleId", + "scenario", + "rubricPolicyVersion", + "oracleTruthPackSha256", + ): if third[field] != first[field]: return { "status": "failed_judge_invalid", @@ -755,7 +913,14 @@ def adjudicate_reviews( __all__ = [ "RUBRIC_PROFILE", + "RUBRIC_POLICY_VERSION", + "COMMON_DIMENSIONS", "DIMENSIONS", + "SCENARIO_DIMENSION", + "SCENARIO_TYPES", + "COMMON_DIMENSION_POLICIES", + "SCENARIO_POLICIES", + "rubric_for_scenario", "EVIDENCE_SOURCE_TYPES", "STRUCTURED_EVIDENCE_SOURCE_TYPES", "STRUCTURED_REPORT_VERSION", diff --git a/.claude/skills/search/SKILL.md b/.claude/skills/search-knowledge/SKILL.md similarity index 61% rename from .claude/skills/search/SKILL.md rename to .claude/skills/search-knowledge/SKILL.md index 9909628..5e35b78 100644 --- a/.claude/skills/search/SKILL.md +++ b/.claude/skills/search-knowledge/SKILL.md @@ -1,23 +1,23 @@ --- -name: search -description: 向量检索——创作意图→embed→pgvector 余弦召回→授权过滤(仅已确认+已绑定,或管理面含草稿)→aiContext 字段裁剪→带相似度分的结果集。B3 检索验证与 C4 PG 版 read-context 的取数端。 +name: search-knowledge +description: 将创作意图嵌入后从 pgvector 召回知识,并按绑定、状态和 aiContext 权限裁剪结果。管理审查或上下文组装需要知识候选时使用;不返回参考书全文,不绕过作品绑定。 --- -# search —— 知识向量检索 +# 检索知识 -对应 muse API 面:知识检索。查询嵌入与知识行嵌入同模型同维(Qwen3-Embedding-8B / 1024,经 embed skill 同实现),距离=余弦(`<=>`,HNSW 索引)。 +对应 muse API 面:知识检索。查询嵌入与知识行嵌入同模型同维(Qwen3-Embedding-8B / 1024,经 `embed-knowledge` 同实现),距离=余弦(`<=>`,HNSW 索引)。 ## 用法 ```bash # 管理面检索(B3 验证:对 draft 面召回,含草稿行) -.venv/bin/python .claude/skills/search/scripts/search.py "主角被围攻时反杀的打斗写法" --scope admin --top 8 +.venv/bin/python .claude/skills/search-knowledge/scripts/search.py "主角被围攻时反杀的打斗写法" --scope admin --top 8 # 作品面检索(C4:仅已确认 entity + 该作品已绑定的库;work-id 必填) -.venv/bin/python .claude/skills/search/scripts/search.py "师徒决裂的情感铺垫" --scope work --work-id 8 --top 5 +.venv/bin/python .claude/skills/search-knowledge/scripts/search.py "师徒决裂的情感铺垫" --scope work --work-id 8 --top 5 # 按型过滤 + 用途裁剪(按 aiContext 只回显该用途可见字段) -.venv/bin/python .claude/skills/search/scripts/search.py "倒计时紧迫感" --type craft --purpose generation +.venv/bin/python .claude/skills/search-knowledge/scripts/search.py "倒计时紧迫感" --type craft --purpose generation ``` ## 合同 diff --git a/.claude/skills/search/scripts/search.py b/.claude/skills/search-knowledge/scripts/search.py similarity index 96% rename from .claude/skills/search/scripts/search.py rename to .claude/skills/search-knowledge/scripts/search.py index 28e0202..00a2e3b 100644 --- a/.claude/skills/search/scripts/search.py +++ b/.claude/skills/search-knowledge/scripts/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""search skill:知识向量检索(余弦召回 + 授权过滤 + aiContext 字段裁剪)。 +"""search-knowledge Skill:知识向量检索(余弦召回 + 授权过滤 + aiContext 字段裁剪)。 -合同见同 skill SKILL.md。查询嵌入与知识行同模型同维(复用 embed skill 的实现)。 +合同见同目录 SKILL.md。查询嵌入与知识行同模型同维(复用 embed-knowledge 的实现)。 """ import json import pathlib @@ -11,8 +11,8 @@ from typing import Any, Callable import click import psycopg -# 复用 embed skill 的通道实现(同模型同维,语义对齐) -sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed" / "scripts")) +# 复用 embed-knowledge 的通道实现(同模型同维,语义对齐) +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "embed-knowledge" / "scripts")) from embed_drafts import _session, embed_texts # noqa: E402 DSN = "postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example" @@ -40,7 +40,7 @@ def visible(ai_rule, purpose): def _default_embedder(intent: str) -> list[float]: - """复用 embed skill 生成单条查询向量,并统一失败语义。""" + """复用 embed-knowledge 生成单条查询向量,并统一失败语义。""" vectors, bad = embed_texts(_session(), [intent]) if bad or not vectors: diff --git a/.claude/skills/search/scripts/test_search.py b/.claude/skills/search-knowledge/scripts/test_search.py similarity index 100% rename from .claude/skills/search/scripts/test_search.py rename to .claude/skills/search-knowledge/scripts/test_search.py diff --git a/.claude/skills/upgrade/SKILL.md b/.claude/skills/upgrade/SKILL.md deleted file mode 100644 index e663a77..0000000 --- a/.claude/skills/upgrade/SKILL.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -name: upgrade -description: 作品面升格管线——把已拆作品的正文按窗抽成 upgrade_book 实体/关系卡(串行有状态:同书 advisory lock 互斥 + 七域备份/verify/rehearse/restore + execute + reset 重抽准备)及其运维。用于存量作品升格抽取,以及升格全量重抽前的备份、复验与恢复。 -disable-model-invocation: true ---- - -# upgrade —— 作品面升格管线(串行有状态:同书 advisory lock + 七域备份/verify/rehearse/restore + execute + reset)+ 其运维 - -何时用:作品已拆书(parse-book 出章级细纲/实体脚手架)后,要把**本书自己的**实体/关系/大纲按窗抽进正式候选层(`upgrade_book` 来源),或对已升格的书做**全量重抽准备**(reset)与**备份/恢复**。拆书逆向(章节结构/细纲/范式)在 parse-book skill;本 skill 只承接其后的作品面升格与升格运维。内容生产 LLM 走 New-API `MiniMax-M3`(经 llm skill),parse_llm(敏感降级链 + llm 入口 + DSN/TENANT)留 parse-book,本 skill 单向跨 skill 引用(upgrade→parse-book)。 - -## 升格执行器 `scripts/upgrade.py`(命令 `windows`/`run`/`status`) - -正文按窗抽取 `upgrade_book` 实体卡。实体观察模型只返回新实体和已知实体的语义变化;新名字另带正文逐字短名称作为证据索引,纯出场和所有出场章由名称/别名索引逐章机械计算,不要求模型回显大批 presence 对象。模型常见结构键别名只做确定性归一,未知额外键仍失败关闭。每窗采用“两阶段短事务”:模型/嵌入调用期间不持有业务连接;实体写入和关系写入各自提交 `processing` marker;最终嵌入必须完整成功,才与 `done` 在同一短事务提交。新卡 `INSERT` 必须返回数据库实际 `revision`,同窗后续更新的 CAS 绑定该值,禁止假定初始版本。关系首次同 pair 冲突只允许一次模型 repair;repair 仍拆成多条时,只机械合并关系类型、本窗演变和无字段冲突的其他维度,非空未知顶层键或同字段异值继续失败关闭,不增加第三次模型调用。窗口输入摘要绑定作品标题、章/块 ID、标题、状态、revision、类型、正文、active schema 合同和当前脚本 SHA。启动时会恢复可精确撤销的 `processing`;本次窗口第二次失败立即以非零退出并停止当前作品,保留 `retryable-clean` 断点,人工再次运行才继续。 - -**登场中性台阶名称边界**:名称只能取 debut 章正文唯一实际命中的合法规范名或别名;若只命中旧别名就用旧别名,多个合法名称同时命中、仅命中通用称谓或无法唯一确定时退化为“人物登场/物件登场”等仅类型描述,禁止泄漏未来才形成的规范名。`new_card` 规范化名称后必须把实际 canonical 与合法 aliases 立即登记到本窗判重索引,不能继续使用模型原始括号名。 - -**嵌入唯一键冲突失败关闭**:owner 预检必须覆盖全部 todo hash;写入前锁 draft、全部活向量和 hash owner,复验 `pending + upgrade_book`、revision、payload hash 与 `entity_id` 归属。普通嵌入异常、owner 冲突或 payload 漂移都令窗口失败,不打印完成;最终向量写入和 `done` 必须同事务。任何外部确认、删除、改版或辅助域漂移都禁止补偿覆盖。 - -### 单卡质量修复 `scripts/upgrade.py repair-card-quality` - -仅登记目标 `work=8/draft=25360/名称=神权之星`。`--preview` 在 `REPEATABLE READ READ ONLY` 事务中输出确定性 location payload、关联行摘要、旧/新 payload 与 embedding hash 和 `confirmation_sha`,不写任何表、不调用嵌入。`--execute` 必须同时提供该 SHA 与 `--confirm-no-live-process`;执行前复核快照,事务外调用现有 embedding route,随后锁定 draft、embedding 和关联行,CAS 校验 revision、payload、owner、presence、alias、card_state 后,在同一事务提交 payload revision 与新 embedding。任何漂移、非法关联、embedding 不完整或 owner 冲突均失败关闭;不修改 window、presence、alias、card_state 或历史 window audit。 - -### presence 冗余清理 `scripts/upgrade.py repair-presence-duplicates` - -仅登记 `work=8` 已核实的十组 `(window_no, chapter_no, entity_type, name)` 双行冗余(共 20 行,组内 observation 语义重合但非字节相同),每组保留 `MIN(id)`、软删 `MAX(id)`。`--preview` 在 `REPEATABLE READ READ ONLY` 事务中只读输出十组行摘要(行级 digest,不含 observation 原文)、保留/删除 ID 集合与 `confirmation_sha`,不写任何表、不调用嵌入/模型。`--execute` 必须同时提供该 SHA 与 `--confirm-no-live-process`;取得同书 advisory lock 后,在锁内带行锁重算精确快照,SHA 精确匹配才于同事务软删十个精确待删 ID,软删返回 ID 必须与预期集合完全一致。任何组数、行数、待删 ID 集合、observation、create_time、tenant、work 漂移,或二次运行(软删后每组只剩单行、不再构成十组)均失败关闭;不修改 draft、window、alias、card_state、audit、embedding,也不写审计行。 - -### legacy failed 窗恢复 `scripts/upgrade.py recover-legacy-failed` - -普通无 marker clean 的产物检查只豁免满足完整墓碑条件且 `updater` 为 `upgrade-reset` 或 `upgrade-undo` 的 alias/new_card/embedding;presence、card_state、audit 仍有行即阻断。下述 legacy 恢复只允许 `upgrade-reset`,两种上下文不得混用。 - -`recover-legacy-failed --preview/--execute` 是 fence 引入前 failed 窗的唯一恢复入口:preview 只读输出窗口、六类本窗产物计数、processing 数和确认 SHA;execute 需确认旧进程已结束、同书锁内重算并 exact 匹配,且无 processing、**阻断产物计数为 0**,才保持 `failed` 并标为 `retryable-clean`。计数只豁免能机械证明来源的全书 reset 墓碑:draft 必须同时 `deleted=true`、`updater='upgrade-reset'`、`status='pending'`;embedding 还必须 `entity_id IS NULL` 且自身 `deleted=true`;alias 必须 `deleted=true` 且 `updater='upgrade-reset'`。presence 没有 reset 来源标记,任何本窗行(包括软删墓碑)都阻断;其他软删、活跃行、已有 entity owner,以及任何 card_state/audit 行也都阻断。完整 `stateSha` 仍覆盖所有 deleted 行,二者不能混淆。不调用模型/嵌入。`--redo-window` 已禁用,历史修正走人工 backup/reset/rebuild。 - -## 同书命令互斥 - -`upgrade.py run/windows`、`reset_upgrade_work.py` 的预览/执行,以及 `backup_upgrade_work.py backup/rehearse/restore` 均须先取得 `scripts/upgrade_work_lock.py` 的同租户同作品 PostgreSQL session advisory lock;失败必须在任何业务 SQL、文件 verify/写入、嵌入或 LLM 调用前非零退出。锁由独立 autocommit 连接持有到命令结束,该连接只执行加锁/解锁 SQL;`status` 只读且不取锁。所有调用方必须导入同一个 `upgrade_work_lock(...)` context manager,禁止另造不兼容锁键。 - -## 升格 work reset 前备份(只读)与恢复合同 - -任何 `reset_upgrade_work.py --execute` 前,先用单个 `REPEATABLE READ READ ONLY` 事务导出该作品七域状态;目录必须是 `/private/tmp` 下尚不存在的新目录。input 摘要在同一快照中绑定不含密码的数据库 identity、Canonical 章节/block 内容、active 窗边界、升格七型 active 字段合同,以及计划参数 `MiniMax-M3 + semantic-dedup=true`;同时绑定本次实际执行的代码 fileSha——契约键(稳定逻辑名,拆分前后不变)为 `backup_upgrade_work.py`、`reset_upgrade_work.py`、`parse_upgrade.py`、`parse_llm.py`、`embed_drafts.py`、`upgrade_work_lock.py`、`llm.py` 和 `parse-book/SKILL.md`。其中逻辑名 `parse_upgrade.py` 对应的实际文件是拆分改名后的 `scripts/upgrade.py`,`parse_llm.py` 实际位于 parse-book skill(`../parse-book/scripts/parse_llm.py`),逻辑名 `parse-book/SKILL.md` 随本 skill 实际指向 `upgrade/SKILL.md`——键名稳定是为让存量备份永久可离线复验,路径只是定位手段。`gitCommit` 只记录 HEAD;当代码未提交时不得把它当执行代码身份,确认以 manifest 的 `inputSha + codeFiles + confirmationSha` 为准。SHA 仅用于发现传输、落盘或误操作造成的意外损坏,不宣称抵抗能同时改写工件和摘要的恶意篡改。命令默认不写数据库、不调用模型,生成后会关闭数据库连接并从磁盘独立复验;任一失败删除本次新建目录并非零退出。 - -reset preview 不要求备份且只读;execute 必须同时提供 `--backup-dir`、与 manifest 精确匹配的 `--backup-id` 和 `--confirmation-sha`。取得统一同书 advisory lock 后,execute 须在同一业务事务内以 `SHARE ROW EXCLUSIVE` 按固定顺序锁住 4 个输入源表 `muse_content_chapter→muse_content_block→muse_meta_schema→muse_meta_schema_version`,再锁 7 个产出表 `muse_knowledge_draft→example_upgrade_window→example_upgrade_alias→example_upgrade_presence→example_upgrade_card_state→example_upgrade_audit→example_knowledge_embedding`;必须在锁内重算完整 input,并复用 `backup_upgrade_work` 的七域读取与摘要规则,将当前完整 input 及七域 `rowCount/primaryKeySha/contentSha` 与已离线复验 manifest exact 比较;任一漂移必须在写入前失败关闭。 - -reset execute 还必须在同书 advisory lock 内、创建业务连接前重新调用 `capture_code_identity`:manifest `codeFiles` 必须包含 `reset_upgrade_work.py`,并与当前实际完整 `codeFiles` 逐项一致;reset/parse/embed/llm/skill 任一 fileSha 漂移或集合变化均拒绝。旧备份仍可离线 `verify`,但缺少 reset fileSha 时不得用于 execute。 - -目标 `upgrade_book` draft 的任一向量只要 `entity_id` 非空,execute 必须失败关闭;reset 只允许软删 `entity_id IS NULL` 的活向量,统一写入 `updater='upgrade-reset'`,禁止物删。向量范围不要求关联卡当前 active,以便幂等收口旧版 reset 遗留向量;其它作品、其它来源和其它租户不在边界内。提交前必须机械断言 active 升格卡为 0、active 窗全部 pending、alias/presence/state/audit 为 0、目标 draft 活向量为 0;任一失败回滚整个事务。七域备份/恢复合同不变。 - -### 备份/复验/恢复命令 - -```bash -# 创建 work 8 七域一致性备份;expected 值进入 input 摘要并机械核对 594 章/116 窗 -.venv/bin/python .claude/skills/upgrade/scripts/backup_upgrade_work.py backup --work-id 8 --expected-chapters 594 --expected-windows 116 -# 显式指定 /private/tmp 下的新目录 -.venv/bin/python .claude/skills/upgrade/scripts/backup_upgrade_work.py backup --work-id 8 --expected-chapters 594 --expected-windows 116 --output-dir /private/tmp/muse-upgrade-work8-before-reset -# 已有备份纯离线复验(不连接数据库、不调用模型) -.venv/bin/python .claude/skills/upgrade/scripts/backup_upgrade_work.py verify --backup-dir /private/tmp/muse-upgrade-work8-before-reset -# 在真实 public 表的单事务中应用七域恢复、逐域读回,最后强制 ROLLBACK;不调整 sequence -.venv/bin/python .claude/skills/upgrade/scripts/backup_upgrade_work.py rehearse --work-id 8 --backup-dir /private/tmp/muse-upgrade-work8-before-reset --confirmation-sha -# 演练通过并取得本次明确确认后才允许提交;backup-id 必须与 manifest 精确一致 -.venv/bin/python .claude/skills/upgrade/scripts/backup_upgrade_work.py restore --work-id 8 --backup-dir /private/tmp/muse-upgrade-work8-before-reset --execute --backup-id --confirmation-sha -``` - -backup 在同一只读快照内机械验证 active `window_no=1..expected-windows`、各窗章域无缝覆盖 Canonical 连续章域,并要求每章至少一个非空 block;任一不符不产备份。恢复前固定先离线 `verify`,核对 `confirmationSha`,并在事务内按 manifest 的 expected 值重算 input;work、tenant、数据库 identity 或 Canonical/窗/合同/代码任一漂移均拒绝。恢复内核按备份 ID exact upsert drafts/windows/embeddings;额外 active 窗直接拒绝,reset 后新增升格 draft 及其额外 embedding 只软删;aliases/presence/card_state/audits 按 work 边界先清后插。为避免 `updated_at` trigger 改写备份时间戳,恢复写段使用事务局部 replica trigger 模式,仍按 drafts→windows→aliases/presence→card_state/audits→embeddings 的父子顺序执行,事务结束自动复原。`rehearse` 永不调用非事务性的 sequence 调整并强制回滚;`restore --execute` 还必须同时精确匹配 `backup_id + confirmationSha`,逐域读回通过后才把 sequence 仅向前推进并提交。 - -## 升格全量重抽准备 `scripts/reset_upgrade_work.py` - -升格是串行有状态管线,直接重跑会被旧状态卡死(窗全 done 不再处理、判重底册装旧代卡、旧审计被撤销机制错还原、别名全行唯一索引吞新别名)。reset 清一本书的升格产出与串行状态:升格卡与活向量**软删**(知识数据绝不物删,可回滚),窗行保留仅重置 `status=pending`(from_chapter 锚幂等),别名/留档/卡水位/审计**硬删**该书行(约束所致:别名全行唯一索引软删会挡新 INSERT、审计 JOIN 不过滤 deleted 会错还原旧卡)。 - -```bash -# 预览(只读统计,不写库) -.venv/bin/python .claude/skills/upgrade/scripts/reset_upgrade_work.py --work-id 8 -# 真清(须先有 backup;锁内验备份,单事务全清或全不清) -.venv/bin/python .claude/skills/upgrade/scripts/reset_upgrade_work.py --work-id 8 --execute \ - --backup-dir /private/tmp/muse-upgrade-work8-before-reset \ - --backup-id --confirmation-sha -``` - -## 升格执行命令 - -```bash -# 机械切正文窗(幂等,from_chapter 锚) -.venv/bin/python .claude/skills/upgrade/scripts/upgrade.py windows --work-id 8 -# 按窗顺序跑,断点续跑(failed 窗自动撤销重跑;--semantic-dedup 开语义判重,默认关) -.venv/bin/python .claude/skills/upgrade/scripts/upgrade.py run --work-id 8 [--max-windows K] [--max-calls M] [--semantic-dedup] -# 进度(只读,不取锁) -.venv/bin/python .claude/skills/upgrade/scripts/upgrade.py status --work-id 8 -``` - -## 一次性迁移脚本(待确认存量处理完后删除) - -以下两只脚本是**一次性存量迁移**工具,非常设管线;存量是否处理完待创始人确认,确认后删除: - -- `scripts/parse_rewash.py`:存量回洗(追加字段前缀堆叠/[本窗]残留清洗+同文去重、关系卡顶层演变轨迹迁移、出场章全量机械重扫重建、脏别名清理、占位值清理)。 -- `scripts/migrate_upgrade_windows.py`:存量升格卡 `[窗N]`→真实章号迁移(演变类字段对象化为里程碑,只 `--dry-run` 零写库,落库由主代理 gate 后另行发起)。 - -## 跨 skill 只读核对 - -`parse-book` skill 的 `parse_health.py`(全链终态体检)与 `parse_export.py`(终态样张导出)会只读核对升格层(窗态/卡数/审计规模/结构垃圾红线、升格实体卡样张)。二者只读、不写升格状态;命令仍在 parse-book skill 下。 diff --git a/.claude/skills/continuation/SKILL.md b/.claude/skills/write-next-chapter/SKILL.md similarity index 74% rename from .claude/skills/continuation/SKILL.md rename to .claude/skills/write-next-chapter/SKILL.md index 02573ad..d1a7a83 100644 --- a/.claude/skills/continuation/SKILL.md +++ b/.claude/skills/write-next-chapter/SKILL.md @@ -1,6 +1,6 @@ --- -name: continuation -description: 续写整章的功能合同(scenario: continuation,写作槽位)。章结构/字数/伏笔纪律/输出契约的功能细节约束;writer 的身份纪律在 agent 文件,这里只管「续写这件事怎么做」。 +name: write-next-chapter +description: 按已确认细纲、冻结事实和动态篇幅合同生成下一整章候选。作品需要续写下一章且输入合同完整时使用;不自行检索、不改规划、不把候选直接写入 Canonical。 disable-model-invocation: true --- @@ -37,8 +37,9 @@ Writer 不接收 `runId`、权限信息、manifest、hash、候选版本、验 ## 生产落库 -- `run_writer_with_receipt()` 只负责可信 writer adapter;生产编排在组装后调用 `read-context/scripts/persist_context_freeze.py`,机械门之后调用 `scripts/persist_writer_run.py`。 -- `persist_writer_run.py` 要求本次 `run_id` 已有成功 writer 调用的 raw 指针,随后登记 `example_candidate`、追加 `example_run_receipt` 和 `example_quality_result`;它不接受正文,Shadow→Canonical 仍只能由 `confirm/write_canonical.py` 完成。 +- 生产编排走 `run_writer_pipeline`(机械门→语义 detector→补证/重写有限环),状态链用 `scripts/candidate_cas.py` 的 `PostgresCasStateStore` 持久化到 `example_candidate_cas`(一次运行一条链,revision 单调,DB 触发器锁方向闭集);内存 `InMemoryCasStateStore` 仅供离线测试。 +- `run_writer_with_receipt()` 只负责可信 writer adapter;生产编排在组装后调用 `assemble-context/scripts/persist_context_freeze.py`,审查落库调用 `scripts/persist_writer_run.py`。 +- `persist_writer_run.py` 要求本次 `run_id` 已有成功 writer 调用的 raw 指针,随后登记 `example_candidate`(传入 `semantic_report` 时校验绑定并把 `semantic_status`/`semantic_report_sha256` 固化到候选行)、追加 `example_run_receipt` 和机械/语义两行 `example_quality_result`;它不接受正文,Shadow→Canonical 仍只能由 `decide-candidate/scripts/write_canonical.py` 完成(接受通道 DB 级兜底复检 `state=passed` 且 `semantic_status=passed`)。 ## 输出合同 diff --git a/.claude/skills/write-next-chapter/scripts/candidate_cas.py b/.claude/skills/write-next-chapter/scripts/candidate_cas.py new file mode 100644 index 0000000..fd5232e --- /dev/null +++ b/.claude/skills/write-next-chapter/scripts/candidate_cas.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""候选 CAS 状态链的 PostgreSQL 持久化存储(生产用)。 + +实现 run_writer_pipeline 的 CasStateStore 协议,替换仅供实验的 InMemoryCasStateStore: +create/transition/start_next/latest 全部走短事务条件 UPDATE,竞争时返回 None(由编排层 +收敛为 CAS_CONFLICT),不再因进程退出丢失状态。DB 触发器(db/ddl/109)另行锁死迁移 +方向、revision 单调 +1 与身份不可变,调用方代码缺陷也无法把链改成非法形状。 +""" + +from __future__ import annotations + +import pathlib +import sys +from typing import Any, Callable + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +DB_DIR = SCRIPT_DIR.parents[1] / "access-database" / "scripts" +for path in (SCRIPT_DIR, DB_DIR): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from db import connect as _default_connect # noqa: E402 +from run_writer_pipeline import CasToken, PipelineError # noqa: E402 + + +class PostgresCasStateStore: + """生产运行的持久 CAS 状态存储。 + + 参数只影响 create 时登记的可观察归属(work_id/target_chapter)与审计 updater; + 迁移语义完全由 CasToken 期望值与 DB 约束决定。connect 可注入以便测试替换。 + """ + + # 与 InMemoryCasStateStore 同形的方向闭集:本地先行拒绝非法方向(返回 None, + # 与内存实现行为一致),DB 触发器仍是最终权威。 + _ALLOWED = { + "DRAFT": frozenset({"CHECKING"}), + "CHECKING": frozenset({"PASSED", "REJECTED"}), + "PASSED": frozenset(), + "REJECTED": frozenset(), + } + + def __init__( + self, + *, + work_id: int | None = None, + target_chapter: int | None = None, + creator: str = "continuation", + connect: Callable[..., Any] | None = None, + ) -> None: + self._work_id = work_id + self._target_chapter = target_chapter + self._creator = creator + self._connect = connect or _default_connect + + def create(self, run_id: str, *, attempt: int, candidate_version: int) -> CasToken: + """为未存在的 run 建链(DRAFT/revision=1);已存在即 CAS 冲突,失败关闭。""" + + with self._connect() as conn: + try: + cur = conn.execute( + "INSERT INTO example_candidate_cas(run_id, work_id, target_chapter, attempt, " + "candidate_version, state, revision, creator, updater) " + "VALUES (%s,%s,%s,%s,%s,'DRAFT',1,%s,%s) ON CONFLICT (run_id) DO NOTHING", + (run_id, self._work_id, self._target_chapter, attempt, candidate_version, + self._creator, self._creator), + ) + if cur.rowcount != 1: + raise PipelineError("CAS_CONFLICT", f"run 已存在 CAS 链,不能重复创建初始状态: {run_id}") + conn.commit() + except Exception: + conn.rollback() + raise + return CasToken(run_id, attempt, candidate_version, "DRAFT", 1) + + def transition(self, expected: CasToken, target_state: str) -> CasToken | None: + """仅当链上最新状态与期望 token 完全一致时迁移;竞争、已迁移或方向非法返回 None。""" + + if target_state not in self._ALLOWED.get(expected.state, frozenset()): + return None + with self._connect() as conn: + try: + cur = conn.execute( + "UPDATE example_candidate_cas SET state=%s, revision=revision+1, updater=%s " + "WHERE run_id=%s AND revision=%s AND state=%s AND attempt=%s " + "AND candidate_version=%s AND deleted=false", + (target_state, self._creator, expected.run_id, expected.revision, + expected.state, expected.attempt, expected.candidate_version), + ) + if cur.rowcount != 1: + return None + conn.commit() + except Exception: + conn.rollback() + raise + return CasToken( + expected.run_id, expected.attempt, expected.candidate_version, + target_state, expected.revision + 1, + ) + + def start_next( + self, expected: CasToken, *, attempt: int, candidate_version: int + ) -> CasToken | None: + """只允许从最新 REJECTED 开严格递增的新一轮 DRAFT;否则返回 None。""" + + if ( + expected.state != "REJECTED" + or attempt <= expected.attempt + or candidate_version <= expected.candidate_version + ): + return None + with self._connect() as conn: + try: + cur = conn.execute( + "UPDATE example_candidate_cas SET state='DRAFT', attempt=%s, candidate_version=%s, " + "revision=revision+1, updater=%s WHERE run_id=%s AND revision=%s AND state='REJECTED' " + "AND attempt=%s AND candidate_version=%s AND deleted=false", + (attempt, candidate_version, self._creator, expected.run_id, + expected.revision, expected.attempt, expected.candidate_version), + ) + if cur.rowcount != 1: + return None + conn.commit() + except Exception: + conn.rollback() + raise + return CasToken(expected.run_id, attempt, candidate_version, "DRAFT", expected.revision + 1) + + def latest(self, run_id: str) -> CasToken | None: + """回读链的最新不可变状态;不存在或已软删返回 None。""" + + with self._connect(readonly=True) as conn: + row = conn.execute( + "SELECT run_id, attempt, candidate_version, state, revision " + "FROM example_candidate_cas WHERE run_id=%s AND deleted=false", + (run_id,), + ).fetchone() + if row is None: + return None + return CasToken(row[0], row[1], row[2], row[3], row[4]) + + +__all__ = ["PostgresCasStateStore"] diff --git a/.claude/skills/continuation/scripts/persist_writer_run.py b/.claude/skills/write-next-chapter/scripts/persist_writer_run.py similarity index 66% rename from .claude/skills/continuation/scripts/persist_writer_run.py rename to .claude/skills/write-next-chapter/scripts/persist_writer_run.py index 2e4b7b5..b6d4bb2 100644 --- a/.claude/skills/continuation/scripts/persist_writer_run.py +++ b/.claude/skills/write-next-chapter/scripts/persist_writer_run.py @@ -13,10 +13,10 @@ from typing import Any, Mapping SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "read-context" / "scripts" -DB_DIR = SCRIPT_DIR.parents[1] / "db" / "scripts" -for path in (RUNTIME_DIR, READ_CONTEXT_DIR, DB_DIR): +EVIDENCE_DIR = SCRIPT_DIR.parents[1] / "record-run-evidence" / "scripts" +READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" +DB_DIR = SCRIPT_DIR.parents[1] / "access-database" / "scripts" +for path in (EVIDENCE_DIR, READ_CONTEXT_DIR, DB_DIR): if str(path) not in sys.path: sys.path.insert(0, str(path)) @@ -59,16 +59,57 @@ def _raw_response(conn, run_id): return row +_SEMANTIC_STATUSES = frozenset({"passed", "failed", "needs_evidence"}) + + +def _semantic_verdict(semantic_report, context, candidate_sha, candidate_version): + """校验语义报告绑定并派生候选语义状态;任何不一致失败关闭。 + + 返回 (semantic_status, semantic_report_sha256 裸哈希)。报告权威在检测适配器, + 这里只复核绑定与哈希自洽,防止把别的候选/运行的报告记到本候选头上。 + """ + + if not isinstance(semantic_report, dict): + raise WriterPersistenceError("语义报告必须是对象") + if semantic_report.get("schemaVersion") != "semantic-detection-v3": + raise WriterPersistenceError("语义报告版本非法,拒绝落库") + status = semantic_report.get("status") + if status not in _SEMANTIC_STATUSES: + raise WriterPersistenceError(f"语义报告 status 非法: {status!r}") + expected = { + "runId": context.get("runId"), + "candidateVersion": candidate_version, + "candidateSha256": "sha256:" + candidate_sha, + "contextSnapshotSha256": context["contextSnapshot"]["contextSha256"], + } + for field, value in expected.items(): + got = semantic_report.get(field) + # candidateVersion 在报告里可能是 int,candidate 里也可能是 str,统一成字符串比 + if str(got) != str(value): + raise WriterPersistenceError(f"语义报告未绑定当前候选: {field}") + report_sha = str(semantic_report.get("reportSha256") or "") + body = {key: value for key, value in semantic_report.items() if key != "reportSha256"} + recomputed = "sha256:" + hashlib.sha256(_json(body).encode("utf-8")).hexdigest() + if report_sha != recomputed: + raise WriterPersistenceError("语义报告哈希自洽校验失败") + return status, _bare_hash(report_sha, "semanticReportSha256") + + def persist_writer_execution( context: Mapping[str, Any], candidate: Mapping[str, Any], receipt: Any, mechanical_report: Mapping[str, Any], *, + semantic_report: Mapping[str, Any] | None = None, assemble_result: Mapping[str, Any] | None = None, dry_run: bool = False, ) -> dict[str, Any]: - """落库一次 writer Shadow 运行;失败不会创建半套候选账本。""" + """落库一次 writer Shadow 运行;失败不会创建半套候选账本。 + + semantic_report 非 None 时校验绑定并把语义状态固化到候选行(接受通道的 DB 兜底依据); + None 时候选 semantic_status 留空,write_canonical 一律不得接受(先审后入,失败关闭)。 + """ run_id = str(context.get("runId") or "") work_id = context.get("workId") @@ -86,6 +127,10 @@ def persist_writer_execution( passed = mechanical_report.get("passed") if not isinstance(passed, bool): raise WriterPersistenceError("机械门报告缺少布尔 passed") + semantic_status = semantic_sha = None + if semantic_report is not None: + semantic_status, semantic_sha = _semantic_verdict( + semantic_report, context, candidate_sha, candidate.get("candidateVersion")) if assemble_result is not None: persist_freeze(assemble_result, dry_run=dry_run) @@ -114,11 +159,12 @@ def persist_writer_execution( candidate_id = conn.execute( "INSERT INTO example_candidate(work_id,target_chapter,run_id,attempt,run_type," "candidate_version,candidate_sha256,candidate_body,context_sha256,quality_policy_version," - "mode,source_role,state,acceptance_eligible,creator) " + "mode,source_role,state,acceptance_eligible,semantic_status,semantic_report_sha256,creator) " "VALUES (%s,%s,%s,%s,'production',%s,%s,%s,%s,'writer-production-v1','continuation'," - "'writer',%s,TRUE,%s) RETURNING id", + "'writer',%s,TRUE,%s,%s,%s) RETURNING id", (work_id, target_chapter, run_id, candidate.get("attempt", 1), candidate_version, - candidate_sha, body, context_sha, "passed" if passed else "rejected", CREATOR), + candidate_sha, body, context_sha, "passed" if passed else "rejected", + semantic_status, semantic_sha, CREATOR), ).fetchone()[0] receipt_row = conn.execute( @@ -154,9 +200,25 @@ def persist_writer_execution( None if passed else "mechanical_gate_failed", _json(dict(mechanical_report)), raw_content_id, CREATOR), ).fetchone() + # 语义审查证据单独一行(dimension='semantic'),与机械门证据并列可审计 + semantic_quality_id = None + if semantic_report is not None: + semantic_quality_row = conn.execute( + "INSERT INTO example_quality_result(run_id,receipt_id,candidate_sha256,judge_kind," + "dimension,scale_version,conclusion,failure_class,detail,raw_content_id,creator,tenant_id) " + "VALUES (%s,%s,%s,'detection','semantic','semantic-detection-v3',%s,%s,%s::jsonb,%s,%s,0) " + "ON CONFLICT (tenant_id,run_id,judge_kind,COALESCE(dimension,''),COALESCE(candidate_sha256,'')) DO NOTHING RETURNING id", + (run_id, receipt_id, candidate_sha, + "pass" if semantic_status == "passed" else "fail", + None if semantic_status == "passed" else f"semantic_{semantic_status}", + _json(dict(semantic_report)), raw_content_id, CREATOR), + ).fetchone() + semantic_quality_id = semantic_quality_row[0] if semantic_quality_row else None result = {"status": "dry_run_ok" if dry_run else "persisted", "candidate_id": candidate_id, "receipt_id": receipt_id, "quality_id": quality_id[0] if quality_id else None, + "semantic_quality_id": semantic_quality_id, + "semantic_status": semantic_status, "raw_content_id": raw_content_id, "state": "passed" if passed else "rejected"} if dry_run: conn.rollback() diff --git a/.claude/skills/continuation/scripts/run_writer.py b/.claude/skills/write-next-chapter/scripts/run_writer.py similarity index 90% rename from .claude/skills/continuation/scripts/run_writer.py rename to .claude/skills/write-next-chapter/scripts/run_writer.py index 06b4d87..381bc2e 100644 --- a/.claude/skills/continuation/scripts/run_writer.py +++ b/.claude/skills/write-next-chapter/scripts/run_writer.py @@ -10,9 +10,9 @@ from decimal import Decimal, ROUND_HALF_UP from typing import Any, Callable, Mapping, Sequence SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "read-context" / "scripts" -RUNTIME_DIR = SCRIPT_DIR.parents[1] / "runtime" / "scripts" -for import_path in (READ_CONTEXT_DIR, RUNTIME_DIR): +READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" +EXECUTION_DIR = SCRIPT_DIR.parents[1] / "execute-claude-task" / "scripts" +for import_path in (READ_CONTEXT_DIR, EXECUTION_DIR): if str(import_path) not in sys.path: sys.path.insert(0, str(import_path)) @@ -215,8 +215,17 @@ def run_writer_with_receipt( candidate_version: int = 1, runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, binding_verifier: Callable[[ExecutionProfile], None] = verify_execution_profile, + run_id: str | None = None, + caller: str | None = None, + persist_call: Callable[[Mapping[str, Any]], Any] | None = None, ) -> tuple[dict[str, Any], ExecutionReceipt]: - """调用 writer,由可信 adapter 绑定候选并返回执行回执。""" + """调用 writer,由可信 adapter 绑定候选并返回执行回执。 + + 生产记账:run_id 默认取上下文的 runId。带着 run_id 走默认 runner 时,runtime 会把 + 本次模型调用的输入/输出原文和调用明细原子落库(example_raw_content / example_llm_call); + 候选记录器 persist_writer_execution 以这些调用明细为前置证据,缺了拒绝写候选。 + 离线 fake runner 不触发默认落库;需要自定义 runner 又要在库留证时,显式传 persist_call。 + """ try: normalized_context = validate_writer_context(context) @@ -234,8 +243,9 @@ def run_writer_with_receipt( runner=runner, binding_verifier=binding_verifier, business_validator=validate_writer_draft, - run_id=normalized_context.get("runId"), - caller="writer", + run_id=run_id or normalized_context.get("runId"), + caller=caller or "writer", + persist_call=persist_call, ) except ClaudeRuntimeError as exc: # runtime 只提供受控原因和回执;这里不拼接 subprocess stderr 或 stdout。 @@ -271,6 +281,9 @@ def run_writer( candidate_version: int = 1, runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, binding_verifier: Callable[[ExecutionProfile], None] = verify_execution_profile, + run_id: str | None = None, + caller: str | None = None, + persist_call: Callable[[Mapping[str, Any]], Any] | None = None, ) -> dict[str, Any]: """返回由可信 adapter 生成的 CandidateEnvelope v2。""" @@ -280,6 +293,9 @@ def run_writer( candidate_version=candidate_version, runner=runner, binding_verifier=binding_verifier, + run_id=run_id, + caller=caller, + persist_call=persist_call, ) return output diff --git a/.claude/skills/continuation/scripts/run_writer_pipeline.py b/.claude/skills/write-next-chapter/scripts/run_writer_pipeline.py similarity index 92% rename from .claude/skills/continuation/scripts/run_writer_pipeline.py rename to .claude/skills/write-next-chapter/scripts/run_writer_pipeline.py index 2a1355a..864867e 100644 --- a/.claude/skills/continuation/scripts/run_writer_pipeline.py +++ b/.claude/skills/write-next-chapter/scripts/run_writer_pipeline.py @@ -14,8 +14,8 @@ from typing import Any, Callable, Mapping, Protocol, Sequence SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -DETECT_DIR = SKILLS_DIR / "detect" / "scripts" -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" +DETECT_DIR = SKILLS_DIR / "check-content-consistency" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" for path in (DETECT_DIR, READ_CONTEXT_DIR): if str(path) not in sys.path: sys.path.insert(0, str(path)) @@ -393,15 +393,23 @@ def run_writer_pipeline( semantic_detector: SemanticDetector, state_store: CasStateStore, result_path: str | pathlib.Path | None = None, + initial_candidate_version: int = 1, ) -> dict[str, Any]: - """执行最多 3 次补证、2 次重写的正文候选有限收敛循环。""" + """执行最多 3 次补证、2 次重写的正文候选有限收敛循环。 + initial_candidate_version 给生产编排接续既有版本号:候选表对 + (作品, 章, candidate_version) 唯一,同一章重跑必须从「已有最大版本+1」起, + 否则与上一轮留库的被拒候选撞版本。 + """ + + if isinstance(initial_candidate_version, bool) or not isinstance(initial_candidate_version, int) or initial_candidate_version < 1: + raise PipelineError("PIPELINE_INPUT_INVALID", "initial_candidate_version 必须是 >=1 的整数") try: current_context = validate_writer_context(context) except ContractError as exc: raise PipelineError("WRITER_CONTEXT_INVALID", str(exc)) from exc run_id = current_context["runId"] - candidate_version = 1 + candidate_version = initial_candidate_version evidence_request_count = 0 rewrite_count = 0 trace: list[dict[str, Any]] = [] @@ -554,6 +562,19 @@ def run_writer_pipeline( ) candidate_failures.extend(semantic_failures) except PipelineError as exc: + # 语义阶段终态失败也保留机械门审查痕迹,审计链不在 detector 失败处断裂。 + trace.append( + { + "attempt": checking.attempt, + "candidateVersion": checking.candidate_version, + "candidateSha256": candidate.get("candidateSha256"), + "mechanicalPassed": mechanical_report.get("passed", False), + "semanticStatus": "not_run", + "failureCodes": [exc.code], + "mechanicalReport": dict(mechanical_report), + "semanticReport": None, + } + ) raise _terminal_failure( code=exc.code, message=str(exc), @@ -568,6 +589,18 @@ def run_writer_pipeline( details=exc.details, ) from exc except Exception as exc: + trace.append( + { + "attempt": checking.attempt, + "candidateVersion": checking.candidate_version, + "candidateSha256": candidate.get("candidateSha256"), + "mechanicalPassed": mechanical_report.get("passed", False), + "semanticStatus": "not_run", + "failureCodes": ["SEMANTIC_DETECTOR_FAILED"], + "mechanicalReport": dict(mechanical_report), + "semanticReport": None, + } + ) raise _terminal_failure( code="SEMANTIC_DETECTOR_FAILED", message="语义 detector 调用失败", @@ -654,12 +687,15 @@ def run_writer_pipeline( ) rewrite_count += 1 candidate_version += 1 + # 补证环的审计痕迹同样携带机械门报告,被拒候选留库不因走补证路径而丢失证据。 trace.append({ "attempt": checking.attempt, "candidateVersion": checking.candidate_version, "candidateSha256": candidate["candidateSha256"], "status": "evidence_gap", "gapIds": [item["gapId"] for item in evidence_gaps], + "mechanicalPassed": mechanical_report.get("passed", False), + "mechanicalReport": dict(mechanical_report), "semanticReport": dict(semantic_report), }) current_context = next_context diff --git a/.claude/skills/write-next-chapter/scripts/test_candidate_cas.py b/.claude/skills/write-next-chapter/scripts/test_candidate_cas.py new file mode 100644 index 0000000..a161509 --- /dev/null +++ b/.claude/skills/write-next-chapter/scripts/test_candidate_cas.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +"""PostgresCasStateStore 的离线测试。 + +用按条件 WHERE 语义模拟 example_candidate_cas 行为的假连接,验证: +- CAS 竞争(旧 token / 重复建链)失败关闭; +- transition/start_next 的守卫与 token 递增; +- 该存储可直接替换 InMemoryCasStateStore 驱动 run_writer_pipeline 全链。 + +跑法(仓库根目录):.venv/bin/python .claude/skills/write-next-chapter/scripts/test_candidate_cas.py +""" + +from __future__ import annotations + +import copy +import pathlib +import sys +import unittest + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +DETECT_DIR = SKILLS_DIR / "check-content-consistency" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" +for path in (SCRIPT_DIR, DETECT_DIR, READ_CONTEXT_DIR): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from candidate_cas import PostgresCasStateStore # noqa: E402 +from run_writer_pipeline import CasToken, PipelineError, run_writer_pipeline # noqa: E402 +from run_writer_semantic_detector import canonical_sha256 # noqa: E402 +from test_check_writer_candidate import _candidate_body, _requirements, _valid_pair # noqa: E402 +from test_run_writer_pipeline import _semantic_pass, _writer_output # noqa: E402 +from writer_contract import build_candidate_envelope, retrieval_identity # noqa: E402 + + +class FakeCursor: + """携带 rowcount 的最小游标。""" + + def __init__(self, rowcount: int = 0, row: tuple | None = None) -> None: + self.rowcount = rowcount + self._row = row + + def fetchone(self): + return self._row + + +class FakeCasConnection: + """按条件 WHERE 语义模拟 example_candidate_cas 的短事务连接。 + + 只模拟条件匹配的「行计数」事实(匹配则更新并计数 1,不匹配计数 0), + 不重复实现 DB 触发器的迁移方向约束——那是 DB 集成测试的职责。 + """ + + def __init__(self, rows: dict) -> None: + self._rows = rows + self._snapshot = copy.deepcopy(rows) + + def __enter__(self): + return self + + def __exit__(self, *_): + return False + + def commit(self) -> None: + self._snapshot = copy.deepcopy(self._rows) + + def rollback(self) -> None: + self._rows = copy.deepcopy(self._snapshot) + + def execute(self, sql: str, params=()): + text = " ".join(sql.split()) + if text.startswith("INSERT INTO example_candidate_cas"): + run_id, work_id, chapter, attempt, version, creator, updater = params + if run_id in self._rows: + return FakeCursor(rowcount=0) + self._rows[run_id] = { + "work_id": work_id, "target_chapter": chapter, "attempt": attempt, + "candidate_version": version, "state": "DRAFT", "revision": 1, + "deleted": False, + } + return FakeCursor(rowcount=1) + if text.startswith("UPDATE example_candidate_cas SET state=%s"): + state, updater, run_id, revision, old_state, attempt, version = params + row = self._rows.get(run_id) + if not self._match(row, run_id, revision, old_state, attempt, version): + return FakeCursor(rowcount=0) + row["state"] = state + row["revision"] += 1 + return FakeCursor(rowcount=1) + if text.startswith("UPDATE example_candidate_cas SET state='DRAFT'"): + attempt, version, updater, run_id, revision, old_attempt, old_version = params + row = self._rows.get(run_id) + if not self._match(row, run_id, revision, "REJECTED", old_attempt, old_version): + return FakeCursor(rowcount=0) + row["state"] = "DRAFT" + row["attempt"] = attempt + row["candidate_version"] = version + row["revision"] += 1 + return FakeCursor(rowcount=1) + if text.startswith("SELECT run_id, attempt, candidate_version, state, revision"): + (run_id,) = params + row = self._rows.get(run_id) + if row is None or row["deleted"]: + return FakeCursor(row=None) + return FakeCursor(row=(run_id, row["attempt"], row["candidate_version"], + row["state"], row["revision"])) + raise AssertionError(f"未预期的 SQL: {text}") + + @staticmethod + def _match(row, run_id, revision, state, attempt, version) -> bool: + return ( + row is not None + and not row["deleted"] + and row["revision"] == revision + and row["state"] == state + and row["attempt"] == attempt + and row["candidate_version"] == version + ) + + +def _store(rows: dict, **kwargs) -> PostgresCasStateStore: + """注入共享行表与假连接的被测存储。""" + + return PostgresCasStateStore(connect=lambda **_kw: FakeCasConnection(rows), **kwargs) + + +class PostgresCasStateStoreTest(unittest.TestCase): + def test_create_then_latest_returns_draft_token(self) -> None: + rows: dict = {} + store = _store(rows) + token = store.create("run-1", attempt=1, candidate_version=1) + self.assertEqual(token, CasToken("run-1", 1, 1, "DRAFT", 1)) + self.assertEqual(store.latest("run-1"), token) + + def test_create_twice_same_run_fails_closed(self) -> None: + rows: dict = {} + store = _store(rows) + store.create("run-1", attempt=1, candidate_version=1) + with self.assertRaises(PipelineError) as caught: + store.create("run-1", attempt=1, candidate_version=1) + self.assertEqual(caught.exception.code, "CAS_CONFLICT") + + def test_transition_requires_exact_expected_token(self) -> None: + rows: dict = {} + store = _store(rows) + draft = store.create("run-1", attempt=1, candidate_version=1) + checking = store.transition(draft, "CHECKING") + self.assertEqual(checking, CasToken("run-1", 1, 1, "CHECKING", 2)) + # 旧 token 重放:revision 已前进,条件不命中,返回 None + self.assertIsNone(store.transition(draft, "CHECKING")) + # 非法方向同样不命中(fake 只模拟条件匹配) + self.assertIsNone(store.transition(checking, "DRAFT")) + + def test_start_next_guards_monotonic_identity(self) -> None: + rows: dict = {} + store = _store(rows) + draft = store.create("run-1", attempt=1, candidate_version=1) + checking = store.transition(draft, "CHECKING") + rejected = store.transition(checking, "REJECTED") + # 非递增的 attempt / candidate_version 一律拒绝 + self.assertIsNone(store.start_next(rejected, attempt=1, candidate_version=2)) + self.assertIsNone(store.start_next(rejected, attempt=2, candidate_version=1)) + self.assertIsNone(store.start_next(checking, attempt=2, candidate_version=2)) + next_draft = store.start_next(rejected, attempt=2, candidate_version=2) + self.assertEqual(next_draft, CasToken("run-1", 2, 2, "DRAFT", 4)) + # 旧 REJECTED token 重放失败 + self.assertIsNone(store.start_next(rejected, attempt=3, candidate_version=3)) + + def test_latest_missing_run_returns_none(self) -> None: + self.assertIsNone(_store({}).latest("no-such-run")) + + +def _advance_context(context: dict, attempt: int) -> dict: + advanced = copy.deepcopy(context) + advanced["attempt"] = attempt + advanced["factEvidence"].append({ + "evidenceId": f"fact-gap-{attempt}", + "fact": "补充证据", + "sourceType": "canonical_state", + "sourceRef": {"sourceId": "state:gap", "sourceVersion": "v1"}, + "contentSha256": "sha256:" + "4" * 64, + "riskLevel": "low", + }) + advanced["contextSnapshot"]["contextSha256"] = retrieval_identity(advanced) + return advanced + + +class PipelineOnPostgresStoreTest(unittest.TestCase): + """用持久存储驱动完整 pipeline,证明协议兼容与补证环行为不变。""" + + def test_full_pipeline_passes_with_postgres_store(self) -> None: + context, _ = _valid_pair() + rows: dict = {} + result = run_writer_pipeline( + context=context, + requirements=_requirements(), + writer=lambda current, version: _writer_output(current, version), + evidence_provider=lambda *_args: self.fail("无 gap 不得补证"), + semantic_detector=_semantic_pass, + state_store=_store(rows), + ) + self.assertEqual(result["status"], "PASSED") + self.assertEqual(result["candidateArtifact"]["schemaVersion"], "candidate-envelope-v2") + chain = rows[context["runId"]] + self.assertEqual(chain["state"], "PASSED") + self.assertEqual(chain["revision"], 3) # create→CHECKING→PASSED + + def test_pipeline_evidence_loop_uses_start_next(self) -> None: + context, _ = _valid_pair() + rows: dict = {} + + def detector(current: dict, candidate: dict, _mechanical: dict) -> dict: + if candidate["candidateVersion"] == 1: + report = { + "schemaVersion": "semantic-detection-v3", + "runId": current["runId"], "sampleId": "cas-sample", + "opaqueArmId": "cas-candidate", "inputSha256": "sha256:" + "1" * 64, + "candidateVersion": candidate["candidateVersion"], + "candidateSha256": candidate["candidateSha256"], + "contextSnapshotSha256": current["contextSnapshot"]["contextSha256"], + "modelReceiptSha256": "sha256:" + "2" * 64, "status": "needs_evidence", + "claims": [], "findings": [], "assertionVerdicts": [], + "hardConstraintVerdicts": [], "newSettingCandidates": [], + "evidenceGaps": [{ + "gapId": "gap-1", "query": "补证", "reason": "缺口", "priority": "high", + "candidateSha256": candidate["candidateSha256"], + "candidateQuote": "旧徽章", "startCodePoint": 11, "endCodePoint": 14, + }], + } + report["reportSha256"] = canonical_sha256(report) + return report + return _semantic_pass(current, candidate, _mechanical) + + result = run_writer_pipeline( + context=context, + requirements=_requirements(), + writer=lambda current, version: _writer_output(current, version), + evidence_provider=lambda current, _gaps, attempt: _advance_context(current, attempt), + semantic_detector=detector, + state_store=_store(rows), + ) + self.assertEqual(result["status"], "PASSED") + chain = rows[context["runId"]] + # create→CHECKING→REJECTED→新DRAFT→CHECKING→PASSED + self.assertEqual((chain["state"], chain["revision"], chain["attempt"], + chain["candidate_version"]), ("PASSED", 6, 2, 2)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.claude/skills/write-next-chapter/scripts/test_candidate_cas_db.py b/.claude/skills/write-next-chapter/scripts/test_candidate_cas_db.py new file mode 100644 index 0000000..cff61b4 --- /dev/null +++ b/.claude/skills/write-next-chapter/scripts/test_candidate_cas_db.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""PostgresCasStateStore 对 muse-example 真实库的集成测试。 + +验证离线 fake 无法覆盖的部分:DB 触发器的迁移方向闭集、revision 恰好 +1、 +PASSED 终态、身份不可变,以及条件 UPDATE 在真实并发语义下的行计数。 +测试行用 unittest-cas- 前缀的 run_id,结束前物理清理(本表是可变注册表,无 append-only 约束)。 + +跑法(需 Tailscale 内网可达 muse-example): +.venv/bin/python .claude/skills/write-next-chapter/scripts/test_candidate_cas_db.py +""" + +from __future__ import annotations + +import pathlib +import sys +import uuid + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +SKILLS_DIR = SCRIPT_DIR.parents[1] +for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +from candidate_cas import PostgresCasStateStore # noqa: E402 +from db import connect # noqa: E402 + +PREFIX = f"unittest-cas-{uuid.uuid4().hex[:8]}-" +_run_ids: list[str] = [] + + +def _cleanup() -> None: + if not _run_ids: + return + with connect() as conn: + for run_id in _run_ids: + conn.execute("DELETE FROM example_candidate_cas WHERE run_id=%s", (run_id,)) + conn.commit() + + +def _new_run_id(tag: str) -> str: + run_id = f"{PREFIX}{tag}" + _run_ids.append(run_id) + return run_id + + +def test_full_lifecycle_and_contention() -> None: + store = PostgresCasStateStore(work_id=0, target_chapter=None, creator="unittest") + run_id = _new_run_id("lifecycle") + draft = store.create(run_id, attempt=1, candidate_version=1) + assert (draft.state, draft.revision) == ("DRAFT", 1) + # 重复建链失败关闭 + try: + store.create(run_id, attempt=1, candidate_version=1) + raise AssertionError("重复建链必须失败") + except Exception as exc: + assert getattr(exc, "code", None) == "CAS_CONFLICT", exc + checking = store.transition(draft, "CHECKING") + assert checking is not None and checking.revision == 2 + # 旧 token 重放不命中 + assert store.transition(draft, "CHECKING") is None + rejected = store.transition(checking, "REJECTED") + assert rejected is not None and rejected.state == "REJECTED" + # start_next 守卫:非递增身份拒绝 + assert store.start_next(rejected, attempt=1, candidate_version=2) is None + assert store.start_next(rejected, attempt=2, candidate_version=1) is None + second = store.start_next(rejected, attempt=2, candidate_version=2) + assert second is not None and (second.state, second.revision) == ("DRAFT", 4) + checking2 = store.transition(second, "CHECKING") + passed = store.transition(checking2, "PASSED") + assert passed is not None and passed.state == "PASSED" + assert store.latest(run_id) == passed + + +def test_passed_is_terminal() -> None: + store = PostgresCasStateStore(creator="unittest") + run_id = _new_run_id("terminal") + draft = store.create(run_id, attempt=1, candidate_version=1) + checking = store.transition(draft, "CHECKING") + passed = store.transition(checking, "PASSED") + # 存储层对 PASSED 出发的迁移方向闭集拒绝(返回 None,不下探到库) + assert store.transition(passed, "REJECTED") is None + assert store.transition(passed, "CHECKING") is None + assert store.start_next(passed, attempt=2, candidate_version=2) is None + assert store.latest(run_id).state == "PASSED" + + +def test_db_trigger_rejects_malformed_direct_updates() -> None: + store = PostgresCasStateStore(creator="unittest") + run_id = _new_run_id("trigger") + store.create(run_id, attempt=1, candidate_version=1) + # 绕过存储直接写非法形状,触发器必须拒绝 + bad_updates = ( + # revision 跳号 + "UPDATE example_candidate_cas SET state='CHECKING', revision=revision+2 WHERE run_id=%s", + # DRAFT 直接到 PASSED + "UPDATE example_candidate_cas SET state='PASSED', revision=revision+1 WHERE run_id=%s", + # 同轮迁移改身份 + "UPDATE example_candidate_cas SET attempt=9, revision=revision+1 WHERE run_id=%s", + # run_id 身份漂移 + "UPDATE example_candidate_cas SET run_id=run_id || 'x' WHERE run_id=%s", + ) + for sql in bad_updates: + try: + with connect() as conn: + conn.execute(sql, (run_id,)) + conn.commit() + raise AssertionError(f"触发器必须拒绝: {sql}") + except AssertionError: + raise + except Exception: + pass + latest = store.latest(run_id) + assert (latest.state, latest.revision) == ("DRAFT", 1), "非法 UPDATE 不得改变链" + + +def test_start_next_monotonic_at_db_level() -> None: + store = PostgresCasStateStore(creator="unittest") + run_id = _new_run_id("startnext") + draft = store.create(run_id, attempt=1, candidate_version=1) + checking = store.transition(draft, "CHECKING") + rejected = store.transition(checking, "REJECTED") + assert rejected is not None and rejected.state == "REJECTED" + # 绕过存储直接开新轮但不递增 attempt/candidate_version,触发器必须拒绝 + try: + with connect() as conn: + conn.execute( + "UPDATE example_candidate_cas SET state='DRAFT', revision=revision+1 " + "WHERE run_id=%s AND state='REJECTED'", (run_id,)) + conn.commit() + raise AssertionError("REJECTED 开新轮必须递增 attempt/candidate_version") + except AssertionError: + raise + except Exception: + pass + assert store.latest(run_id).state == "REJECTED", "非法开新轮不得改变链" + + +def test_insert_must_start_draft_revision_one() -> None: + run_id = _new_run_id("insert-guard") + try: + with connect() as conn: + conn.execute( + "INSERT INTO example_candidate_cas(run_id, attempt, candidate_version, state, revision, creator) " + "VALUES (%s,1,1,'CHECKING',1,'unittest')", (run_id,)) + conn.commit() + raise AssertionError("初始行必须是 DRAFT") + except AssertionError: + raise + except Exception: + pass + try: + with connect() as conn: + conn.execute( + "INSERT INTO example_candidate_cas(run_id, attempt, candidate_version, state, revision, creator) " + "VALUES (%s,1,1,'DRAFT',5,'unittest')", (run_id,)) + conn.commit() + raise AssertionError("初始 revision 必须是 1") + except AssertionError: + raise + except Exception: + pass + + +def main() -> None: + tests = ( + test_full_lifecycle_and_contention, + test_passed_is_terminal, + test_db_trigger_rejects_malformed_direct_updates, + test_start_next_monotonic_at_db_level, + test_insert_must_start_draft_revision_one, + ) + try: + for test in tests: + test() + print(f"PASS: {test.__name__}") + print("PASS:PostgresCasStateStore 真实库集成测试全部通过") + finally: + _cleanup() + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/continuation/scripts/test_persist_writer_run.py b/.claude/skills/write-next-chapter/scripts/test_persist_writer_run.py similarity index 100% rename from .claude/skills/continuation/scripts/test_persist_writer_run.py rename to .claude/skills/write-next-chapter/scripts/test_persist_writer_run.py diff --git a/.claude/skills/continuation/scripts/test_run_writer.py b/.claude/skills/write-next-chapter/scripts/test_run_writer.py similarity index 92% rename from .claude/skills/continuation/scripts/test_run_writer.py rename to .claude/skills/write-next-chapter/scripts/test_run_writer.py index 73841f0..5a05a46 100644 --- a/.claude/skills/continuation/scripts/test_run_writer.py +++ b/.claude/skills/write-next-chapter/scripts/test_run_writer.py @@ -13,7 +13,7 @@ import unittest from decimal import Decimal SCRIPT_DIR = pathlib.Path(__file__).resolve().parent -READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "read-context" / "scripts" +READ_CONTEXT_DIR = SCRIPT_DIR.parents[1] / "assemble-context" / "scripts" sys.path.insert(0, str(SCRIPT_DIR)) sys.path.insert(0, str(READ_CONTEXT_DIR)) @@ -275,6 +275,28 @@ class RunWriterTest(unittest.TestCase): self.assertEqual(raised.exception.code, "WRITER_PROFILE_REQUIRED") + def test_run_identity_and_persistence_forward_to_runtime(self): + """生产记账:运行号默认随上下文,调用方与存证入口显式转发给 runtime。""" + + context = _bound_context() + runner = _success_runner(context) + events = [] + result = run_writer( + context, + profile=_profile(), + runner=runner, + binding_verifier=lambda _profile: None, + persist_call=events.append, + ) + self.assertIn("candidateBody", result) + self.assertEqual(len(events), 1) + event = events[0] + self.assertEqual(event["run_id"], context["runId"]) + self.assertEqual(event["caller"], "writer") + self.assertEqual(event["purpose"], "production_generation") + self.assertIn("businessInput", event["prompt"]) + self.assertIn("candidateBody", event["response"]) + def test_result_field_is_never_business_fallback(self): """structured_output 缺失时,即使 result 含合法 WriterDraft 也必须拒绝。""" diff --git a/.claude/skills/continuation/scripts/test_run_writer_pipeline.py b/.claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py similarity index 85% rename from .claude/skills/continuation/scripts/test_run_writer_pipeline.py rename to .claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py index c6f3c3a..8f3d432 100644 --- a/.claude/skills/continuation/scripts/test_run_writer_pipeline.py +++ b/.claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py @@ -14,8 +14,8 @@ from unittest import mock SCRIPT_DIR = pathlib.Path(__file__).resolve().parent SKILLS_DIR = SCRIPT_DIR.parents[1] -DETECT_DIR = SKILLS_DIR / "detect" / "scripts" -READ_CONTEXT_DIR = SKILLS_DIR / "read-context" / "scripts" +DETECT_DIR = SKILLS_DIR / "check-content-consistency" / "scripts" +READ_CONTEXT_DIR = SKILLS_DIR / "assemble-context" / "scripts" for path in (SCRIPT_DIR, DETECT_DIR, READ_CONTEXT_DIR): sys.path.insert(0, str(path)) @@ -245,6 +245,45 @@ class RunWriterPipelineV3Test(unittest.TestCase): atomic_write_json(target, {"status": "PASSED"}) self.assertLess(events.index("fsync"), events.index("replace")) + def test_initial_candidate_version_offsets_same_chapter_rerun(self) -> None: + """同章重跑从既有最大版本接续,避免与留库被拒候选撞版本。""" + + context, _ = _valid_pair() + seen_versions: list[int] = [] + + def writer(current: dict, version: int) -> dict: + seen_versions.append(version) + return _writer_output(current, version) + + result = run_writer_pipeline( + context=context, + requirements=_requirements(), + writer=writer, + evidence_provider=lambda *_args: self.fail("无 gap 不得补证"), + semantic_detector=_semantic_pass, + state_store=InMemoryCasStateStore(), + initial_candidate_version=4, + ) + self.assertEqual(result["status"], "PASSED") + self.assertEqual(seen_versions, [4]) + self.assertEqual(result["candidateVersion"], 4) + self.assertEqual(result["candidateArtifact"]["candidateVersion"], 4) + + def test_invalid_initial_candidate_version_fails_closed(self) -> None: + context, _ = _valid_pair() + for bad in (0, -1, True, "1"): + with self.subTest(bad=bad), self.assertRaises(PipelineError) as caught: + run_writer_pipeline( + context=context, + requirements=_requirements(), + writer=lambda current, version: _writer_output(current, version), + evidence_provider=lambda *_args: self.fail("不得补证"), + semantic_detector=_semantic_pass, + state_store=InMemoryCasStateStore(), + initial_candidate_version=bad, + ) + self.assertEqual(caught.exception.code, "PIPELINE_INPUT_INVALID") + if __name__ == "__main__": unittest.main() diff --git a/.claude/skills/write-next-chapter/scripts/test_semantic_verdict.py b/.claude/skills/write-next-chapter/scripts/test_semantic_verdict.py new file mode 100644 index 0000000..89a737f --- /dev/null +++ b/.claude/skills/write-next-chapter/scripts/test_semantic_verdict.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""persist_writer_run._semantic_verdict 的离线测试。 + +语义状态固化到候选行是接受通道 DB 兜底的依据,绑定/哈希校验必须失败关闭: +报告版本、候选哈希、候选版本、上下文哈希、运行 ID 任一不一致都拒绝落库。 + +跑法:.venv/bin/python .claude/skills/write-next-chapter/scripts/test_semantic_verdict.py +""" + +from __future__ import annotations + +import copy +import pathlib +import sys +import unittest + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +if str(SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPT_DIR)) + +import persist_writer_run as writer_persist # noqa: E402 + + +CANDIDATE_SHA = "a" * 64 +CONTEXT_SHA = "sha256:" + "b" * 64 + + +def _context() -> dict: + return { + "runId": "run-prod-work12-ch2-x", + "contextSnapshot": {"contextSha256": CONTEXT_SHA}, + } + + +def _report(*, status: str = "passed", candidate_sha: str = "sha256:" + CANDIDATE_SHA, + candidate_version=1) -> dict: + body = { + "schemaVersion": "semantic-detection-v3", + "runId": "run-prod-work12-ch2-x", + "sampleId": "writer-ch2", + "opaqueArmId": "production", + "inputSha256": "sha256:" + "1" * 64, + "candidateVersion": candidate_version, + "candidateSha256": candidate_sha, + "contextSnapshotSha256": CONTEXT_SHA, + "modelReceiptSha256": "sha256:" + "2" * 64, + "status": status, + "claims": [], "findings": [], "assertionVerdicts": [], + "hardConstraintVerdicts": [], "newSettingCandidates": [], "evidenceGaps": [], + } + body["reportSha256"] = "sha256:" + writer_persist.hashlib.sha256( + writer_persist._json(body).encode("utf-8")).hexdigest() + return body + + +class SemanticVerdictTest(unittest.TestCase): + def test_bound_passed_report_yields_status_and_bare_sha(self) -> None: + report = _report() + status, sha = writer_persist._semantic_verdict( + report, _context(), CANDIDATE_SHA, 1) + self.assertEqual(status, "passed") + self.assertEqual(sha, report["reportSha256"][len("sha256:"):]) + + def test_failed_and_needs_evidence_statuses_pass_through(self) -> None: + for status in ("failed", "needs_evidence"): + report = _report(status=status) + got, _ = writer_persist._semantic_verdict(report, _context(), CANDIDATE_SHA, 1) + self.assertEqual(got, status) + + def test_unbound_candidate_sha_rejected(self) -> None: + report = _report(candidate_sha="sha256:" + "f" * 64) + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(report, _context(), CANDIDATE_SHA, 1) + + def test_unbound_candidate_version_rejected(self) -> None: + report = _report(candidate_version=2) + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(report, _context(), CANDIDATE_SHA, 1) + + def test_unbound_context_sha_rejected(self) -> None: + report = _report() + report["contextSnapshotSha256"] = "sha256:" + "c" * 64 + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(report, _context(), CANDIDATE_SHA, 1) + + def test_tampered_report_hash_rejected(self) -> None: + report = _report() + tampered = copy.deepcopy(report) + tampered["status"] = "passed" + tampered["reportSha256"] = report["reportSha256"] + tampered["claims"] = [{"claimId": "forged"}] # 改内容不改哈希 + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(tampered, _context(), CANDIDATE_SHA, 1) + + def test_bad_version_or_status_rejected(self) -> None: + bad_version = _report() + bad_version["schemaVersion"] = "semantic-detection-v2" + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(bad_version, _context(), CANDIDATE_SHA, 1) + bad_status = _report() + bad_status["status"] = "approved" + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(bad_status, _context(), CANDIDATE_SHA, 1) + with self.assertRaises(writer_persist.WriterPersistenceError): + writer_persist._semantic_verdict(None, _context(), CANDIDATE_SHA, 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/.gitignore b/.gitignore index a9652c8..e4e4400 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ works/*/评审/ __pycache__/ # IDE 本地配置不入库 .idea/ +# 运行 Skill 产生的 Python 缓存不属于源码 +.claude/skills/**/__pycache__/ diff --git a/AGENTS.md b/AGENTS.md index 9c15100..aa5aa47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,11 +58,13 @@ agent-example/ 技能按能力分组如下,实际清单以 `.claude/skills/*/SKILL.md` 为准,不手填数量镜像: -- 数据、运行与上下文:`db`、`import`、`embed`、`search`、`llm`、`runtime`、`snapshot`、`read-context`。 -- 流程与主权治理:`confirm`。 -- 清洗、拆解与知识:`clean`、`parse-book`、`extract-knowledge`、`review-cards`、`upgrade`。 -- 规划与写作:`planning`、`fine-outline`、`continuation`、`rewrite`、`expansion`、`polish`。 -- 检测与质量评测:`detect`、`quality-gate`、`eval`、`replay-eval`。 +- 数据与检索:`access-database`、`import-book`、`embed-knowledge`、`search-knowledge`、`call-content-model`。 +- 执行、证据与上下文:`execute-claude-task`、`record-run-evidence`、`freeze-context`、`assemble-context`。 +- 流程与主权治理:`decide-candidate`。 +- 清洗、拆解与知识:`clean-book-text`、`deconstruct-book`、`extract-chapter-knowledge`、`review-knowledge-cards`、`extract-work-knowledge`、`maintain-work-extraction`。 +- 质量证据回填:`capture-ai-flavor-cases`。 +- 规划与写作:`design-story-foundation`、`plan-story`、`plan-chapter`、`write-next-chapter`、`rewrite-selection`、`expand-scene`、`polish-prose`。 +- 检测与质量评测:`check-content-consistency`、`score-content-quality`、`optimize-content-quality`、`evaluate-frozen-replay`。 上下文目标合同以 [上下文领域 SoT](.agent/docs/architecture/domains/04-上下文领域.md) 为准:数据库读取器是核心实现,库内检索加速(向量)只做候选召回;任何命中都要回读库行并校验 hash。Skill 对自己读写哪些表负责,并把经手的输入和产出落库;没落库的输入产出在系统视角里等于不存在。 @@ -92,8 +94,8 @@ agent-example/ ## 6. 模型边界 -- 清洗、抽卡、范式拆取及其模型调用统一走 `llm` skill,不裸调 New-API。治理政策固定为 5 小时额度窗:MiniMax 模型累计花费上限 `$24`,全模型成功调用上限 `6000`;机械事实源是 `.claude/skills/llm/scripts/llm.py` 及 `test_quota.py`,模型链切换必须由该 skill 治理并留下日志。 -- 角色模型归属:`planner`/`writer`/`judge` 固定 `opus`;`extractor`/`detector` 可用其它模型(非必须降级)。拆书/导入侧抽取经 `llm`/`parse-book` skill 走 MiniMax-M3,不走角色 model 派发;创作期章后抽取作为角色派发,可用 `opus`。 +- 清洗、抽卡、范式拆取及其模型调用统一走 `call-content-model` Skill,不裸调 New-API。治理政策固定为 5 小时额度窗:MiniMax 模型累计花费上限 `$24`,全模型成功调用上限 `6000`;机械事实源是 `.claude/skills/call-content-model/scripts/llm.py` 及 `test_quota.py`,模型链切换必须由该 Skill 治理并留下日志。 +- 角色模型归属:`planner`/`writer`/`judge` 固定 `opus`;`extractor`/`detector` 可用其它模型(非必须降级)。拆书/导入侧抽取经 `call-content-model`/`deconstruct-book` Skill 走 MiniMax-M3,不走角色 model 派发;创作期章后抽取作为角色派发,可用 `opus`。 - 确定性脚本、合同校验、快照冻结、泄漏审计和报告生成不调用模型;除非对应 `SKILL.md` 明确声明模型步骤,不得把机械任务升级为模型任务。 - Claude 生成或评测只在对应任务 SoT、显式预算、固定执行配置和原文用途授权全部满足后运行;任一前置门失败都应关闭执行。 - Claude 的模型别名、解析后的完整模型 ID、预算和回执必须与冻结配置一致。不得因模型不可用而静默换模型、换供应商或降低评测规格;需要变更时先取得明确授权并重新登记配置。 @@ -105,17 +107,19 @@ agent-example/ - 每次创作生成结束后,向用户说明使用了哪些依据、候选或报告位于哪里,以及涉及哪些伏笔动作;不得把运行日志当作用户可观察的结果界面。 - 全程使用简体中文。任务正常执行期间只汇报关键里程碑;任务终止或需要用户决策时,第一句先说明用户现在需要做什么。 - 内部代号和英文术语要么不用,要么当场用白话解释;一句话只表达一件事,常规汇报以 30 秒内可读完为限。 +- **汇报禁止 AI 味**:不堆抽象标签、不用空转缓冲词(其实/往往/某种程度上)、不假对照(不是 A 而是 B)、不凑数排比、不段末升华;具体压倒抽象,名词给实物、动词给动作。 +- **需要用户决策时,每个决策点必须交代清楚**:前因后果(为什么需要这个决策、背景与约束是什么)、每个选项各自带来什么后果(选 A 影响什么、选 B 影响什么、对下游和全书走向各自意味着什么),用清晰的中文语义写;不许只列选项不交代后果,不许把后果写成抽象标签。 ## 8. 数据与合规边界 - 主代理和业务 agent 不裸连 PostgreSQL 或 New-API。查询、写入和 DDL 走对应 skill 的 `scripts/`;专用导入、嵌入、检索也走各自 skill。 -- 数据库写入、迁移、授权快照变更和批量运行必须先取得明确授权。DDL 先落 `db/ddl/` 的审计文件,再通过 `db` skill 应用;不得用一次性直连命令绕过。 -- 原书全文、完整问答、候选正文、标准答案和供应商响应按 raw 规则进库(单独表加访问控制,只读看板可看全文),授权、冻结边界遵守 `replay-eval` 合同。对外或跨任务引用的最终报告仍只保留评分、摘要、章节定位、失败类别和哈希,不直接复制全文。 +- 数据库写入、迁移、授权快照变更和批量运行必须先取得明确授权。DDL 先落 `db/ddl/` 的审计文件,再通过 `access-database` Skill 应用;不得用一次性直连命令绕过。 +- 原书全文、完整问答、候选正文、标准答案和供应商响应按 raw 规则进库(单独表加访问控制,只读看板可看全文),授权、冻结边界遵守 `evaluate-frozen-replay` 合同。对外或跨任务引用的最终报告仍只保留评分、摘要、章节定位、失败类别和哈希,不直接复制全文。 - 未绑定、未授权、来源状态无效或超出用途范围的 `knowledge/` 与数据库来源不得进入上下文;失败时明确记录 `not_authorized`、`stale_source` 等原因,不静默降级。 ## 9. 候选、改动与提交 -- 创作候选默认不提交。用户明确选择接受或修改后合并,且实时审查通过后,才可经 `confirm` 流程进入正式事实;丢弃也必须针对用户明确指定的候选。 +- 创作候选默认不提交。用户明确选择接受或修改后合并,且实时审查通过后,才可经 `decide-candidate` 流程进入正式事实;丢弃也必须针对用户明确指定的候选。 - 框架改动与创作内容分开审查、分开提交。不得把 agents、skills、meta、脚本改动和正文、规划、知识卡候选混在同一提交。 - 任何 `git add` 或 `git commit` 都必须先取得用户明确授权;获得授权后,提交信息使用 `作品(书名): 动作 摘要` 或 `框架: 摘要`。 - **提交前必须形而上审查。** 取得授权后、执行 `git add` 前,必须起一个独立子代理,对即将提交的全部内容(先 `git diff` 出真实改动)从形而上层面审查四个维度:**逻辑完整性**(论点/合同/链路有无缺环、边界是否闭合)、**一致性**(与既有 SSOT、术语、字段合同、相邻文档有无矛盾)、**合理性**(设计取舍是否站得住、是否过度或不足)、**可行性**(是否真能落地运行、测试是否支撑结论)。审查只读、不改代码;四维给出明确结论(通过 / 带条件通过 / 不通过 + 具体问题),通过后才允许提交。框架改动与创作内容分别审查。 @@ -128,9 +132,9 @@ agent-example/ - Python 一律使用仓内解释器 `.venv/bin/python`。先读目标 skill 的 `SKILL.md`,再运行其已存在的相关自测;例如: ```bash -.venv/bin/python .claude/skills/fine-outline/scripts/test_contract.py -.venv/bin/python .claude/skills/llm/scripts/test_quota.py -.venv/bin/python .claude/skills/read-context/scripts/test_writer_contract.py +.venv/bin/python .claude/skills/plan-chapter/scripts/test_contract.py +.venv/bin/python .claude/skills/call-content-model/scripts/test_quota.py +.venv/bin/python .claude/skills/assemble-context/scripts/test_writer_contract.py git diff --check ``` @@ -161,6 +165,8 @@ git diff --check 4. **符合 skill 规范**:`SKILL.md` 是否清楚定义输入、输出、红线、用法?`scripts/` 是否其确定性实现与自测入口?是否走 `.venv`、不裸调 PG/New-API/模型? 5. **边界一致**:引用的路径、合同、字段是否与现行 SoT(本文件、`.agent/`、`meta/` 和库内正式内容)一致?数据库是正式内容权威,Skill 必须声明读写哪些表、失败如何关闭、输入产出如何落库;引用已失效合同的不得执行。 +命名采用小写 `动作-对象`:目录名必须等于 frontmatter `name`,名称表达可调用能力,不复用 `scenario`、内部模块名或含混阶段词。`scenario`、`source_type`、updater 和备份逻辑键是独立稳定标识,不随 Skill 改名。 + ### 11.3 审查产出 每次审查对每个被审对象给出:**面对谁 / 目的**、**逐条问题**(正向资产 + 负向污染,各标严重度)、**具体修改建议**。审查只读、不改代码;修改另起授权,框架改动与创作内容分开提交。 diff --git a/CLAUDE.md b/CLAUDE.md index c129c71..2a21175 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,40 +1,3 @@ -# agent-example 会话章程 +# Claude Code 项目入口 -本仓是 muse 的**创作实验台**:Claude Code 扮演智能体运行时,真写小说,用来打磨 agent 能力、验证元数据与知识卡设计、沉淀给 muse 的 prompt 种子。不是独立系统,概念定义一律以 `../design-docs/`(专题-06、架构-02)为准,设计改进回填那边,不在本仓自立门户。 - -## 角色纪律 - -- **主会话只编排与裁决,不亲手写创作内容**。设定、大纲、正文、知识卡一律出自 `.claude/agents/` 的智能体;主会话负责组装上下文(经 `read-context`)、派发、校验产出格式、向用户呈报。 -- 派发子代理必须显式传 model:`planner`/`writer`/`judge` 固定 `opus`;`extractor`/`detector` 可用其它模型(非必须降级,创作期章后抽取可用 `opus`);纯机械活 = `haiku`。 -- **清洗与拆书/导入侧抽取的内容生产 LLM 走 New-API `MiniMax-M3`**(创始人 2026-07-13 拍板):经 `llm` / `parse-book` skill 统一入口调用,**不走角色 model 派发**;主会话只固化提示词/skill 与发起调用,不派 opus/haiku 子代理做这类内容生产。创作期章后抽取作为角色派发,可用 `opus`。 - -## 双轨纪律(本仓的命根子) - -- 智能体产出一律落入工作区,**不提交**。`git status`/`git diff` 就是待审层。 -- **确认 = commit,且只能由用户指令触发**,走 `confirm` 工具;丢弃 = restore。主会话与智能体都无权自行 commit 创作内容。 -- `works/*/评审/` 是运行噪音,已被 .gitignore 挡住,永不入历史。 -- 框架文件(agents/skills/meta/README)的改动属维护,可正常提交,但不得与创作内容混在同一个 commit。 - -## 开工读序 - -1. `README.md`(本仓定位与流程) -2. `meta/schemas/README.md`(两轴、控制项、实例落点表) -3. 目标作品的 `装配.yaml`(槽位与知识绑定)与 `状态.md`(写到哪了) - -## 生成纪律 - -- **所有 API/工具能力一律封装为 skill**(`.claude/skills/`):数据库、嵌入、检索、导入、确认、组装全走 skill,主会话与智能体不得裸调外部服务(PG/New-API)或散写一次性工具脚本。skill 形态 = SKILL.md + `scripts/*.py` 封装(uv `.venv` + requirements.txt,通用依赖优先)。能力清单见 README §四。 -- 组装上下文只走 `read-context`,按 schema 的 `aiContext` 裁剪;智能体不得自行翻仓库找上下文。 -- 未在 `装配.yaml` 绑定的 `knowledge/` 内容不得进入上下文。 -- 每次生成后向用户报:依据了什么、产出在哪、伏笔动作;等用户裁决。 - -## 语言 - -一律简体中文;提交信息格式 `作品(书名): 动作 摘要` 或 `框架: 摘要`。 - -## 呈报纪律(创始人 2026-07-13 纠正后固化) - -- 每次向创始人汇报,**第一句先回答「你现在需要做什么」**(哪怕是"不需要做任何事")。 -- 内部代号(S1/P2/门①)与英文行话(scaffold/embedding/pass)不得裸用——要么不用,要么当场白话展开。 -- 一句话只说一件事;汇报量以 30 秒读完为度,细节留仓库文档给路径。 -- 技术机制用白话说清因果,不用黑话压缩(反例:「合同漂移致守卫冤杀」;正例:「提示词教 AI 的字段名和数据库校验的字段名不一致,AI 交对了卡也被判错」)。 +请先完整读取并遵守 [`AGENTS.md`](AGENTS.md)。本文件只提供兼容入口,不定义独立规则或事实来源。 diff --git a/dashboard/server.py b/dashboard/server.py index 07f135d..79f7b2f 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -7,26 +7,56 @@ 命根子(验收按机械门查): 1. 连接只读:会话级 default_transaction_read_only=on,任何写语句数据库直接报错。 2. 代码无写语句:全文件只允许 SELECT,无 INSERT/UPDATE/DELETE/DDL(grep 即门)。 - 3. 不接会写的通道:不调用 confirm / db skill,只自己只读查库。 + 3. 不接会写的通道:不调用 decide-candidate / access-database Skill,只自己只读查库。 4. 挂了不牵连:进程崩了不影响库内正式内容与创作链。 视觉系统:dataviz 方法(已验证默认调色板,light/dark 双模式,状态色固定不主题化)。 跑法:.venv/bin/python dashboard/server.py (默认 http://127.0.0.1:8765) """ import html +import json import re import traceback +from collections import Counter from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from urllib.parse import urlparse, parse_qs +from pathlib import Path +from urllib.parse import parse_qs, urlencode, urlparse import psycopg -# 与 db skill 同一库,但独立只读连接(合同 §2:连接串与写通道分开,只读是机械门) +# 与 access-database Skill 同一库,但独立只读连接(合同 §2:连接串与写通道分开,只读是机械门) DSN = "postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example" HOST, PORT = "127.0.0.1", 8765 -# 额度治理上限(llm skill 合同:5 小时窗 $24 / 6000 次) +# 额度治理上限(call-content-model Skill 合同:5 小时窗 $24 / 6000 次) QUOTA_USD_CAP, QUOTA_CALL_CAP = 24.0, 6000 +# AI 味案例的正式来源是 muse-example;离线 JSON 只作为数据库不可用时的恢复/回退证据。 +AI_FLAVOR_REPORT_DIR = (Path(__file__).resolve().parents[1] + / ".claude/skills/capture-ai-flavor-cases/references/fixtures") +AI_FLAVOR_REVALIDATION_SCHEMA = "ai-flavor-revalidation-v1" +AI_FLAVOR_CASE_SCHEMA = "ai-flavor-case-v1" +AI_FLAVOR_TENANT_ID = 1 +AI_FLAVOR_PAGE_SIZE = 40 +_AI_FLAVOR_PATTERN_LABELS = { + "lexical.meta_disclaimer": "元话语套式", + "lexical.stock_micro_expression": "库存微表情", + "lexical.abstract_atmosphere": "抽象气氛", +} +_AI_FLAVOR_LABELS = { + "unclassified": "未分类", + "sf": "当前认为应修", + "snf": "表面相似但不应修", + "boundary": "需上下文裁决", + "regression": "修订回归", +} +_AI_FLAVOR_REVALIDATION_LABELS = { + "verified": "来源已验证", + "stale": "来源已变化", + "unavailable": "来源不可得", + "card_mismatch": "位置不匹配", + "missing": "尚未重验", +} + def ro_connect(): """只读连接:会话级锁死只读,写语句会被 PostgreSQL 拒绝(机械门,不靠自觉)。""" @@ -162,6 +192,77 @@ td a:hover { text-decoration:underline; } .chip.crit .dot { background:var(--crit); } .chip.gap { color:var(--muted); border:1px dashed var(--baseline); background:transparent; } .chip.gap .dot { background:var(--baseline); } +.chip code, .field-code { font-size:11px; font-weight:500; color:inherit; opacity:.78; } +.enum-code { font-size:11px; color:var(--muted); white-space:nowrap; } + +.knowledge-grid, .planning-grid { display:grid; grid-template-columns:minmax(0,1.1fr) minmax(320px,.9fr); + gap:14px; align-items:start; margin-top:14px; } +.knowledge-grid > .card, .planning-grid > .card { margin-top:0; } +.knowledge-filter { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:14px; } +.knowledge-filter input, .knowledge-filter select, .knowledge-filter button { min-height:34px; + border:1px solid var(--baseline); border-radius:5px; background:var(--surface); color:var(--ink); + font:inherit; padding:5px 9px; } +.knowledge-filter input { flex:1 1 220px; min-width:180px; } +.knowledge-filter button { cursor:pointer; background:var(--ink); color:var(--plane); border-color:var(--ink); } +.knowledge-filter button:hover { opacity:.86; } +.knowledge-filter .clear { color:var(--s1); font-size:13px; text-decoration:none; } +.knowledge-filter .clear:hover { text-decoration:underline; } +.knowledge-card-list { display:flex; flex-direction:column; } +.knowledge-card-item { display:block; padding:13px 16px; border-top:1px solid var(--grid); color:var(--ink); + text-decoration:none; } +.knowledge-card-item:first-child { border-top:none; } +.knowledge-card-item:hover, .knowledge-card-item.selected { background:var(--plane); } +.knowledge-card-item.selected { box-shadow:inset 3px 0 var(--s1); } +.knowledge-card-item .title-row { display:flex; gap:8px; align-items:baseline; justify-content:space-between; } +.knowledge-card-item .title { font-weight:650; overflow-wrap:anywhere; } +.knowledge-card-item .meta, .knowledge-card-item .summary { color:var(--muted); font-size:12px; margin-top:4px; } +.knowledge-card-item .summary { color:var(--ink2); line-height:1.5; } +.planning-item { display:block; padding:13px 16px; border-top:1px solid var(--grid); color:var(--ink); + text-decoration:none; } +.planning-item:first-child { border-top:none; } +.planning-item:hover, .planning-item.selected { background:var(--plane); } +.planning-item.selected { box-shadow:inset 3px 0 var(--s1); } +.planning-item .title-row { display:flex; gap:8px; align-items:baseline; justify-content:space-between; } +.planning-item .title { font-weight:650; overflow-wrap:anywhere; } +.planning-item .meta, .planning-item .summary { color:var(--muted); font-size:12px; margin-top:4px; } +.planning-item .summary { color:var(--ink2); line-height:1.5; } +.pager { display:flex; flex-wrap:wrap; align-items:center; gap:6px; padding:12px 16px; border-top:1px solid var(--grid); } +.pager a, .pager span { min-width:30px; padding:4px 8px; border:1px solid var(--grid); border-radius:4px; + color:var(--s1); text-align:center; text-decoration:none; font-size:12px; } +.pager span.current { background:var(--ink); border-color:var(--ink); color:var(--plane); } +.pager span.total { margin-right:auto; border:none; color:var(--muted); text-align:left; } +.knowledge-detail { position:sticky; top:18px; } +.knowledge-detail .detail-head { padding:14px 16px; border-bottom:1px solid var(--grid); } +.knowledge-detail .detail-head h3 { font-size:18px; line-height:1.35; overflow-wrap:anywhere; } +.knowledge-detail .detail-head .sub { color:var(--muted); font-size:12px; margin-top:4px; } +.detail-table { width:100%; border-collapse:collapse; font-size:13px; } +.detail-table th { width:112px; color:var(--muted); font-weight:600; text-align:left; vertical-align:top; } +.detail-table th, .detail-table td { padding:9px 16px; border-bottom:1px solid var(--grid); } +.detail-table tr:last-child th, .detail-table tr:last-child td { border-bottom:none; } +.detail-text { white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ink2); } +.detail-list { margin:0; padding-left:18px; color:var(--ink2); } +.detail-list li + li { margin-top:5px; } +pre.data-block { margin:10px 16px 16px; padding:12px; max-height:520px; overflow:auto; background:var(--plane); + border:1px solid var(--grid); border-radius:5px; color:var(--ink2); font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace; + white-space:pre-wrap; overflow-wrap:anywhere; } +pre.data-block code { font:inherit; color:inherit; white-space:inherit; } +details.data-details { border-top:1px solid var(--grid); } +details.data-details summary { cursor:pointer; padding:10px 16px; color:var(--s1); font-size:13px; } +.error-panel { margin-top:14px; border:1px solid color-mix(in srgb,var(--crit) 45%,var(--ring)); + border-radius:8px; background:color-mix(in srgb,var(--crit) 7%,var(--surface)); } +.error-panel .hd { padding:11px 14px; border-bottom:1px solid var(--grid); font-weight:650; color:var(--crit); } +.error-item { padding:11px 14px; border-bottom:1px solid var(--grid); } +.error-item:last-child { border-bottom:none; } +.error-item .label { color:var(--muted); font-size:12px; } +.error-item .value { margin-top:3px; color:var(--ink2); overflow-wrap:anywhere; } +.run-evidence details { border-top:1px solid var(--grid); } +.run-evidence details:first-child { border-top:none; } +.run-evidence summary { cursor:pointer; padding:11px 16px; color:var(--s1); } +.run-evidence pre { margin:0 16px 16px; } +@media (max-width:900px) { + .knowledge-grid, .planning-grid { grid-template-columns:1fr; } + .knowledge-detail { position:static; } +} .meter { margin:10px 0; } .meter .track { height:10px; border-radius:5px; background:var(--grid); overflow:hidden; } @@ -202,14 +303,295 @@ _STATE_MAP = { } -def chip(state, label=None): +_ENUM_LABELS = { + "status": { + "pending": "待确认", "draft": "草稿", "checking": "检查中", "parsing": "解析中", + "running": "运行中", "writing": "写作中", "shadow": "待审", + "confirmed": "已确认", "passed": "通过", "done": "完成", "parsed": "已解析", + "completed": "已完成", "accepted": "已采纳", "active": "有效", "canonical": "正式", + "published": "已发布", "rejected": "已拒绝", "failed": "失败", "revise": "待修订", + "stale": "已过期", "discarded": "已丢弃", "error": "错误", "unauthorized": "未授权", + "ignored": "已忽略", "not_built": "未建", "open": "开放", "closed": "已关闭", + "migrating": "归档中", "migrated": "已归档", + }, + "section_type": { + "setting": "设定", "outline": "大纲", "fine_outline": "细纲", + "state": "叙事状态", "assembly": "装配", + }, + "draft_type": {"entity": "实体卡", "relation": "关系卡"}, + "entity_type": { + "character": "人物", "character_relation": "人物关系", "location": "地点", + "faction": "势力", "item": "物品", "event": "事件", "power_system": "力量体系", + "scene_pattern": "场景范式", "combat": "战斗范式", "craft": "写法范式", + "emotion": "情绪范式", "trope": "套路范式", "chapter": "章节", + "generation_context": "生成上下文", "narrative_state": "叙事状态", + "novel_work": "作品", "outline": "大纲", "pacing": "节奏", + "reference_work": "参考书", "scene": "场景", "style": "文风", + "work_core": "作品核心", "world": "世界观", + }, + "run_type": {"production": "生产", "eval": "评测", "replay": "回放"}, + "source_role": { + "writer": "写手", "extractor": "章后抽卡", "planner": "规划器", + "detector": "语义检测器", "judge": "质量评审器", "continuation": "续写编排", + }, + "agent_role": { + "writer": "写手", "extractor": "章后抽卡", "planner": "规划器", + "detector": "语义检测器", "judge": "质量评审器", + }, + "stage_kind": { + "generation": "生成", "detection": "检测", "supplement": "补证", + "rewrite": "改写", "accept": "接受", + }, + "judge_kind": { + "detection": "检测", "scoring": "评分", "review": "审核", "experiment": "实验", + }, + "decision": {"accept": "采纳", "merge": "合并", "discard": "丢弃"}, + "conclusion": {"pass": "通过", "fail": "失败", "revise": "需修订", "pending": "待评判"}, + "source_type": { + "chapter_extract": "章后抽取", "parse_book": "拆书解析", "upgrade_book": "作品升格", + "ai_candidate": "模型候选", "manual": "人工录入", + }, + "trigger_source": { + "user": "用户触发", "system": "系统触发", "api": "接口触发", "scheduled": "定时触发", + }, + "purpose": { + "llm_call": "模型调用", "planning_persistence": "规划落库", + "production_generation": "正文生产", "evaluation": "评测", + }, + "skill_name": { + "access-database": "访问数据库", "assemble-context": "组装上下文", + "call-content-model": "调用内容模型", "check-content-consistency": "检查内容一致性", + "clean-book-text": "清洗书稿正文", "decide-candidate": "处理候选决策", + "deconstruct-book": "拆解完整作品", "design-story-foundation": "设计作品前期", + "embed-knowledge": "嵌入知识", "evaluate-frozen-replay": "评估冻结回放", + "execute-claude-task": "执行 Claude 任务", "expand-scene": "扩写场景", + "extract-chapter-knowledge": "抽取章节知识", "extract-work-knowledge": "抽取作品知识", + "freeze-context": "冻结上下文", "import-book": "导入书稿", + "maintain-work-extraction": "维护作品抽取", "optimize-content-quality": "优化内容质量", + "plan-chapter": "规划章节", "plan-story": "规划作品", "polish-prose": "润色正文", + "record-run-evidence": "记录运行证据", "review-knowledge-cards": "审核知识卡", + "rewrite-selection": "改写指定范围", "score-content-quality": "评估内容质量", + "search-knowledge": "检索知识", "write-next-chapter": "续写下一章", + }, +} + +_ENUM_ROLES = { + "decision": {"accept": "good", "merge": "warn", "discard": "crit"}, + "conclusion": {"pass": "good", "fail": "serious", "revise": "warn", "pending": "warn"}, + "raw_status": {"closed": "good", "migrated": "good", "open": "warn", "migrating": "warn"}, +} + +_REASON_LABELS = { + "completed": "已完成", "failed": "失败", "stop": "异常停止", "tool_use": "工具调用结束", + "process_exit_without_terminal_report": "进程退出且未上报终态", + "InvalidTextRepresentation": "文本格式错误", "ExtractionContractError": "抽取合同错误", +} + +_TEXT_TERM_LABELS = { + "writer sandbox": "写手隔离环境", "semantic detector": "语义检测器", "Claude CLI": "模型命令行客户端", + "writer": "写手", "detector": "检测器", "asOf": "截至章", + "LLM": "大模型", "New-API": "模型接口", "MiniMax": "模型服务", "Claude": "模型", "CLI": "命令行客户端", "scenario": "场景", + "continuation": "续写", "validation": "校验", "consistency_check": "一致性检查", + "fine_outline_replay": "细纲回放", "full_parse": "全书解析", "quality_gate": "质量门", + "writer_replay": "写手回放", "fieldAiContext": "字段上下文策略", "pgvector": "向量检索库", + "embed": "向量化", "dimensions": "维度数", "judge": "评审", "prompt": "提示词", + "expansion": "扩写", "extraction": "抽取", "planning": "规划", "polish": "润色", + "rewrite": "改写", "fine_outline": "细纲", "review": "审核", "as_of": "截至章", + "fresh": "全新", "sandbox": "隔离环境", "deadline": "截止时间", + "structured_output": "结构化输出", "aiContext": "智能上下文", "fail-closed": "失败即关闭", "build_snapshot": "构建快照", + "check_snapshot": "校验快照", "audit_leakage": "泄漏审计", "upgrade_book": "作品升格", + "revision journal": "修订日志", "raw vault": "原始证据仓", "muse-example": "实验库", "advisory lock": "咨询锁", "git commit": "提交操作", + "file_cas": "文件内容寻址存储", "CAS": "内容寻址存储", "journal": "日志", "revision": "修订号", + "raw": "原始", "vault": "存储仓", "skill": "技能", "block": "正文块", "replay-eval": "回放评测", "quality-gate": "质量门", + "detect": "语义检测", "restore": "恢复", "SQL": "数据库查询语言", + "DML": "数据操作语言", "DDL": "数据定义语言", "txt": "文本", "Git": "版本库", + "PostgreSQL": "数据库", "scripts/": "脚本目录", + "MiniMax-M3": "模型", "Qwen3-Embedding-8B": "嵌入模型", +} +_TEXT_TERM_PATTERN = re.compile( + r"(? 127 for char in value) + + +def enum_text(value, group="status"): + """枚举展示:中文语义优先,英文仅作为可复制的字符串编码。""" + if value is None: + return esc(value) + raw = str(value) + if _is_human_text(raw): + return html.escape(raw) + label = _ENUM_LABELS.get(group, {}).get(raw) + if label is None: + label = "未登记" + return f"{html.escape(label)} {html.escape(raw)}" + + +def field_label(label, code): + """字段标题使用中文,数据库/协议字段名只作为编码保留。""" + return f"{html.escape(label)} {html.escape(code)}" + + +def annotated_text(value): + """自由文本中的受控技术术语显示中文语义,原词保留为编码。""" + if value is None: + return esc(value) + escaped = html.escape(str(value)) + return _TEXT_TERM_PATTERN.sub( + lambda match: f"{html.escape(_TEXT_TERM_LABELS[match.group(0)])} " + f"{match.group(0)}", + escaped, + ) + + +def short_text(value, limit=56): + """截断说明时不切断英文编码,避免出现 ``fine_outl`` 这类伪字段。""" + text = str(value or "") + if len(text) <= limit: + return text + cut = text[:limit] + while cut and len(cut) < len(text): + next_char = text[len(cut)] + last_char = cut[-1] + if not (last_char.isalnum() or last_char in "_-") or not (next_char.isalnum() or next_char in "_-"): + break + cut = cut[:-1] + return cut.rstrip() + "…" + + +def _json_text(value): + """JSONB 详情使用可读缩进,标量和空值也保持稳定文本。""" + if value is None: + return "" + if isinstance(value, str): + return value + try: + return json.dumps(value, ensure_ascii=False, indent=2, default=str) + except (TypeError, ValueError): + return str(value) + + +def _data_block(value): + text = _json_text(value) + return f"
{esc(text)}
" + + +def _content_text(value): + """原始 prompt/response 若是 JSON,格式化后再展示;普通文本原样保留。""" + if value is None: + return "" + text = str(value) + try: + return json.dumps(json.loads(text), ensure_ascii=False, indent=2) + except (TypeError, ValueError, json.JSONDecodeError): + return text + + +_RAW_KIND_LABELS = { + "prompt": "模型输入", "response": "模型输出", "stderr": "错误输出", "error": "错误信息", +} + + +def _raw_kind_label(value): + raw = "" if value is None else str(value) + return f"{html.escape(_RAW_KIND_LABELS.get(raw, '原始证据'))} {html.escape(raw)}" + + +def _reason_text(value): + if value is None or value == "": + return "—" + raw = str(value) + label = _REASON_LABELS.get(raw, "未登记原因") + return f"{html.escape(label)} {html.escape(raw)}" + + +def _row_dicts(cols, rows): + return [dict(zip(cols, row)) for row in rows] + + +def _query_href(path, current, **updates): + """保留当前筛选条件,生成可复制的 GET 下钻地址。""" + values = { + key: vals[0] + for key, vals in (current or {}).items() + if vals and vals[0] not in (None, "") + } + for key, value in updates.items(): + if value in (None, ""): + values.pop(key, None) + else: + values[key] = value + query = urlencode(values) + return f"{path}?{query}" if query else path + + +def _chapter_text(chapter_id, order_no, title, *, link_work=None): + """章节主键只作为编码显示,用户看到章节序号和标题。""" + if order_no is None: + label = "未关联章节" + else: + label = f"第{order_no}章{f'《{title}》' if title else ''}" + if link_work is not None and order_no is not None: + label = f"{html.escape(label)}" + else: + label = html.escape(label) + if chapter_id is not None: + label += f" chapter_id={html.escape(str(chapter_id))}" + return label + + +def _mapping_html(mapping): + if not isinstance(mapping, dict) or not mapping: + return "暂无" + return ("" + + "".join(f"" + for key, value in mapping.items()) + + "
{esc(key)}{esc(_json_text(value))}
") + + +def display_trigger(value): + raw = "" if value is None else str(value) + if raw in _ENUM_LABELS["trigger_source"]: + return enum_text(raw, "trigger_source") + return annotated_text(value) + + +def _operator_text(value): + """运行调用方可能是角色,也可能是技能;两者都先显示中文语义。""" + raw = "" if value is None else str(value) + if raw in _ENUM_LABELS.get("source_role", {}): + return enum_text(raw, "source_role") + return enum_text(raw, "skill_name") + + +def enum_chip(value, group="status"): + raw = "" if value is None else str(value) + visual = _ENUM_ROLES.get(group, {}).get(raw, raw) + label = _ENUM_LABELS.get(group, {}).get(raw) + if label is None: + label = raw if _is_human_text(raw) else "未登记" + return chip(visual, label, code=raw) + + +def chip(state, label=None, *, code=None): """状态标签:颜色按语义角色,图标+文字同在。未识别状态归 warn(待审)。""" role = _STATE_MAP.get(str(state).lower(), "warn") if state else "gap" if state in (None, "", "not_built"): role = "gap" r, icon = _STATUS[role] - text = label if label is not None else (state or "未建") - return (f'{icon} {html.escape(str(text))}') + raw = code if code is not None else state + if label is None: + label = _ENUM_LABELS.get("status", {}).get(str(raw), "未登记") + suffix = "" + if code is not None or (state and str(state).lower() not in _STATUS): + suffix = f" {html.escape(str(raw))}" + return (f'{icon} ' + f'{html.escape(str(label))}{suffix}') def esc(v): @@ -236,37 +618,451 @@ NAV = [ ("/", "总览 · 健康", "数据权威 08"), ("/works", "作品", "作品领域 01"), ("/knowledge", "知识库", "实体 02 · 范式 03"), - ("/agents", "智能体", "Agent 07"), + ("/ai-flavor", "AI 味案例", "质量 06 · 数据库证据"), + ("/agents", "智能体", "智能体 07"), ("/runs", "运行记录", "流程 05 · 质量 06"), ] -def layout(title, body, active="/"): +def layout(title, body, active="/", footer_note=None): links = [] for href, label, dom in NAV: cls = "on" if href == active else "" links.append(f'{html.escape(label)}' f'
{html.escape(dom)}
') + footer_note = footer_note or "数据实时查库,无缓存 · 数字为当前库内事实 · 空白即落库缺口" return f""" -{html.escape(title)} · muse 看板 - +{html.escape(title)} · 创作看板 +
{body} -
数据实时查库,无缓存 · 数字为当前库内事实 · 空白即落库缺口
+
{footer_note}
""" +def _latest_ai_flavor_report(): + """数据库不可用时取仓库内最新的回填报告;不扫描原文。""" + reports = sorted(AI_FLAVOR_REPORT_DIR.glob("backfill-inventory-*.json")) + return reports[-1] if reports else None + + +def _load_ai_flavor_report(): + path = _latest_ai_flavor_report() + if path is None: + return None, "尚未生成离线回填报告" + try: + report = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as exc: + return None, f"回填报告读取失败:{exc}" + if not isinstance(report, dict) or report.get("schema_version") != "ai-flavor-inventory-v1": + return None, "回填报告版本不受支持" + if not isinstance(report.get("cards"), list) or not isinstance(report.get("works"), list): + return None, "回填报告缺少 works/cards 数组" + return report, None + + +def _latest_ai_flavor_revalidation_report(): + """数据库不可用时取仓内重验证回执;页面不因浏览而读取原文。""" + reports = sorted(AI_FLAVOR_REPORT_DIR.glob("revalidation-*.json")) + return reports[-1] if reports else None + + +def _load_ai_flavor_revalidation_report(): + path = _latest_ai_flavor_revalidation_report() + if path is None: + return None, "尚未生成来源重验证回执" + try: + report = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as exc: + return None, f"重验证回执读取失败:{exc}" + if not isinstance(report, dict) or report.get("schema_version") != AI_FLAVOR_REVALIDATION_SCHEMA: + return None, "重验证回执版本不受支持" + if not isinstance(report.get("cards"), list) or not isinstance(report.get("works"), list): + return None, "重验证回执缺少 works/cards 数组" + return report, None + + +def _ai_db_available(): + """只检查正式三表是否存在;查询失败由调用方转为离线回退。""" + return all(table_exists(name) for name in ( + "example_ai_flavor_case", + "example_ai_flavor_revalidation_batch", + "example_ai_flavor_revalidation", + )) + + +def _ai_db_bundle(): + """从 muse-example 组装看板所需的 inventory + 最新重验证回执视图。""" + if not _ai_db_available(): + return None, None, "AI 味案例正式表尚未建立" + _, card_rows = q( + "SELECT card_id,schema_version,card_type,state,label,layer,carrier,capture_mode," + "source_kind,source_license,source_sha256,excerpt_sha256,source_ref,work_ref," + "source_location,surface_location,excerpt,context,observation,feedback,review,rule_candidate_ids " + "FROM example_ai_flavor_case WHERE tenant_id=%s AND deleted=false " + "ORDER BY work_ref, (source_location->>'char_start')::bigint, card_id", + (AI_FLAVOR_TENANT_ID,), + ) + cards = [] + for row in card_rows: + (card_id, schema_version, card_type, state, label, layer, carrier, capture_mode, + source_kind, source_license, source_sha256, excerpt_sha256, source_ref, work_ref, + source_location, surface_location, excerpt, context, observation, feedback, review, + rule_candidate_ids) = row + cards.append({ + "schema_version": schema_version, + "id": card_id, + "card_type": card_type, + "state": state, + "label": label, + "layer": layer, + "carrier": carrier, + "capture_mode": capture_mode, + "excerpt": excerpt or "", + "context": context or "", + "source": { + "kind": source_kind, "license": source_license, + "source_sha256": source_sha256, "excerpt_sha256": excerpt_sha256, + "source_ref": source_ref or "", "work_ref": work_ref, + "location": source_location or {}, "surface_location": surface_location or {}, + }, + "observation": observation or {}, + **({"feedback": feedback} if feedback is not None else {}), + **({"review": review} if review is not None else {}), + "rule_candidate_ids": rule_candidate_ids or [], + }) + _, batch_rows = q( + "SELECT id,batch_ref,report_sha256,schema_version,checked_on,source_root_ref,usable,totals,works,report_path " + "FROM example_ai_flavor_revalidation_batch WHERE tenant_id=%s " + "ORDER BY checked_on DESC,id DESC LIMIT 1", + (AI_FLAVOR_TENANT_ID,), + ) + if not batch_rows: + work_map = {} + for card in cards: + source = card["source"] + key = (source.get("work_ref", ""), source.get("source_ref", "")) + work = work_map.setdefault(key, { + "work_ref": key[0], "source_ref": key[1], + "source_sha256": source.get("source_sha256"), "card_count": 0, + "pattern_counts": {}, + }) + work["card_count"] += 1 + pattern = card.get("observation", {}).get("pattern_key") + work["pattern_counts"][pattern] = work["pattern_counts"].get(pattern, 0) + 1 + return { + "schema_version": "ai-flavor-inventory-v1", "generated_on": None, + "source_root_ref": None, "source_license": "unknown", "totals": {}, + "works": list(work_map.values()), "cards": cards, "_authority": "postgresql", + }, None, None + (batch_id, batch_ref, report_sha256, revalidation_schema, checked_on, source_root_ref, + usable, batch_totals, batch_works, report_path) = batch_rows[0] + _, receipt_rows = q( + "SELECT card_id,work_ref,source_ref,expected_source_sha256,actual_source_sha256,status,reason,checked_on " + "FROM example_ai_flavor_revalidation WHERE tenant_id=%s AND batch_id=%s ORDER BY card_id", + (AI_FLAVOR_TENANT_ID, batch_id), + ) + receipts = [{ + "card_id": row[0], "work_ref": row[1], "source_ref": row[2], + "expected_source_sha256": row[3], "actual_source_sha256": row[4], + "status": row[5], "reason": row[6], "checked_on": row[7].isoformat() if row[7] else None, + } for row in receipt_rows] + by_id = {item["card_id"]: item for item in receipts} + for card in cards: + card["_revalidation"] = by_id.get(card["id"], {"status": "missing"}) + work_map = {} + pattern_counts = Counter() + for card in cards: + source = card["source"] + key = (source.get("work_ref", ""), source.get("source_ref", "")) + work = work_map.setdefault(key, { + "work_ref": key[0], "source_ref": key[1], + "source_sha256": source.get("source_sha256"), "card_count": 0, + "pattern_counts": {}, + }) + work["card_count"] += 1 + pattern = card.get("observation", {}).get("pattern_key") + work["pattern_counts"][pattern] = work["pattern_counts"].get(pattern, 0) + 1 + pattern_counts.update([pattern]) + aggregate_totals = { + "books": len(work_map), "cards": len(cards), + "pattern_counts": dict(sorted(pattern_counts.items())), + } + report = { + "schema_version": "ai-flavor-inventory-v1", + "generated_on": checked_on.isoformat() if checked_on else None, + "source_root_ref": source_root_ref, + "source_license": next((card["source"]["license"] for card in cards), "unknown"), + "totals": {**aggregate_totals, **(batch_totals or {})}, + "works": list(work_map.values()), "cards": cards, + "_authority": "postgresql", "_batch_id": batch_id, + "_batch_ref": batch_ref, "_report_sha256": report_sha256, + "_report_path": report_path, "_usable": usable, + } + return report, {"schema_version": revalidation_schema, "checked_on": report["generated_on"], + "usable": usable, "totals": batch_totals or {}, "works": list(work_map.values()), + "cards": receipts, "_authority": "postgresql", "_batch_id": batch_id, + "_report_sha256": report_sha256, "_report_path": report_path}, None + + +def _load_ai_flavor_bundle(): + """数据库优先;只有数据库表/连接不可用才退回仓内证据文件。""" + try: + report, revalidation, error = _ai_db_bundle() + if error is None and report is not None: + return report, revalidation, None, "postgresql" + except Exception as exc: # 看板不能阻断创作链,回退原因要显式显示 + error = f"数据库读取失败:{exc}" + report, report_error = _load_ai_flavor_report() + revalidation, revalidation_error = _load_ai_flavor_revalidation_report() + if report_error: + return None, None, error or report_error, "offline" + return report, revalidation, error or revalidation_error, "offline" + + +def _ai_first(params, key, default=""): + values = (params or {}).get(key, []) + return values[0] if values else default + + +def _ai_pattern_label(key): + return _AI_FLAVOR_PATTERN_LABELS.get(key, "未登记模式") + + +def _ai_revalidation_label(status): + return _AI_FLAVOR_REVALIDATION_LABELS.get(status, "未登记复核状态") + + +def _ai_revalidation_chip(status): + status = status or "missing" + role = {"verified": "good", "missing": "warn"}.get(status, "serious") + icon = {"verified": "✓", "missing": "●"}.get(status, "▲") + return (f"{icon}" + f"{esc(_ai_revalidation_label(status))} {esc(status)}") + + +def _ai_location(card): + location = card.get("source", {}).get("surface_location") or card.get("source", {}).get("location") or {} + line_start = location.get("line_start", "?") + line_end = location.get("line_end", line_start) + char_start = location.get("char_start", "?") + char_end = location.get("char_end", char_start) + line = f"第 {line_start} 行" if line_start == line_end else f"第 {line_start}-{line_end} 行" + return f"{line} · 字符 {char_start}-{char_end}" + + +def _ai_pager(path, params, page, pages): + links = [f"共 {fmt_num(pages)} 页"] + if page > 1: + links.append(f"上一页") + window = range(max(1, page - 2), min(pages, page + 2) + 1) + for number in window: + if number == page: + links.append(f"{number}") + else: + links.append(f"{number}") + if page < pages: + links.append(f"下一页") + return "
" + "".join(links) + "
" + + +def _ai_card_detail(card): + source = card.get("source", {}) + observation = card.get("observation", {}) + verification = card.get("_revalidation") or {} + checks = observation.get("function_check") or [] + check_html = "".join(f"
  • {esc(item)}
  • " for item in checks) + label = _AI_FLAVOR_LABELS.get(card.get("label"), "未登记标签") + state = card.get("state") + verification_status = verification.get("status", "missing") + if state == "canonical" and verification_status == "verified": + usability = "可进入样例投影或规则候选评审;仍不能直接成为 active 规则" + elif state in {"rejected", "archived"}: + usability = "仅保留审计,不参与当前诊断、样例或规则消费" + elif verification_status in {"stale", "unavailable", "card_mismatch", "missing"}: + usability = "当前失效:需重新取得来源并通过重验证;历史卡和回执继续保留" + else: + usability = "仅供本卡/本作品人工复核与候选提示;不可注入生成上下文或作为 blocking 规则" + scope = f"{source.get('work_ref') or '未绑定作品'} · {source.get('source_ref') or '未登记来源'} · {card.get('capture_mode') or '未知入口'}" + return ( + "
    " + "

    案例卡详情

    " + f"
    {esc(card.get('id'))} · 数据库当前投影 · 规则仍需评审
    " + "" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + "
    状态{chip(card.get('state'), 'Shadow · 待复核', code=card.get('state'))}
    来源重验证{_ai_revalidation_chip(verification.get('status'))}
    {esc(verification.get('reason', '页面未执行重验证'))}
    人工标签{esc(label)} {esc(card.get('label'))}
    当前用途{esc(usability)}
    作用域{esc(scope)}
    模式{esc(_ai_pattern_label(observation.get('pattern_key')))} {esc(observation.get('pattern_key'))}
    作品{esc(source.get('work_ref'))}
    来源位置{esc(source.get('source_ref'))}
    {esc(_ai_location(card))}
    表面命中{esc(observation.get('surface'))}
    来源许可{esc(source.get('license'))} · {'原文片段未保存' if not card.get('excerpt') else '已按授权保存片段'}
    全文 SHA-256{esc(source.get('source_sha256'))}
    命中 SHA-256{esc(source.get('excerpt_sha256'))}
    诊断{esc(observation.get('diagnosis'))}
    改动风险{esc(observation.get('risk_if_changed'))}
    建议动作{esc(observation.get('suggested_action'))}
    " + f"
    功能复核项
    " + f"
      {check_html}
    " + "
    " + ) + + +def view_ai_flavor(params=None): + """展示数据库正式案例卡;数据库不可用时明确标注离线回退。""" + report, revalidation, load_note, authority = _load_ai_flavor_bundle() + if report is None: + body = ("

    AI 味案例已有作品反向回填 · 数据库案例卡

    " + + empty_gap("暂无可展示案例", load_note or "数据库和离线恢复证据均不可用")) + return layout("AI 味案例", body, "/ai-flavor", + "质量证据页 · 尚未形成可读数据 · 看板只读") + + revalidation_by_id = { + item.get("card_id"): item for item in (revalidation or {}).get("cards", []) + if isinstance(item, dict) and item.get("card_id") + } + # 只给渲染副本附加回执,不改写磁盘报告;页面本身不读取原文。 + cards = [] + for card in report["cards"]: + rendered = dict(card) + rendered["_revalidation"] = rendered.get("_revalidation") or revalidation_by_id.get(card.get("id"), {"status": "missing"}) + cards.append(rendered) + works = report["works"] + totals = report.get("totals") or {} + selected_work = _ai_first(params, "work") + selected_pattern = _ai_first(params, "pattern") + selected_card_id = _ai_first(params, "card") + try: + page = max(1, int(_ai_first(params, "page", "1"))) + except ValueError: + page = 1 + + filtered = [card for card in cards + if (not selected_work or card.get("source", {}).get("work_ref") == selected_work) + and (not selected_pattern or card.get("observation", {}).get("pattern_key") == selected_pattern)] + filtered.sort(key=lambda card: ( + card.get("source", {}).get("work_ref", ""), + card.get("source", {}).get("location", {}).get("char_start", 0), + card.get("id", ""), + )) + pages = max(1, (len(filtered) + AI_FLAVOR_PAGE_SIZE - 1) // AI_FLAVOR_PAGE_SIZE) + page = min(page, pages) + visible = filtered[(page - 1) * AI_FLAVOR_PAGE_SIZE:page * AI_FLAVOR_PAGE_SIZE] + selected = next((card for card in cards if card.get("id") == selected_card_id), None) + + pattern_counts = Counter(card.get("observation", {}).get("pattern_key") for card in filtered) + state_counts = Counter(card.get("state") for card in cards) + verification_counts = Counter(card.get("_revalidation", {}).get("status", "missing") for card in cards) + source_license = report.get("source_license", "unknown") + generated_on = report.get("generated_on", "unknown") + report_rel = report.get("_report_path") or report.get("_batch_ref") or "数据库当前投影" + # 批次历史可能来自旧运行的本机临时路径;页面只暴露可迁移的逻辑引用。 + if authority == "postgresql" and report_rel: + report_rel = f"capture://ai-flavor/{Path(str(report_rel)).name}" + if authority == "offline": + try: + report_rel = str(_latest_ai_flavor_report().relative_to(Path(__file__).resolve().parents[1])) + except (AttributeError, ValueError): + report_rel = "仓内离线回退文件" + + tiles = ("
    " + f"
    {fmt_num(totals.get('books', len(works)))}
    作品
    " + f"
    {fmt_num(totals.get('cards', len(cards)))}
    候选卡
    当前筛选 {fmt_num(len(filtered))}
    " + f"
    {fmt_num(state_counts.get('shadow', 0))}
    Shadow · 待复核
    " + f"
    {fmt_num(verification_counts.get('verified', 0))}
    来源已验证
    非已验证 {fmt_num(len(cards) - verification_counts.get('verified', 0))}
    " + f"
    {esc(source_license)}
    来源许可
    " + "
    ") + + work_rows = [] + for work in works: + work_ref = work.get("work_ref", "") + active = "" if selected_work == work_ref else "" + href = _query_href("/ai-flavor", params, work=work_ref, pattern=None, page=1, card=None) + work_rows.append( + f"{esc(work.get('source_ref'))}" + f"
    {esc(work_ref)}
    " + f"{fmt_num(work.get('card_count', 0))}" + f"{', '.join(f'{esc(_ai_pattern_label(k))} {fmt_num(v)}' for k, v in (work.get('pattern_counts') or {}).items())}" + f"{esc(work.get('source_sha256'))}" + ) + work_filter = (f"清除作品筛选" if selected_work else "") + work_table = (f"
    按作品汇总({fmt_num(len(works))} 本)
    " + "" + f"{''.join(work_rows)}
    来源文件 / work_ref命中卡模式分布全文 SHA-256
    {work_filter}
    ") + + pattern_rows = [] + for key, count in sorted(pattern_counts.items(), key=lambda item: (-item[1], item[0])): + href = _query_href("/ai-flavor", params, pattern=key, page=1, card=None) + pattern_rows.append(f"{esc(_ai_pattern_label(key))} {esc(key)}{fmt_num(count)}") + pattern_table = ("
    当前筛选的模式分布
    " + "" + f"{''.join(pattern_rows) or ''}
    模式命中卡
    暂无
    ") + + card_rows = [] + for card in visible: + source = card.get("source", {}) + observation = card.get("observation", {}) + href = _query_href("/ai-flavor", params, card=card.get("id")) + card_rows.append( + f"{esc(card.get('id'))}" + f"{esc(source.get('work_ref'))}" + f"{esc(_ai_pattern_label(observation.get('pattern_key')))}
    {esc(observation.get('pattern_key'))}
    " + f"{esc(observation.get('surface'))}" + f"{esc(_ai_location(card))}" + f"{chip(card.get('state'), 'Shadow · 待复核', code=card.get('state'))}" + f"{_ai_revalidation_chip(card.get('_revalidation', {}).get('status'))}" + ) + cards_table = ("
    案例卡明细" + f"({fmt_num(len(filtered))} 张,当前第 {page}/{pages} 页)
    " + "" + f"{''.join(card_rows) or ''}
    卡片 ID作品模式表面命中位置状态来源重验证
    当前筛选暂无案例
    " + f"{_ai_pager('/ai-flavor', params, page, pages)}
    ") + detail = _ai_card_detail(selected) if selected else empty_gap("选择一张案例卡", "点击明细表中的卡片 ID 查看来源锚点和人工复核要求。") + filters = ("
    " + f"" + f"" + "清除
    ") + if revalidation: + if authority == "postgresql": + receipt_ref = f"batch_id={esc((revalidation or {}).get('_batch_id'))} · report_sha256={esc((revalidation or {}).get('_report_sha256'))}" + else: + latest = _latest_ai_flavor_revalidation_report() + receipt_ref = (str(latest.relative_to(Path(__file__).resolve().parents[1])) + if latest else "仓内离线回执") + revalidation_note = (f"重验证 {esc(receipt_ref)} · " + f"检查于 {esc((revalidation or {}).get('checked_on', 'unknown'))} · " + f"来源已验证 {fmt_num(verification_counts.get('verified', 0))}/{fmt_num(len(cards))}") + else: + revalidation_note = f"重验证:{esc(revalidation_error or '未提供回执')}" + authority_label = "PostgreSQL muse-example 正式库" if authority == "postgresql" else "离线回退(数据库当前不可读)" + fallback_note = f"
    回退 {esc(load_note)}" if authority == "offline" and load_note else "" + source_storage_note = "研究限定/未授权来源原文片段未保存" if source_license in {"research_only", "unauthorized"} else "授权来源可在评审后保存受控片段" + meta = (f"

    来源 {esc(authority_label)} · 批次/报告 {esc(report_rel)} · 生成于 {esc(generated_on)} · " + f"确定性模式命中数不是人工确认的 AI 味结论;{esc(source_storage_note)},当前状态仍须按评审门禁升级。
    " + f"{revalidation_note}。来源重验证只在确认、投影或规则使用前作为门禁,打开本页不会重新读取原文。{fallback_note}

    ") + body = ("

    AI 味案例已有作品反向回填 · 数据库案例卡 · 候选命中(非确认结论)

    " + f"{meta}{tiles}{filters}
    {work_table}{pattern_table}{cards_table}
    {detail}
    ") + return layout("AI 味案例", body, "/ai-flavor", + "质量证据页 · 数据库只读渲染 · 案例卡可追溯 · 重验证回执 append-only · 规则仍须人工/回放门禁") + + # ── 总览(08 数据权威:落库验收面 + 健康数字 + 额度水位)────────────────────── # 落库缺口板:一切输入产出该落哪张表,建没建、有没有数(合同 §4.2 的验收面) _LEDGER = [ + ("AI 味案例卡", "example_ai_flavor_case"), + ("AI 味重验证批次", "example_ai_flavor_revalidation_batch"), + ("AI 味逐卡回执", "example_ai_flavor_revalidation"), ("待审候选", "example_candidate"), ("用户决策", "example_user_decision"), ("运行回执", "example_run_receipt"), ("质量评判", "example_quality_result"), ("模型调用明细", "example_llm_call"), ("运行注册", "example_run"), - ("raw 全文", "example_raw_content"), + ("原始全文", "example_raw_content"), ("规划 / 细纲", "example_planning_section"), ("上下文冻结", "example_context_freeze"), ] @@ -283,9 +1079,9 @@ def _quota_block(): pct = min(100.0, val / cap * 100) if cap else 0 return (f"
    " f"
    {text}(上限 {cap:,.0f})
    ") - return (f"

    额度水位 窗口 {esc(wk)}

    " + return (f"

    额度水位 额度窗口 {esc(wk)}

    " f"
    " - f"{meter(usd, QUOTA_USD_CAP, f'MiniMax 花费 ${usd:.4f} / ${QUOTA_USD_CAP:.0f}')}" + f"{meter(usd, QUOTA_USD_CAP, f'模型 MiniMax 花费 ${usd:.4f} / ${QUOTA_USD_CAP:.0f}')}" f"{meter(calls, QUOTA_CALL_CAP, f'成功调用 {calls:,} / {QUOTA_CALL_CAP:,} 次')}
    ") @@ -321,13 +1117,13 @@ def view_home(): st, cnt = chip("not_built", "未建 · 待落库"), "—" else: n = counts[tbl] - st = chip("good", "已建 · 有数据") if n > 0 else chip("warn", "已建 · 0 行") + st = chip("active", "已建 · 有数据") if n > 0 else chip("pending", "已建 · 0 行") cnt = fmt_num(n) rows.append(f"{esc(label)}{esc(tbl)}" f"{cnt}{st}") ledger = f"""
    落库账本 —— 一切输入产出,落没落库一目了然
    - +
    输入产出承接表行数状态
    {"".join(rows)}
    输入产出承接表编码行数状态

    「未建」= 表还没建(合同要求落库,属缺口);「已建 · 0 行」= 表已就位,等创作链写入。 知识卡已确认 {fmt_num(entities)} 张(草稿≠已确认,全部确认前此数为 0 是正常状态)。

    """ @@ -353,14 +1149,72 @@ def view_works(): f"{esc(r[1])}{chip(r[2])}" f"{esc(r[3])}{esc(r[4])}{fmt_num(r[5])}" for r in rows) - body = (f"

    作品正式内容以库为准(muse_content_work)· 作品领域 01

    " + body = (f"

    作品正式内容以库为准(表编码 muse_content_work)· 作品领域 01

    " f"
    " f"{trs}
    作品状态类型章数字数
    ") return layout("作品", body, "/works") -def _work_knowledge_panel(wid): - """作品级知识链下钻:抽取运行、草稿、向量、实体、关系和绑定。""" +def _knowledge_card_detail(wid, card): + payload = card.get("draft_payload") or {} + entity_id = card.get("entity_id") + source = payload.get("source") if isinstance(payload.get("source"), dict) else {} + target = payload.get("target") if isinstance(payload.get("target"), dict) else {} + name = (card.get("entity_name") or payload.get("name") or payload.get("名称") or + source.get("name") or target.get("name") or "未命名卡") + card_type = card.get("entity_type") or payload.get("type") or payload.get("型") or card.get("draft_type") + brief = (card.get("entity_description") or payload.get("brief") or payload.get("description") or + payload.get("摘要") or "") + fields = payload.get("fields") or payload.get("attributes") or {} + extract_run_id = payload.get("extractRunId") or payload.get("extract_run_id") + run_link = (f"{html.escape(str(extract_run_id))}" + if extract_run_id else "—") + chapter = _chapter_text(card.get("chapter_id"), card.get("chapter_order"), card.get("chapter_title"), link_work=wid) + entity_html = "" + if entity_id: + entity_html = ( + "
    正式实体快照" + f"" + f"" + f"" + f"
    实体编号{esc(entity_id)}
    实体状态{chip(card.get('entity_status'))}
    实体名称{esc(card.get('entity_name'))}
    实体说明{esc(card.get('entity_description'))}
    " + f"{_data_block(card.get('entity_attributes'))}" + "
    " + ) + relation_html = "" + if source or target: + relation_html = ("关系两端" + f"{esc(source.get('name', '未命名'))} → {esc(target.get('name', '未命名'))}") + return ( + "
    " + f"

    {esc(name)}

    {enum_text(card_type, 'entity_type')} · 卡片编号 {esc(card.get('draft_id'))}
    " + "" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"{relation_html}" + f"" + f"" + "
    卡片类型{enum_text(card.get('draft_type'), 'draft_type')}
    实体/关系类型{enum_text(card_type, 'entity_type')}
    状态{chip(card.get('status'))}
    置信度{esc(card.get('confidence'))}
    来源章节{chapter}
    抽取运行{run_link}
    更新时间{esc(card.get('update_time'))}
    来源类型{enum_text(card.get('source_type'), 'source_type')} · source_id={esc(card.get('source_id'))}
    摘要{esc(brief) if brief else '—'}
    证据原文{esc(payload.get('evidence')) if payload.get('evidence') else '—'}
    " + f"
    字段
    {_mapping_html(fields)}" + f"{entity_html}" + f"
    查看完整卡载荷{_data_block(payload)}
    " + + (f"
    查看拟议变更{_data_block(card.get('proposed_changes'))}
    " + if card.get("proposed_changes") else "") + + (f"
    查看当前正式快照{_data_block(card.get('current_canonical_snapshot'))}
    " + if card.get("current_canonical_snapshot") else "") + + "
    " + ) + + +def _work_knowledge_panel(wid, params=None): + """作品级知识链下钻:可筛选卡片、右侧详情、抽取运行与向量状态。""" + params = params or {} if not table_exists("muse_knowledge_draft"): return empty_gap("知识抽取 · 待落库", "知识草稿表尚未建立。") _, summary = q( @@ -387,23 +1241,136 @@ def _work_knowledge_panel(wid): (wid, wid, wid, wid, wid, wid), ) pending, confirmed_drafts, pending_entities, pending_relations, entities, relations, bindings, embeddings = summary[0] - _, rows = q( - """SELECT d.id,d.source_id,d.draft_type,d.status, - COALESCE(d.draft_payload->>'name',d.draft_payload->>'名称', - d.draft_payload->>'type',d.draft_payload->>'型','?'), - COALESCE(d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type), - EXISTS (SELECT 1 FROM example_knowledge_embedding e - WHERE e.deleted=false AND (e.draft_id=d.id OR e.entity_id=d.entity_id)) - FROM muse_knowledge_draft d - WHERE d.work_id=%s AND d.deleted=false - ORDER BY d.source_id,d.id""", + + search = (params.get("q") or [""])[0].strip() + type_filter = (params.get("type") or [""])[0].strip() + status_filter = (params.get("status") or [""])[0].strip() + chapter_filter = (params.get("chapter") or [""])[0].strip() + selected_card = (params.get("card") or [""])[0].strip() + try: + selected_card_id = int(selected_card) if selected_card else None + except ValueError: + selected_card_id = None + + _, type_rows = q( + """SELECT DISTINCT COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type) + FROM muse_knowledge_draft d LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false + WHERE d.work_id=%s AND d.deleted=false ORDER BY 1""", (wid,), ) - draft_rows = "".join( - f"{esc(r[0])}{esc(r[1])}" - f"{esc(r[2])}{esc(r[4])}{esc(r[5])}" - f"{chip(r[3])}{chip('active' if r[6] else 'not_built', '已建' if r[6] else '待补')}" - for r in rows + _, chapter_rows = q( + "SELECT id,order_no,title FROM muse_content_chapter WHERE work_id=%s AND deleted=false ORDER BY order_no", + (wid,), + ) + type_options = [row[0] for row in type_rows if row[0]] + _, status_rows = q( + "SELECT DISTINCT status FROM muse_knowledge_draft WHERE work_id=%s AND deleted=false ORDER BY status", + (wid,), + ) + status_options = [row[0] for row in status_rows if row[0]] + + conditions = ["d.work_id=%s", "d.deleted=false"] + query_params = [wid] + if search: + like = f"%{search}%" + conditions.append("(d.draft_payload::text ILIKE %s OR COALESCE(e.normalized_name,'') ILIKE %s OR COALESCE(e.description,'') ILIKE %s)") + query_params.extend([like, like, like]) + if type_filter: + conditions.append("COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type)=%s") + query_params.append(type_filter) + if status_filter: + conditions.append("d.status=%s") + query_params.append(status_filter) + if chapter_filter: + try: + chapter_order = int(chapter_filter) + except ValueError: + chapter_order = None + if chapter_order is not None: + conditions.append("c.order_no=%s") + query_params.append(chapter_order) + + list_cols, list_rows = q( + """SELECT d.id AS draft_id,d.entity_id,d.draft_type,d.status,d.confidence,d.source_type,d.source_id, + c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title, + d.update_time,d.create_time, + COALESCE(e.normalized_name,d.draft_payload->>'name',d.draft_payload->>'名称', + d.draft_payload->'source'->>'name',d.draft_payload->'target'->>'name','未命名卡') AS entity_name, + COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type) AS entity_type, + COALESCE(e.description,d.draft_payload->>'brief',d.draft_payload->>'description','') AS summary, + EXISTS (SELECT 1 FROM example_knowledge_embedding emb + WHERE emb.deleted=false AND (emb.draft_id=d.id OR emb.entity_id=d.entity_id)) AS has_embedding + FROM muse_knowledge_draft d + LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false + LEFT JOIN muse_content_chapter c ON c.id=d.source_id AND c.work_id=d.work_id AND c.deleted=false + WHERE """ + " AND ".join(conditions) + + " ORDER BY d.update_time DESC NULLS LAST,d.create_time DESC,d.id DESC", + tuple(query_params), + ) + rows = _row_dicts(list_cols, list_rows) + + detail = ("
    卡详情
    " + "
    点击左侧实体/卡列表,右侧显示完整内容和来源证据。
    ") + if selected_card_id is not None: + detail_cols, detail_rows = q( + """SELECT d.id AS draft_id,d.entity_id,d.draft_type,d.status,d.confidence,d.source_type,d.source_id, + d.revision,d.create_time,d.update_time,d.draft_payload,d.proposed_changes, + d.current_canonical_snapshot,d.source_snapshot_id,d.authorization_snapshot_id, + d.source_status,d.source_action_policy, + c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title, + e.id AS entity_id_joined,e.normalized_name AS entity_name,e.entity_type AS entity_type, + e.description AS entity_description,e.attributes AS entity_attributes,e.status AS entity_status, + e.update_time AS entity_update_time,e.lineage_payload AS entity_lineage_payload + FROM muse_knowledge_draft d + LEFT JOIN muse_content_chapter c ON c.id=d.source_id AND c.work_id=d.work_id AND c.deleted=false + LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false + WHERE d.id=%s AND d.work_id=%s AND d.deleted=false""", + (selected_card_id, wid), + ) + if detail_rows: + card = dict(zip(detail_cols, detail_rows[0])) + card["entity_id"] = card.get("entity_id_joined") + detail = _knowledge_card_detail(wid, card) + else: + detail = ("
    卡详情
    " + "
    该卡不属于当前作品,或已经被软删除。
    ") + + def option(value, label, selected): + return f"" + + type_opts = "".join(option(value, _ENUM_LABELS.get("entity_type", {}).get(value, value), type_filter) + for value in type_options) + status_opts = "".join(option(value, _ENUM_LABELS.get("status", {}).get(value, value), status_filter) + for value in status_options) + chapter_opts = "".join(option(row[1], f"第{row[1]}章《{row[2]}》" if row[2] else f"第{row[1]}章", chapter_filter) + for row in chapter_rows) + filter_form = ( + "
    " + f"" + f"" + f"" + f"" + "" + f"清除条件
    " + ) + card_rows = [] + for row in rows: + selected = " selected" if row["draft_id"] == selected_card_id else "" + current = dict(params or {}) + href = _query_href(f"/works/{wid}", current, card=row["draft_id"]) + summary_text = short_text(row["summary"], 120) if row["summary"] else "暂无摘要" + source_text = _chapter_text(row["chapter_id"], row["chapter_order"], row["chapter_title"]) + card_rows.append( + f"" + f"
    {esc(row['entity_name'])}{enum_text(row['entity_type'], 'entity_type')}
    " + f"
    {enum_text(row['draft_type'], 'draft_type')} · {source_text} · 更新于 {esc(row['update_time'])}
    " + f"
    {esc(summary_text)} · {chip(row['status'])}
    " + ) + list_body = "".join(card_rows) if card_rows else empty_gap("没有匹配的实体/卡", "调整搜索或筛选条件后重试。") + list_html = ( + f"
    实体/卡列表({len(rows)})默认按更新时间倒序
    " + f"
    {list_body}
    " ) cards = ( f"
    " @@ -416,31 +1383,227 @@ def _work_knowledge_panel(wid): f"
    {fmt_num(bindings)}
    有效知识库绑定
    " f"
    " ) - draft_html = ( - f"
    章后抽卡明细({len(rows)})
    " - f"" - f"" - f"{draft_rows}
    draft来源章草稿型名称实体型/关系型状态向量
    " - ) if rows else empty_gap("还没有章后抽卡", "正文接受后运行 extract-knowledge 才会产生草稿。") - _, runs = q( - "SELECT run_id,target_chapter,trigger_source,terminal_state,started_at,finished_at " - "FROM example_run WHERE work_id=%s AND deleted=false ORDER BY started_at DESC LIMIT 30", - (wid,), - ) - run_rows = "".join( - f"{esc(r[0])}" - f"{esc(r[1])}{esc(r[2])}{chip(r[3])}" - f"{esc(r[4])} → {esc(r[5])}" for r in runs - ) + knowledge_grid = f"
    {list_html}{detail}
    " + runs = _runs_for_work(wid, 30) runs_html = ( f"
    作品运行链(最近 {len(runs)})
    " - f"" - f"{run_rows}
    run_id章触发终态起止
    " + f"{_RUN_COLS}{_run_rows(runs)}" ) if runs else empty_gap("作品暂无运行", "运行登记后会显示模型、回执和原始证据下钻入口。") - return f"

    知识闭环

    抽取 → 草稿 → 向量 → 用户确认 → 正式实体/关系;看板只读,确认仍走 confirm。

    {cards}{draft_html}{runs_html}" + return f"

    知识闭环

    抽取 → 草稿 → 向量 → 用户确认 → 正式实体/关系;看板只读,确认仍走确认通道。

    {cards}{filter_form}{knowledge_grid}{runs_html}" -def view_work(wid): +_SCHEMA_LABELS = { + "outline": "大纲结构", "novel_work": "作品设定", "narrative_state": "叙事状态", "assembly": "装配结构", +} + + +def _planning_title(payload, section_type, target_chapter): + payload = payload if isinstance(payload, dict) else {} + if payload.get("title"): + return str(payload["title"]) + if payload.get("chapterGoal"): + return f"第{target_chapter}章目标" if target_chapter is not None else "章节目标" + if isinstance(payload.get("state"), dict) and payload["state"].get("currentSituation"): + return "当前叙事状态" + if section_type == "assembly": + return "范式装配结果" + return _ENUM_LABELS.get("section_type", {}).get(section_type, "规划内容") + + +def _planning_summary(payload): + payload = payload if isinstance(payload, dict) else {} + candidates = [ + payload.get("chapterGoal"), payload.get("overall_arc"), payload.get("core_premise"), + payload.get("currentSituation"), payload.get("chapterEndHook"), payload.get("handoff"), + ] + state = payload.get("state") + if isinstance(state, dict): + candidates.insert(3, state.get("currentSituation")) + return next((str(value) for value in candidates if value), "暂无摘要") + + +def _planning_detail(wid, section): + payload = section.get("payload") or {} + section_type = section.get("section_type") + title = _planning_title(payload, section_type, section.get("target_chapter")) + summary_rows = [] + summary_keys = [ + ("title", "标题"), ("chapterGoal", "章节目标"), ("overall_arc", "总体弧线"), + ("core_premise", "核心设定"), ("currentSituation", "当前局势"), + ("chapterEndHook", "章末钩子"), ("handoff", "交接信息"), + ] + for key, label in summary_keys: + value = payload.get(key) + if value: + summary_rows.append(f"{label}{esc(_json_text(value))}") + state = payload.get("state") + if isinstance(state, dict) and state.get("currentSituation") and not payload.get("currentSituation"): + summary_rows.append(f"当前局势{esc(_json_text(state['currentSituation']))}") + summary_html = ("
    规划摘要
    " + + "".join(summary_rows) + "
    ") if summary_rows else "" + chapter = _chapter_text(section.get("chapter_id"), section.get("chapter_order"), section.get("chapter_title"), link_work=wid) + return ( + "
    " + f"

    {esc(title)}

    {enum_text(section_type, 'section_type')} · 规划编号 {esc(section.get('planning_id'))}
    " + "" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"" + "
    规划类型{enum_text(section_type, 'section_type')}
    结构类型{_SCHEMA_LABELS.get(section.get('schema_type'), '未登记')} {esc(section.get('schema_type'))}
    目标章{chapter}
    版本号{esc(section.get('version'))}
    状态{chip(section.get('state'))}
    确认时间{esc(section.get('confirmed_at'))}
    创建时间{esc(section.get('create_time'))}
    更新时间{esc(section.get('update_time'))}
    " + f"{summary_html}" + f"
    查看完整规划载荷{_data_block(payload)}
    " + "
    " + ) + + +def _planning_panel(wid, params=None): + """规划列表:更新时间倒序、服务端筛选分页,选中项右侧展示完整 payload。""" + params = params or {} + if not table_exists("example_planning_section"): + return empty_gap("规划 · 待落库", "规划表尚未建立(编码:100)。") + + keyword = (params.get("plan_q") or [""])[0].strip() + type_filter = (params.get("plan_type") or [""])[0].strip() + schema_filter = (params.get("plan_schema") or [""])[0].strip() + state_filter = (params.get("plan_state") or [""])[0].strip() + chapter_filter = (params.get("plan_chapter") or [""])[0].strip() + selected_raw = (params.get("plan") or [""])[0].strip() + try: + selected_id = int(selected_raw) if selected_raw else None + except ValueError: + selected_id = None + try: + page = max(1, int((params.get("plan_page") or ["1"])[0])) + except ValueError: + page = 1 + page_size = 10 + + _, type_rows = q("SELECT DISTINCT section_type FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) + _, schema_rows = q("SELECT DISTINCT schema_type FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) + _, state_rows = q("SELECT DISTINCT state FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) + _, chapter_rows = q("SELECT order_no,title FROM muse_content_chapter WHERE work_id=%s AND deleted=false ORDER BY order_no", (wid,)) + + conditions = ["p.work_id=%s", "p.deleted=false"] + query_params = [wid] + if keyword: + like = f"%{keyword}%" + conditions.append("(p.payload::text ILIKE %s OR p.section_type ILIKE %s OR p.schema_type ILIKE %s)") + query_params.extend([like, like, like]) + if type_filter: + conditions.append("p.section_type=%s") + query_params.append(type_filter) + if schema_filter: + conditions.append("p.schema_type=%s") + query_params.append(schema_filter) + if state_filter: + conditions.append("p.state=%s") + query_params.append(state_filter) + if chapter_filter == "none": + conditions.append("p.target_chapter IS NULL") + elif chapter_filter: + try: + chapter_number = int(chapter_filter) + except ValueError: + chapter_number = None + if chapter_number is not None: + conditions.append("p.target_chapter=%s") + query_params.append(chapter_number) + where_sql = " AND ".join(conditions) + total = int(one("SELECT count(*) FROM example_planning_section p WHERE " + where_sql, tuple(query_params)) or 0) + page_count = max(1, (total + page_size - 1) // page_size) + page = min(page, page_count) + list_cols, list_rows = q( + """SELECT p.id AS planning_id,p.section_type,p.schema_type,p.target_chapter,p.version,p.state,p.confirmed_at, + p.create_time,p.update_time,p.payload,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title + FROM example_planning_section p + LEFT JOIN muse_content_chapter c ON c.work_id=p.work_id AND c.order_no=p.target_chapter AND c.deleted=false + WHERE """ + where_sql + + " ORDER BY p.update_time DESC NULLS LAST,p.create_time DESC,p.id DESC LIMIT %s OFFSET %s", + tuple(query_params) + (page_size, (page - 1) * page_size), + ) + rows = _row_dicts(list_cols, list_rows) + + def option(value, label, selected): + return f"" + + type_opts = "".join(option(row[0], _ENUM_LABELS.get("section_type", {}).get(row[0], row[0]), type_filter) for row in type_rows) + schema_opts = "".join(option(row[0], _SCHEMA_LABELS.get(row[0], row[0]), schema_filter) for row in schema_rows) + state_opts = "".join(option(row[0], _ENUM_LABELS.get("status", {}).get(row[0], row[0]), state_filter) for row in state_rows) + chapter_opts = "".format(" selected" if chapter_filter == "none" else "") + chapter_opts += "".join(option(row[0], f"第{row[0]}章《{row[1]}》" if row[1] else f"第{row[0]}章", chapter_filter) for row in chapter_rows) + filter_form = ( + "
    " + f"" + f"" + f"" + f"" + f"" + "" + f"清除规划条件
    " + ) + items = [] + for row in rows: + selected = " selected" if row["planning_id"] == selected_id else "" + href = _query_href(f"/works/{wid}", params, plan=row["planning_id"], plan_page=page) + title = _planning_title(row["payload"], row["section_type"], row["target_chapter"]) + chapter = _chapter_text(row["chapter_id"], row["chapter_order"], row["chapter_title"]) + items.append( + f"" + f"
    {esc(title)}{enum_text(row['section_type'], 'section_type')}
    " + f"
    目标:{chapter} · 版本 {esc(row['version'])} · 更新于 {esc(row['update_time'])}
    " + f"
    {esc(short_text(_planning_summary(row['payload']), 130))} · {chip(row['state'])}
    " + ) + list_body = "".join(items) if items else empty_gap("没有匹配的规划", "调整搜索或筛选条件后重试。") + pager_parts = [f"第 {page}/{page_count} 页 · 共 {total} 条"] + if page > 1: + pager_parts.append(f"上一页") + page_start = max(1, page - 2) + page_end = min(page_count, page + 2) + if page_start > 1: + pager_parts.append(f"1") + if page_start > 2: + pager_parts.append("…") + pager_parts.extend( + f"{number}" if number == page else + f"{number}" + for number in range(page_start, page_end + 1) + ) + if page_end < page_count: + if page_end < page_count - 1: + pager_parts.append("…") + pager_parts.append(f"{page_count}") + if page < page_count: + pager_parts.append(f"下一页") + pager = "
    " + "".join(pager_parts) + "
    " + list_html = (f"
    规划列表({len(rows)} / {total})默认按更新时间倒序
    " + f"
    {list_body}
    {pager}
    ") + + detail = ("
    规划详情
    " + "
    点击左侧规划列表,右侧显示完整规划内容和版本信息。
    ") + if selected_id is not None: + detail_cols, detail_rows = q( + """SELECT p.id AS planning_id,p.section_type,p.schema_type,p.target_chapter,p.version,p.state,p.confirmed_at, + p.create_time,p.update_time,p.payload,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title + FROM example_planning_section p + LEFT JOIN muse_content_chapter c ON c.work_id=p.work_id AND c.order_no=p.target_chapter AND c.deleted=false + WHERE p.id=%s AND p.work_id=%s AND p.deleted=false""", + (selected_id, wid), + ) + if detail_rows: + detail = _planning_detail(wid, dict(zip(detail_cols, detail_rows[0]))) + else: + detail = ("
    规划详情
    " + "
    该规划不属于当前作品,或已经被软删除。
    ") + return f"{filter_form}
    {list_html}{detail}
    " + + +def view_work(wid, params=None): _, wrows = q( "SELECT w.id,w.title,w.status,w.genre,w.summary, " "COALESCE(SUM(b.word_count),0)::bigint AS word_count, " @@ -466,18 +1629,8 @@ def view_work(wid): f"{trs}") else: chap = empty_gap("还没有章节", "") - if table_exists("example_planning_section"): - _, secs = q("SELECT section_type, target_chapter, version, state FROM example_planning_section " - "WHERE work_id=%s AND deleted=false ORDER BY section_type, target_chapter NULLS FIRST, version DESC", - (wid,)) - plan = ("
    规划(大纲 / 细纲 / 设定)
    " - "" - + "".join(f"" - f"" for s in secs) - + "
    类型章版本状态
    {esc(s[0])}{esc(s[1]) if s[1] is not None else '—'}{esc(s[2])}{chip(s[3])}
    ") if secs else "
    规划表已建 · 暂无规划(planner 落库后显示)
    " - else: - plan = empty_gap("规划 · 待落库", "规划表(100)未建。") - knowledge = _work_knowledge_panel(wid) + plan = _planning_panel(wid, params) + knowledge = _work_knowledge_panel(wid, params) body = (f"
    作品 / {esc(title)}
    " f"

    {esc(title)} {chip(status)}

    " f"

    {esc(genre)} · {fmt_num(wc)} 字 · {esc(cc)} 章

    " @@ -506,7 +1659,7 @@ def view_chapter(wid, order_no): # ── 知识库(02 实体 + 03 范式:知识卡 + 参考书·拆书·清洗)───────────────────── -def _barset(rows, slots): +def _barset(rows, slots, group=None): if not rows: return "
    无数据
    " mx = max(r[1] for r in rows) @@ -514,7 +1667,8 @@ def _barset(rows, slots): out = [] for i, (name, n) in enumerate(rows): color = f"var(--s{slots[i % len(slots)]})" - out.append(f"
    {esc(name)}" + display_name = enum_text(name, group) if group else esc(name) + out.append(f"
    {display_name}" f"" f"{fmt_num(n)} · {n/total*100:.0f}%
    ") return "".join(out) @@ -542,7 +1696,7 @@ def view_knowledge(params=None): public = one("SELECT count(*) FROM muse_knowledge_draft WHERE work_id=0 AND deleted=false") or 0 work_cards = one("SELECT count(*) FROM muse_knowledge_draft WHERE work_id<>0 AND deleted=false") or 0 - # 型别看 payload,不看 draft_type 列(库内约定,防误导):公共卡用中文键「型」,作品卡用英文键 type + # 型别看载荷字段,不看 draft_type 列(库内约定,防误导):公共卡用中文键「型」,作品卡用 type 编码。 _, pub_types = q("SELECT COALESCE(draft_payload->>'型','(无型)') t, count(*) FROM muse_knowledge_draft " "WHERE work_id=0 AND deleted=false GROUP BY 1 ORDER BY 2 DESC") _, wk_types = q("SELECT COALESCE(draft_payload->>'type','(无型)') t, count(*) FROM muse_knowledge_draft " @@ -552,13 +1706,13 @@ def view_knowledge(params=None):
    {fmt_num(drafts)}
    草稿卡(待确认)
    {fmt_num(entities)}
    已确认知识
    {"当前为 0 · 全部草稿待确认" if entities==0 else ""}
    -
    {fmt_num(public)}
    公共范式卡(work_id=0)
    -
    {fmt_num(work_cards)}
    作品级卡(work_id≠0)
    +
    {fmt_num(public)}
    公共范式卡(work_id=0)
    +
    {fmt_num(work_cards)}
    作品级卡(work_id≠0)
    -
    公共范式卡 · 按型分布(型在 payload 中文键「型」)
    {_barset(pub_types,[1,2,3,4,5])}
    -
    作品级卡 · 按型分布(型在 payload 英文键 type)
    {_barset(wk_types,[1,2,3,4,5])}
    -

    型别看 draft_payload,不看 draft_type 列(该列恒为 entity,无区分力)。 - style / pacing 已注册 schema 但当前 0 张卡。草稿≠已确认:确认前 entity 表为 0 是正常状态。

    """ +
    公共范式卡 · 按型分布(载荷字段 payload 的中文键「型」)
    {_barset(pub_types,[1,2,3,4,5], 'entity_type')}
    +
    作品级卡 · 按型分布(类型字段 type)
    {_barset(wk_types,[1,2,3,4,5], 'entity_type')}
    +

    型别看载荷字段 draft_payload,不看 draft_type 列(该列恒为实体草稿,无区分力)。 + 文风与节奏(style / pacing)已注册 schema,但当前 0 张卡。草稿≠已确认:确认前正式实体表为 0 是正常状态。

    """ # 参考书·拆书·清洗(知识从哪来——拆书是知识的生产线) _, refs = q("SELECT id, source_file, author, imported_chapter_count, declared_chapter_count, " @@ -585,33 +1739,94 @@ def view_knowledge(params=None): _, recent = q("SELECT batch, reason, model, occurrences, create_time FROM example_clean_log " "WHERE deleted=false ORDER BY create_time DESC LIMIT 10") clean_html = (f"
    清洗审计(共 {fmt_num(cleans)} 条 · 最近 10)
    " + - "".join(f"
    {esc(r[0])} · {esc(r[2])}" + "".join(f"
    {esc(r[0])} · {esc(r[2])}" f"{esc(r[1])}" f"{esc(r[3])} 处
    " for r in recent) + "
    " ) if recent else "" - body = (f"

    知识库实体与范式(muse_knowledge_*)+ 它们的生产线(拆书·清洗)· 实体 02 · 范式 03

    " + body = (f"

    知识库实体与范式(表编码 muse_knowledge_*)+ 它们的生产线(拆书·清洗)· 实体 02 · 范式 03

    " f"{selector}{cards}

    参考书 · 拆书 · 清洗

    {reftbl}{pt_html}{clean_html}") return layout("知识库", body, "/knowledge") # ── 运行记录(05 流程 + 06 质量:运行列表按作品/按角色双维 + 运行详情下钻)──────── -_RUN_COLS = "" +_RUN_COLS = ("
    run_id作品章触发终态开始
    " + "") + + +def _run_description(run): + """把机器运行登记拼成用户能判断用途的中文描述。""" + run_id, work_id, work_title, target_chapter, trigger, terminal, started = run[:7] + chapter_id, chapter_order, chapter_title = run[7:10] + adapter_role, stage_kind, run_type, source_role, purpose = run[10:15] + if purpose == "planning_persistence" or str(run_id).startswith("planning-persist"): + kind = "规划落库" + elif purpose == "production_generation" or run_type == "production" or str(run_id).startswith("run-prod"): + kind = "正文生产" + elif adapter_role == "extractor" or source_role == "extractor" or str(run_id).startswith("extract-knowledge"): + kind = "章后抽卡" + elif purpose == "evaluation" or run_type == "eval": + kind = "质量评测" + elif adapter_role == "planner": + kind = "规划生成" + else: + kind = _ENUM_LABELS.get("stage_kind", {}).get(stage_kind, "运行") + actor = source_role or adapter_role + actor_html = f" · {enum_text(actor, 'source_role')}" if actor else "" + chapter_html = "" + if chapter_order is not None: + chapter_html = f" · {_chapter_text(chapter_id, chapter_order, chapter_title)}" + return f"{html.escape(kind)}{actor_html}{chapter_html}" def _run_rows(runs): return "".join( - f"" - f"" + f"" + f"" + f"" for r in runs) -def _all_runs(limit=50): - _, runs = q("SELECT run_id, work_id, target_chapter, trigger_source, terminal_state, started_at " - "FROM example_run WHERE deleted=false ORDER BY started_at DESC LIMIT %s", (limit,)) +def _runs_query(where_sql="r.deleted=false", params=(), limit=50): + candidate_join = ( + "LEFT JOIN LATERAL (SELECT ec.run_type,ec.source_role FROM example_candidate ec " + "WHERE ec.run_id=r.run_id AND ec.deleted=false ORDER BY ec.create_time,ec.id LIMIT 1) cand ON TRUE" + if table_exists("example_candidate") else + "LEFT JOIN LATERAL (SELECT NULL::text AS run_type,NULL::text AS source_role) cand ON TRUE" + ) + raw_join = ( + "LEFT JOIN LATERAL (SELECT rl.purpose FROM example_raw_lease rl " + "WHERE rl.run_id=r.run_id ORDER BY rl.create_time,rl.id LIMIT 1) raw ON TRUE" + if table_exists("example_raw_lease") else + "LEFT JOIN LATERAL (SELECT NULL::text AS purpose) raw ON TRUE" + ) + receipt_join = ( + "LEFT JOIN LATERAL (SELECT er.adapter_role,er.stage_kind FROM example_run_receipt er " + "WHERE er.run_id=r.run_id ORDER BY er.create_time,er.id LIMIT 1) first_receipt ON TRUE" + if table_exists("example_run_receipt") else + "LEFT JOIN LATERAL (SELECT NULL::text AS adapter_role,NULL::text AS stage_kind) first_receipt ON TRUE" + ) + sql = ( + "SELECT r.run_id,r.work_id,w.title AS work_title,r.target_chapter,r.trigger_source,r.terminal_state,r.started_at," + "c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title," + "first_receipt.adapter_role,first_receipt.stage_kind,cand.run_type,cand.source_role,raw.purpose " + "FROM example_run r LEFT JOIN muse_content_work w ON w.id=r.work_id " + "LEFT JOIN muse_content_chapter c ON c.work_id=r.work_id AND c.order_no=r.target_chapter AND c.deleted=false " + f"{receipt_join} {candidate_join} {raw_join} WHERE {where_sql} " + "ORDER BY r.started_at DESC LIMIT %s" + ) + _, runs = q(sql, tuple(params) + (limit,)) return runs +def _all_runs(limit=50): + return _runs_query(limit=limit) + + +def _runs_for_work(work_id, limit=50): + return _runs_query("r.deleted=false AND r.work_id=%s", (work_id,), limit) + + def _selector(label, name, options, sel): """服务端渲染的下拉筛选(GET 表单,无 JS)。options=[(值,显示名)]。""" opts = "".join(f"" @@ -634,23 +1849,23 @@ def view_runs(): "FROM example_candidate WHERE deleted=false AND state IN ('draft','checking','passed') " "ORDER BY create_time DESC LIMIT 30") cand = ("
    待审候选(活跃)
    运行编号 run_id作品运行内容目标章触发来源终态开始时间
    {esc(r[0])}{esc(r[1])}{esc(r[2])}{esc(r[3])}{chip(r[4])}{esc(r[5])}
    {esc(r[0])}{esc(r[2])} work_id={esc(r[1])}{_run_description(r)}{esc(r[3])}{display_trigger(r[4])}{chip(r[5])}{esc(r[6])}
    " - "" + "" + "".join(f"" - f"" for c in cands) + f"" for c in cands) + "
    id作品章版本类型状态角色
    候选编号 candidate_id作品目标章版本运行类型状态来源角色
    {esc(c[0])}{esc(c[1])}{esc(c[2])}{esc(c[3])}{esc(c[4])}{chip(c[5])}{esc(c[6])}
    {esc(c[3])}{enum_text(c[4], 'run_type')}{chip(c[5])}{enum_text(c[6], 'source_role')}
    ") if cands else "
    候选表已建 · 暂无待审候选
    " else: - cand = empty_gap("待审候选 · 待落库", "候选表(99)未建,AI 候选正文暂无持久化登记。") + cand = empty_gap("待审候选 · 待落库", "候选表尚未建立(编码:99),模型候选正文暂无持久化登记。") if table_exists("example_user_decision"): _, decs = q("SELECT work_id, target_chapter, decision, rationale, decided_by, create_time " "FROM example_user_decision ORDER BY create_time DESC LIMIT 30") dec = ("
    用户决策(最近 30)
    " "" - + "".join(f"" - f"" for d in decs) + + "".join(f"" + f"" for d in decs) + "
    作品章决策理据决策人时间
    {esc(d[0])}{esc(d[1])}{chip({'accept':'good','merge':'warn','discard':'crit'}.get(d[2],'warn'), d[2])}{esc((d[3] or '')[:40])}{esc(d[4])}{esc(d[5])}
    {esc(d[0])}{esc(d[1])}{enum_chip(d[2], 'decision')}{annotated_text((d[3] or '')[:40])}{esc(d[4])}{esc(d[5])}
    ") if decs else "
    决策表已建 · 暂无决策
    " else: - dec = empty_gap("用户决策 · 待落库", "决策记录表(99)未建,接受/合并/丢弃暂以 git 提交留痕。") - body = (f"

    运行记录agentic 的操作与产出 · 流程 05 · 质量 06

    " + dec = empty_gap("用户决策 · 待落库", "决策记录表尚未建立(编码:99),接受/合并/丢弃暂以版本库提交留痕。") + body = (f"

    运行记录智能体的操作与产出 · 流程 05 · 质量 06

    " f"{entries}{runs_html}

    待审候选

    {cand}

    用户决策

    {dec}") return layout("运行记录", body, "/runs") @@ -660,8 +1875,7 @@ def view_runs_by_work(params): _, works = q("SELECT id, title FROM muse_content_work WHERE deleted=false ORDER BY id") selector = _selector("选作品:", "work", [(w[0], w[1]) for w in works], sel) if sel: - _, runs = q("SELECT run_id, work_id, target_chapter, trigger_source, terminal_state, started_at " - "FROM example_run WHERE work_id=%s AND deleted=false ORDER BY started_at DESC LIMIT 50", (sel,)) + runs = _runs_for_work(sel, 50) else: runs = _all_runs() tbl = (_RUN_COLS + _run_rows(runs) + "") if runs else "
    暂无运行
    " @@ -675,9 +1889,11 @@ def view_runs_by_role(params): _, roles = q("SELECT role, display_name FROM example_agent_role WHERE deleted=false ORDER BY role") selector = _selector("选角色:", "role", [(r[0], r[1] or r[0]) for r in roles], sel) if sel: - _, runs = q("SELECT DISTINCT n.run_id, n.work_id, n.target_chapter, n.trigger_source, n.terminal_state, n.started_at " - "FROM example_run_receipt r JOIN example_run n ON n.run_id=r.run_id " - "WHERE r.adapter_role=%s AND n.deleted=false ORDER BY n.started_at DESC LIMIT 50", (sel,)) + runs = _runs_query( + "r.deleted=false AND EXISTS (SELECT 1 FROM example_run_receipt role_receipt " + "WHERE role_receipt.run_id=r.run_id AND role_receipt.adapter_role=%s)", + (sel,), 50, + ) else: runs = _all_runs() tbl = (_RUN_COLS + _run_rows(runs) + "") if runs else "
    该角色暂无运行
    " @@ -687,75 +1903,78 @@ def view_runs_by_role(params): def view_run_detail(run_id): - _, rr = q("SELECT run_id, work_id, target_chapter, trigger_source, started_at, finished_at, terminal_state " - "FROM example_run WHERE run_id=%s AND deleted=false", (run_id,)) + _, rr = q( + """SELECT r.run_id,r.work_id,r.target_chapter,r.trigger_source,r.started_at,r.finished_at,r.terminal_state, + w.title AS work_title,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title + FROM example_run r + LEFT JOIN muse_content_work w ON w.id=r.work_id + LEFT JOIN muse_content_chapter c ON c.work_id=r.work_id AND c.order_no=r.target_chapter AND c.deleted=false + WHERE r.run_id=%s AND r.deleted=false""", + (run_id,), + ) if not rr: return layout("运行", "

    运行不存在

    ", "/runs") - rid, wid, chap, trig, started, finished, state = rr[0] - info = (f"
    " - f"" - f"" - f"" - f"" - f"" - f"" - f"
    run_id{esc(rid)}
    作品{esc(wid)}
    目标章{esc(chap)}
    触发{esc(trig)}
    终态{chip(state)}
    起止{esc(started)} → {esc(finished)}
    ") - _, rc = q("SELECT sample_id, revision, stage_kind, adapter_role, attempt, loop_seq, candidate_version, is_error " - "FROM example_run_receipt WHERE run_id=%s ORDER BY sample_id, revision", (run_id,)) - rc_html = ("
    回执链(按样本 · revision 哈希链)
    " - "" - + "".join(f"" - f"" for r in rc) - + "
    样本rev阶段角色attempt补证候选版本结果
    {esc(r[0])}{esc(r[1])}{esc(r[2])}{esc(r[3])}{esc(r[4])}{esc(r[5])}{esc(r[6])}{chip('failed' if r[7] else 'passed')}
    ") if rc else empty_gap("暂无回执", "该运行的回执落库后在此显示(revision 哈希链)。") - _, qres = q("SELECT judge_kind, dimension, scale_version, score, conclusion, failure_class " - "FROM example_quality_result WHERE run_id=%s ORDER BY create_time", (run_id,)) - q_html = ("
    质量评判
    " - "" - + "".join(f"" for u in qres) - + "
    类型维度量表版本分结论失败类别
    {chip(u[0])}{esc(u[1])}{esc(u[2])}{esc(u[3])}{chip(u[4])}{esc(u[5])}
    ") if qres else empty_gap("暂无质量评判", "") - _, calls = q("SELECT call_time, caller, requested_model_id, actual_model_id, out_tokens, cost_usd " - "FROM example_llm_call WHERE run_id=%s ORDER BY call_time", (run_id,)) - call_html = ("
    模型调用
    " - "" - + "".join(f"" - f"" for c in calls) - + "
    时间调用方请求模型实际模型输出token成本
    {esc(c[0])}{esc(c[1])}{esc(c[2])}{esc(c[3])}{fmt_num(c[4])}${float(c[5] or 0):.5f}
    ") if calls else empty_gap("暂无模型调用", "") + rid, wid, chap, trig, started, finished, state, work_title, chapter_id, chapter_order, chapter_title = rr[0] + rc_cols, rc_rows = q( + """SELECT id,sample_id,revision,stage_kind,adapter_role,attempt,loop_seq,candidate_version,is_error, + stop_reason,terminal_reason,safe_summary,raw_content_id,requested_model_id,actual_model_id, + total_cost_usd,usage,create_time + FROM example_run_receipt WHERE run_id=%s ORDER BY sample_id,revision""", + (run_id,), + ) + rc = _row_dicts(rc_cols, rc_rows) + q_cols, q_rows = q( + """SELECT judge_kind,dimension,scale_version,score,conclusion,failure_class,detail,raw_content_id,create_time + FROM example_quality_result WHERE run_id=%s ORDER BY create_time""", + (run_id,), + ) + qres = _row_dicts(q_cols, q_rows) + call_cols, call_rows = q( + """SELECT id,call_time,caller,requested_model_id,actual_model_id,in_tokens,cached_tokens,out_tokens, + cost_usd,stop_reason,duration_ms,prompt_sha256,raw_content_id + FROM example_llm_call WHERE run_id=%s ORDER BY call_time""", + (run_id,), + ) + calls = _row_dicts(call_cols, call_rows) + candidates = [] if table_exists("example_candidate"): - _, candidates = q( + candidate_cols, candidate_rows = q( "SELECT id, target_chapter, candidate_version, run_type, state, source_role, " "candidate_sha256, context_sha256 FROM example_candidate " "WHERE run_id=%s AND deleted=false ORDER BY target_chapter, candidate_version", (run_id,), ) + candidates = _row_dicts(candidate_cols, candidate_rows) cand = ( "
    候选(本运行)
    " - "" - "" + "" + "" + "".join( - f"" - f"" - f"" + f"" + f"" + f"" for c in candidates ) + "
    id章版本运行类型状态角色正文哈希上下文哈希
    候选编号 candidate_id目标章版本编码运行类型状态来源角色正文哈希上下文哈希
    {esc(c[0])}{esc(c[1])}{esc(c[2])}{esc(c[3])}{chip(c[4])}{esc(c[5])}{esc(c[6])}{esc(c[7])}
    {esc(c['id'])}{esc(c['target_chapter'])}{esc(c['candidate_version'])}{enum_text(c['run_type'], 'run_type')}{chip(c['state'])}{enum_text(c['source_role'], 'source_role')}{esc(c['candidate_sha256'])}{esc(c['context_sha256'])}
    " if candidates else "
    候选表已建 · 该运行暂无候选
    " ) else: - cand = empty_gap("待审候选 · 待落库", "候选表(99)未建。") + cand = empty_gap("待审候选 · 待落库", "候选表尚未建立(编码:99)。") if table_exists("example_user_decision") and table_exists("example_candidate"): - _, decisions = q( + decision_cols, decision_rows = q( "SELECT d.id,d.decision,d.rationale,d.decided_by,d.canonical_block_id,d.create_time " "FROM example_user_decision d JOIN example_candidate c ON c.id=d.candidate_id " "WHERE c.run_id=%s ORDER BY d.create_time", (run_id,), ) + decisions = _row_dicts(decision_cols, decision_rows) dec = ( "
    用户决策(本运行)
    " - "" + "" "" + "".join( - f"" - f"" + f"" + f"" for d in decisions ) + "
    id决策理据决策人
    决策编号 decision_id决策理据决策人正式正文块时间
    {esc(d[0])}{chip({'accept':'good','merge':'warn','discard':'crit'}.get(d[1],'warn'), d[1])}{esc(d[2])}{esc(d[3])}{esc(d[4])}{esc(d[5])}
    {esc(d['id'])}{enum_chip(d['decision'], 'decision')}{annotated_text(d['rationale'])}{esc(d['decided_by'])}{esc(d['canonical_block_id'])}{esc(d['create_time'])}
    " @@ -763,25 +1982,47 @@ def view_run_detail(run_id): ) else: dec = empty_gap("用户决策 · 待落库", "决策表(99)或候选表未建。") + raw_rows = [] if table_exists("example_raw_lease"): - _, raw_rows = q( + raw_cols, raw_data = q( "SELECT l.id,l.purpose,l.status,count(c.id),max(c.create_time) " "FROM example_raw_lease l LEFT JOIN example_raw_content c ON c.lease_id=l.id " "WHERE l.run_id=%s GROUP BY l.id,l.purpose,l.status ORDER BY l.id", (run_id,), ) + raw_rows = [dict(zip(raw_cols, row)) for row in raw_data] raw_html = ( - "
    raw 证据(本运行)
    " - "" + "
    原始证据(本运行)
    lease用途状态内容数最近写入
    " + "" + "".join( - f"" - f"" for r in raw_rows + f"" + f"" for r in raw_rows ) + "
    租约编号 lease_id用途状态内容数最近写入
    {esc(r[0])}{esc(r[1])}{chip(r[2])}{esc(r[3])}{esc(r[4])}
    {esc(r['id'])}{enum_text(r['purpose'], 'purpose')}{enum_chip(r['status'], 'status')}{esc(r['count'])}{esc(r['max'])}
    " - if raw_rows else empty_gap("raw · 该运行暂无证据", "模型输入输出落库后在此显示。") + if raw_rows else empty_gap("原始证据 · 该运行暂无记录", "模型输入输出落库后在此显示。") ) else: - raw_html = empty_gap("raw · 待落库", "raw 表(101)未建。") + raw_html = empty_gap("原始证据 · 待落库", "原始证据表尚未建立(编码:example_raw_lease)。") + raw_content = [] + if table_exists("example_raw_content"): + raw_content_cols, raw_content_rows = q( + "SELECT id,kind,role,content_sha256,content,create_time FROM example_raw_content " + "WHERE run_id=%s ORDER BY create_time,id", (run_id,) + ) + raw_content = _row_dicts(raw_content_cols, raw_content_rows) + raw_details = [] + for index, content in enumerate(raw_content): + open_attr = " open" if index == len(raw_content) - 1 else "" + raw_details.append( + f"{_raw_kind_label(content['kind'])} · 内容编号 {esc(content['id'])} · " + f"{len(content['content'] or '')} 字 · 哈希 {esc(content['content_sha256'])}" + f"
    {esc(_content_text(content['content']))}
    " + ) + raw_detail_html = ( + "
    模型输入 / 输出正文
    " + + "".join(raw_details) + "
    " + if raw_details else empty_gap("暂无模型输入输出", "原始内容落库后在此显示完整正文。") + ) if table_exists("example_context_freeze") and wid is not None and chap is not None: _, freezes = q( "SELECT id,as_of_chapter,manifest_sha256,context_sha256,token_budget,create_time " @@ -791,49 +2032,135 @@ def view_run_detail(run_id): ) freeze_html = ( "
    上下文冻结(匹配本作品/目标章)
    " - "" + "" + "".join( f"" f"" f"" for f in freezes ) + "
    idasOfmanifestcontext预算时间
    冻结编号 freeze_id基线章 as_of_chapter来源清单哈希上下文哈希令牌预算时间
    {esc(f[0])}{esc(f[1])}{esc(f[2])}{esc(f[3])}{esc(f[4])}{esc(f[5])}
    " - if freezes else empty_gap("上下文冻结 · 该目标章暂无记录", "assemble 后的冻结清单落库后在此显示。") + if freezes else empty_gap("上下文冻结 · 该目标章暂无记录", "上下文装配后的冻结清单落库后在此显示。") ) else: - freeze_html = empty_gap("上下文冻结 · 待落库", "冻结表(100)未建或该运行不是章级运行。") + freeze_html = empty_gap("上下文冻结 · 待落库", "冻结表尚未建立(编码:100),或该运行不是章级运行。") + + first_receipt = rc[0] if rc else {} + first_candidate = candidates[0] if candidates else {} + first_raw = raw_rows[0] if raw_rows else {} + run_content = _run_description(( + rid, wid, work_title, chap, trig, state, started, chapter_id, chapter_order, chapter_title, + first_receipt.get("adapter_role"), first_receipt.get("stage_kind"), first_candidate.get("run_type"), + first_candidate.get("source_role") or first_receipt.get("adapter_role"), first_raw.get("purpose"), + )) + chapter_html = _chapter_text(chapter_id, chapter_order, chapter_title, link_work=wid) + info = (f"
    " + f"" + f"" + f"" + f"" + f"" + f"" + f"" + f"
    {field_label('运行编号', 'run_id')}{esc(rid)}
    作品{esc(work_title)} work_id={esc(wid)}
    运行内容{run_content}
    目标章{chapter_html}
    触发来源{display_trigger(trig)}
    终态{chip(state)}
    起止{esc(started)} → {esc(finished)}
    ") + + rc_html = ("
    回执链(按样本 · 修订链 revision)
    " + "" + + "".join( + f"" + f"" + f"" + f"" + f"" + for r in rc) + + "
    样本编号修订号 revision阶段角色尝试次数 attempt补证次数 loop_seq候选版本结果诊断
    {esc(r['sample_id'])}{esc(r['revision'])}{enum_text(r['stage_kind'], 'stage_kind')}{enum_text(r['adapter_role'], 'source_role')}{esc(r['attempt'])}{esc(r['loop_seq'])}{esc(r['candidate_version'])}{chip('failed' if r['is_error'] else 'passed')}{_reason_text(r['stop_reason'])} / {_reason_text(r['terminal_reason'])}
    ") if rc else empty_gap("暂无回执", "该运行的回执落库后在此显示(修订链编码:revision)。") + q_html = ("
    质量评判
    " + "" + + "".join( + f"" + f"" + f"" + for u in qres) + + "
    评判类型维度编号量表版本编码分数结论失败类别编码详细信息
    {enum_chip(u['judge_kind'], 'judge_kind')}{esc(u['dimension'])}{esc(u['scale_version'])}{esc(u['score'])}{enum_chip(u['conclusion'], 'conclusion')}{esc(u['failure_class'])}{('
    查看' + _data_block(u['detail']) + '
    ') if u['detail'] else '—'}
    ") if qres else empty_gap("暂无质量评判", "") + call_html = ("
    模型调用
    " + "" + + "".join( + f"" + f"" + f"" + for c in calls) + + "
    时间调用方请求模型编码实际模型编码输入令牌缓存令牌输出令牌停止原因耗时毫秒原始内容
    {esc(c['call_time'])}{_operator_text(c['caller'])}{esc(c['requested_model_id'])}{esc(c['actual_model_id'])}{fmt_num(c['in_tokens'])}{fmt_num(c['cached_tokens'])}{fmt_num(c['out_tokens'])}{_reason_text(c['stop_reason'])}{fmt_num(c['duration_ms'])}{esc(c['raw_content_id'])}
    ") if calls else empty_gap("暂无模型调用", "") + + error_items = [] + for r in rc: + abnormal_stop = r["stop_reason"] not in (None, "", "completed", "tool_use") + abnormal_terminal = r["terminal_reason"] not in (None, "", "completed") + if r["is_error"] or abnormal_stop or abnormal_terminal: + summary = _data_block(r["safe_summary"]) if r["safe_summary"] else "—" + error_items.append( + f"
    回执 · 样本 {esc(r['sample_id'])} · 修订 {esc(r['revision'])}
    " + f"
    停止原因:{_reason_text(r['stop_reason'])} · 终态原因:{_reason_text(r['terminal_reason'])}
    " + f"
    安全摘要:{summary}
    " + ) + for u in qres: + if u["failure_class"] or u["conclusion"] == "fail": + error_items.append( + f"
    质量评判 · 类型 {enum_chip(u['judge_kind'], 'judge_kind')}
    " + f"
    失败类别:{esc(u['failure_class'])}
    " + f"
    判定细节:{_data_block(u['detail']) if u['detail'] else '—'}
    " + ) + if state == "failed" and not error_items: + error_items.append("
    运行终态为失败,但回执和质量表没有提供更细错误记录。
    ") + error_html = ("
    错误与诊断
    " + "".join(error_items) + "
    ") if error_items else "" body = (f"
    运行记录 / {esc(rid)}
    " - f"

    运行详情

    {info}" + f"

    运行详情

    {info}{error_html}" f"

    回执链

    {rc_html}

    质量评判

    {q_html}

    模型调用

    {call_html}" - f"

    候选与决策

    {cand}{dec}

    证据与上下文

    {raw_html}{freeze_html}") - return layout(f"运行 · {rid}", body, "/runs") + f"

    候选与决策

    {cand}{dec}

    证据与上下文

    {raw_html}{raw_detail_html}{freeze_html}") + return layout("运行详情", body, "/runs") # ── 智能体(07:读 102 登记表 example_agent_role / example_skill;权威在 Git 侧,登记脚本同步)──── def _fmt_list(v): - """JSONB 列表 → 逗号串;非列表原样转义。""" + """JSONB 列表 → 技术编码逗号串;非列表原样转义。""" if isinstance(v, list): - return esc(", ".join(str(x) for x in v)) if v else '∅' + return ", ".join(f"{esc(x)}" for x in v) if v else '∅' return esc(v) +_TOOL_LABELS = { + "Read": "读取", "Write": "写入", "Grep": "文本搜索", "Glob": "文件匹配", +} + + +def _fmt_tools(v): + """工具名称使用中文语义,CLI 工具名作为编码保留。""" + if not isinstance(v, list): + return esc(v) + if not v: + return '∅' + return ", ".join( + f"{html.escape(_TOOL_LABELS.get(str(item), '未登记'))} " + f"{esc(item)}" + for item in v + ) + + def view_agents(): _, roles = q("SELECT role, display_name, model, tools FROM example_agent_role " "WHERE deleted=false ORDER BY role") _, skills = q("SELECT skill_name, purpose, model_used FROM example_skill " "WHERE deleted=false ORDER BY skill_name") role_rows = "".join( - f"{esc(r[1] or r[0])} " - f"{esc(r[0])}" - f"{esc(r[2])}{_fmt_list(r[3])}" for r in roles) + f"{esc(r[1] or _ENUM_LABELS['agent_role'].get(r[0], '未登记'))} " + f"{esc(r[0])}" + f"{esc(r[2])}{_fmt_tools(r[3])}" for r in roles) skill_rows = "".join( - f"{esc(s[0])}" - f"{esc((s[1] or '')[:56])}{esc(s[2])}" for s in skills) - body = (f"

    智能体角色与技能 · 读 102 登记表(权威在 Git 侧,登记脚本同步)· Agent 07

    " + f"{enum_text(s[0], 'skill_name')}" + f"{annotated_text(short_text(s[1]))}{esc(s[2])}" for s in skills) + body = (f"

    智能体角色与技能 · 读取登记表(表编码 example_agent_role / example_skill)· 智能体 07

    " f"
    角色({len(roles)})
    " - f"{role_rows}
    角色模型工具
    " + f"{role_rows}
    角色名称 / 编码模型编码工具
    " f"
    技能({len(skills)})
    " - f"{skill_rows}
    技能用途用模型
    ") + f"{skill_rows}
    技能名称 / 编码用途使用模型编码
    ") return layout("智能体", body, "/agents") @@ -843,35 +2170,35 @@ def view_agent_role(role): if not rr: return layout("角色", "

    角色不存在

    ", "/agents") role_, disp, model, effort, budget, tools, resp, src, synced = rr[0] - info = (f"
    {esc(disp or role_)} {esc(role_)}
    " + info = (f"
    {esc(disp or _ENUM_LABELS['agent_role'].get(role_, '未登记'))} {esc(role_)}
    " f"" - f"" - f"" - f"" - f"" - f"" + f"" + f"" + f"" + f"" + f"" f"
    模型归属{esc(model)}
    工具{_fmt_list(tools)}
    可配置项effort {esc(effort)} · 预算 {esc(budget)}(暂空待补)
    出处{esc(src)} · 同步于 {esc(synced)}
    职责{esc(resp)}
    模型归属{esc(model)}
    工具{_fmt_tools(tools)}
    可配置项工作强度 effort:{esc(effort)} · 预算:{esc(budget)}
    出处编码{esc(src)} · 同步于 {esc(synced)}
    职责{annotated_text(resp)}
    ") _, runs = q("SELECT run_id, stage_kind, revision, create_time FROM example_run_receipt " "WHERE adapter_role=%s ORDER BY create_time DESC LIMIT 20", (role,)) runs_html = ("
    本角色运行(回执,最近 20)
    " - "" - + "".join(f"" for r in runs) + "" + + "".join(f"" for r in runs) + "
    run_id阶段revision时间
    {esc(r[0])}{esc(r[1])}{esc(r[2])}{esc(r[3])}
    运行编号 run_id阶段修订号 revision时间
    {esc(r[0])}{enum_text(r[1], 'stage_kind')}{esc(r[2])}{esc(r[3])}
    ") if runs else empty_gap("本角色暂无运行回执", "创作链落库后,该角色的运行会显示在这里。") _, calls = q("SELECT call_time, requested_model_id, out_tokens, cost_usd FROM example_llm_call " "WHERE caller=%s ORDER BY call_time DESC LIMIT 20", (role,)) calls_html = ("
    本角色模型调用(最近 20)
    " - "" - + "".join(f"" for c in calls) + "" + + "".join(f"" for c in calls) + "
    时间模型输出token成本
    {esc(c[0])}{esc(c[1])}{fmt_num(c[2])}${float(c[3] or 0):.5f}
    时间模型编码输出令牌数成本(美元)
    {esc(c[0])}{esc(c[1])}{fmt_num(c[2])}${float(c[3] or 0):.5f}
    ") if calls else empty_gap("本角色暂无模型调用", "") _, quals = q("SELECT q.judge_kind, q.dimension, q.conclusion, q.failure_class FROM example_quality_result q " "JOIN example_run_receipt r ON r.id=q.receipt_id WHERE r.adapter_role=%s ORDER BY q.create_time DESC LIMIT 20", (role,)) quals_html = ("
    本角色质量评判(最近 20)
    " "" - + "".join(f"" for u in quals) + + "".join(f"" for u in quals) + "
    类型维度结论失败类别
    {esc(u[0])}{esc(u[1])}{chip(u[2])}{esc(u[3])}
    {enum_text(u[0], 'judge_kind')}{esc(u[1])}{enum_chip(u[2], 'conclusion')}{esc(u[3])}
    ") if quals else empty_gap("本角色暂无质量评判", "") - body = (f"
    智能体 / {esc(disp or role_)}
    " - f"

    {esc(disp or role_)}

    {info}

    运行与产出

    {runs_html}{quals_html}{calls_html}") - return layout(f"角色 · {disp or role_}", body, "/agents") + body = (f"
    智能体 / {esc(disp or _ENUM_LABELS['agent_role'].get(role_, '未登记'))}
    " + f"

    {esc(disp or _ENUM_LABELS['agent_role'].get(role_, '未登记'))}

    {info}

    运行与产出

    {runs_html}{quals_html}{calls_html}") + return layout(f"角色 · {disp or _ENUM_LABELS['agent_role'].get(role_, '未登记')}", body, "/agents") def view_skill(name): @@ -880,26 +2207,29 @@ def view_skill(name): if not ss: return layout("技能", "

    技能不存在

    ", "/agents") sn, purpose, consumer, reads, writes, red_lines, model_used, src, synced = ss[0] - body = (f"
    智能体 / 技能 / {esc(sn)}
    " - f"

    {esc(sn)}

    " + skill_title = _ENUM_LABELS['skill_name'].get(sn, '未登记') + model_html = f"{esc(model_used)}" if model_used else "确定性工具(不用模型)" + body = (f"
    智能体 / 技能 / {esc(skill_title)}
    " + f"

    {esc(skill_title)} {esc(sn)}

    " f"
    " - f"" - f"" - f"" - f"" - f"" - f"" + f"" + f"" + f"" + f"" + f"" + f"" f"
    用途{esc(purpose)}
    给谁用{esc(consumer) if consumer else '待补'}
    用模型{esc(model_used) if model_used else '确定性工具(不用模型)'}
    涉及表{_fmt_list(reads)}
    自动抽取,读/写方向待人工核
    红线{esc(red_lines) if red_lines else '待补'}
    出处{esc(src)} · 同步于 {esc(synced)}
    用途{annotated_text(purpose)}
    给谁用{annotated_text(consumer) if consumer else '待补'}
    用模型{model_html}
    涉及表编码{_fmt_list(reads)}
    自动抽取,读/写方向待人工核
    红线{annotated_text(red_lines) if red_lines else '待补'}
    出处编码{esc(src)} · 同步于 {esc(synced)}
    ") - return layout(f"技能 · {sn}", body, "/agents") + return layout(f"技能 · {skill_title}", body, "/agents") # ── 路由 ───────────────────────────────────────────────────────────────────── _ROUTES = [ (re.compile(r"^/$"), lambda m, p: view_home()), (re.compile(r"^/works$"), lambda m, p: view_works()), - (re.compile(r"^/works/(\d+)$"), lambda m, p: view_work(int(m.group(1)))), + (re.compile(r"^/works/(\d+)$"), lambda m, p: view_work(int(m.group(1)), p)), (re.compile(r"^/works/(\d+)/ch/(\d+)$"), lambda m, p: view_chapter(int(m.group(1)), int(m.group(2)))), (re.compile(r"^/knowledge$"), lambda m, p: view_knowledge(p)), + (re.compile(r"^/ai-flavor$"), lambda m, p: view_ai_flavor(p)), (re.compile(r"^/agents$"), lambda m, p: view_agents()), (re.compile(r"^/agents/role/([\w-]+)$"), lambda m, p: view_agent_role(m.group(1))), (re.compile(r"^/agents/skill/([\w-]+)$"), lambda m, p: view_skill(m.group(1))), diff --git a/dashboard/test_server_display.py b/dashboard/test_server_display.py new file mode 100644 index 0000000..872823c --- /dev/null +++ b/dashboard/test_server_display.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +"""看板展示层的中文语义与编码合同测试,不连接数据库。""" +import pathlib +import sys +import unittest + + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) +import server # noqa: E402 + + +class DashboardDisplayTest(unittest.TestCase): + def test已知枚举显示中文语义和字符串编码(self): + text = server.enum_text("character", "entity_type") + self.assertIn("人物", text) + self.assertIn("character", text) + self.assertNotEqual(text, "character") + + def test未知英文枚举不裸露(self): + text = server.enum_text("future_state", "status") + self.assertIn("未登记", text) + self.assertIn("future_state", text) + + def test状态标签显示中文和编码(self): + text = server.chip("pending") + self.assertIn("待确认", text) + self.assertIn("pending", text) + + def test字段标题使用中文并保留协议字段编码(self): + text = server.field_label("运行编号", "run_id") + self.assertIn("运行编号", text) + self.assertIn("run_id", text) + + def test工具名称使用中文并保留_cli编码(self): + text = server._fmt_tools(["Read", "Grep"]) + self.assertIn("读取", text) + self.assertIn("文本搜索", text) + self.assertIn("Read", text) + self.assertIn("Grep", text) + + def test自由文本技术术语显示中文语义和编码(self): + text = server.annotated_text("writer sandbox + writer + Claude CLI + MiniMax + semantic detector + asOf") + self.assertIn("写手隔离环境", text) + self.assertIn("写手 writer sandbox", text) + + def test说明截断不会切断编码或误改_postgresql(self): + text = server.annotated_text(server.short_text("场景 scenario: fine_outline_replay/校验", 20)) + self.assertNotIn("fine_outl", text) + self.assertNotIn("Postgre数据库", server.annotated_text("PostgreSQL 查询")) + + def test详情自由文本中的运行时编码也有中文语义(self): + text = server.annotated_text( + "Claude CLI fresh/sandbox + revision journal (file_cas) + raw vault; " + "被 detect/quality-gate/continuation/replay-eval 复用" + ) + self.assertIn("模型命令行客户端", text) + self.assertIn("修订日志", text) + self.assertIn("原始证据仓", text) + self.assertIn("语义检测", text) + self.assertIn("质量门", text) + self.assertIn("实验库", server.annotated_text("muse-example")) + self.assertIn("正文块", server.annotated_text("正文 block")) + self.assertIn("file_cas", text) + + def test卡片下钻地址保留筛选条件(self): + href = server._query_href( + "/works/12", {"q": ["薄雾号"], "type": ["location"], "status": ["pending"]}, card=25438 + ) + self.assertIn("q=", href) + self.assertIn("type=location", href) + self.assertIn("status=pending", href) + self.assertIn("card=25438", href) + + def test章节展示中文标题并保留主键编码(self): + text = server._chapter_text(17474, 2, "称量") + self.assertIn("第2章《称量》", text) + self.assertIn("chapter_id=17474", text) + + def test运行描述包含用途角色和章节(self): + text = server._run_description( + ("run-prod-work12-ch2", 12, "深渊机神", 2, "user", "completed", "时间", + 17474, 2, "称量", "writer", "generation", "production", "writer", "production_generation") + ) + self.assertIn("正文生产", text) + self.assertIn("写手", text) + self.assertIn("第2章《称量》", text) + + def test原始_JSON_错误信息可读(self): + text = server._content_text('{"failureType":"InvalidTextRepresentation"}') + self.assertIn('"failureType": "InvalidTextRepresentation"', text) + + def test规划标题和摘要从载荷提取(self): + payload = {"chapterGoal": "林深被隔离审讯", "chapterEndHook": "茧准备出击"} + self.assertEqual(server._planning_title(payload, "fine_outline", 2), "第2章目标") + self.assertEqual(server._planning_summary(payload), "林深被隔离审讯") + + def test数据库AI味页面展示正式库汇总(self): + text = server.view_ai_flavor({}) + self.assertIn("AI 味案例", text) + self.assertIn("788", text) + self.assertIn("8", text) + self.assertIn("PostgreSQL muse-example 正式库", text) + self.assertIn("原文片段未保存", text) + self.assertIn("不是人工确认的 AI 味结论", text) + self.assertIn("来源已验证", text) + self.assertIn("788/788", text) + self.assertIn("batch_id=", text) + + def test数据库AI味页面支持作品分页与卡片详情(self): + filtered = server.view_ai_flavor({"work": ["ref:机破星河_当年离歌"], "page": ["2"]}) + self.assertIn("当前第 2/6 页", filtered) + self.assertIn("ref:机破星河_当年离歌", filtered) + detail = server.view_ai_flavor({"card": ["case-0b9ed3af70a7dbf315fc"]}) + self.assertIn("全文 SHA-256", detail) + self.assertIn("原文片段未保存", detail) + self.assertIn("来源已验证", detail) + + def test数据库案例详情显示用途和作用域边界(self): + detail = server.view_ai_flavor({"card": ["case-0b9ed3af70a7dbf315fc"]}) + self.assertIn("当前用途", detail) + self.assertIn("仅供本卡/本作品人工复核", detail) + self.assertIn("backfill", detail) + + def test数据库案例页面按当前数据库作品数动态渲染(self): + original_bundle = server._ai_db_bundle + try: + report, revalidation, error = original_bundle() + report = dict(report) + report["works"] = list(report["works"][:1]) + report["totals"] = dict(report.get("totals") or {}) + report["totals"]["books"] = 1 + report["cards"] = list(report["cards"][:1]) + report["totals"]["cards"] = 1 + server._ai_db_bundle = lambda: (report, revalidation, error) + text = server.view_ai_flavor({}) + finally: + server._ai_db_bundle = original_bundle + self.assertIn("按作品汇总(1 本)", text) + self.assertNotIn("按作品汇总(8 本)", text) + + +if __name__ == "__main__": + unittest.main() diff --git a/db/ddl/102-example智能体技能登记.sql b/db/ddl/102-example智能体技能登记.sql index 5a25a24..0804b73 100644 --- a/db/ddl/102-example智能体技能登记.sql +++ b/db/ddl/102-example智能体技能登记.sql @@ -1,4 +1,4 @@ --- 智能体与技能登记表:example_agent_role(5 角色)+ example_skill(24 技能)。 +-- 智能体与技能登记表:example_agent_role(5 角色)+ example_skill(目录扫描,不固定数量)。 -- 设计拍板 2026-07-30(docs/2026-07-30-落库与看板设计.md §2.10,A 方案)。 -- 用途:看板"智能体"空间的数据源。这些元数据的**权威在 Git 侧**(.claude/agents/*.md、 -- .claude/skills/*/SKILL.md);本表是 Git 权威在库内的**影子**(类比 muse_meta_function_chain @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS example_agent_role ( ); CREATE OR REPLACE TRIGGER trg_example_agent_role_updated_at BEFORE UPDATE ON example_agent_role FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); --- ══ example_skill:技能登记(24 个 skill)══ +-- ══ example_skill:技能登记(由目录扫描同步)══ CREATE TABLE IF NOT EXISTS example_skill ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, skill_name VARCHAR(64) NOT NULL, -- 技能标识(= skill 目录名 / frontmatter name) diff --git a/db/ddl/104-example-ai-flavor-cases.sql b/db/ddl/104-example-ai-flavor-cases.sql new file mode 100644 index 0000000..20f9b1a --- /dev/null +++ b/db/ddl/104-example-ai-flavor-cases.sql @@ -0,0 +1,142 @@ +-- AI 味案例卡、来源重验证批次与逐卡回执。 +-- agent-example 的 muse-example 是此资产的正式权威;离线 JSON 只是可复核的导入/恢复证据。 +-- 案例卡是当前投影(可按评审更新);批次与回执是 append-only,保留每次检查历史。 +-- 研究限定/未授权来源只允许 hash + 位置,数据库级约束禁止写入原文或升格。 + +CREATE TABLE IF NOT EXISTS example_ai_flavor_case ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + card_id VARCHAR(64) NOT NULL, + schema_version VARCHAR(64) NOT NULL, + card_type VARCHAR(64) NOT NULL, + state VARCHAR(20) NOT NULL, + label VARCHAR(20) NOT NULL, + layer VARCHAR(20) NOT NULL, + carrier VARCHAR(32) NOT NULL, + capture_mode VARCHAR(32) NOT NULL, + source_kind VARCHAR(32) NOT NULL, + source_license VARCHAR(32) NOT NULL, + source_sha256 VARCHAR(64) NOT NULL, + excerpt_sha256 VARCHAR(64) NOT NULL, + source_ref VARCHAR(512) NOT NULL DEFAULT '', + work_ref VARCHAR(256), + source_location JSONB NOT NULL, + surface_location JSONB, + excerpt TEXT NOT NULL DEFAULT '', + context TEXT NOT NULL DEFAULT '', + observation JSONB NOT NULL, + feedback JSONB, + review JSONB, + rule_candidate_ids JSONB NOT NULL DEFAULT '[]'::jsonb, + inventory_ref VARCHAR(512), + inventory_sha256 VARCHAR(64), + creator VARCHAR(64) NOT NULL DEFAULT '1', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '1', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 1, + CONSTRAINT uk_example_ai_flavor_case UNIQUE (tenant_id, card_id), + CONSTRAINT chk_example_ai_flavor_case_schema CHECK (schema_version = 'ai-flavor-case-v1'), + CONSTRAINT chk_example_ai_flavor_case_type CHECK (card_type = 'ai_flavor_case'), + CONSTRAINT chk_example_ai_flavor_case_state CHECK (state IN ('shadow','canonical','rejected','archived')), + CONSTRAINT chk_example_ai_flavor_case_label CHECK (label IN ('unclassified','sf','snf','boundary','regression')), + CONSTRAINT chk_example_ai_flavor_case_layer CHECK (layer IN ('mechanical','lexical','structural','density','semantic','unknown')), + CONSTRAINT chk_example_ai_flavor_case_carrier CHECK (carrier IN ('narration','dialogue','monologue','in_text_carrier','mixed','unknown')), + CONSTRAINT chk_example_ai_flavor_case_capture CHECK (capture_mode IN ('backfill','live_feedback','synthetic','review_import')), + CONSTRAINT chk_example_ai_flavor_case_source_kind CHECK (source_kind IN ('existing_work','creation_feedback','synthetic','public_domain')), + CONSTRAINT chk_example_ai_flavor_case_license CHECK (source_license IN ('owned','licensed','public_domain','research_only','unauthorized','synthetic')), + CONSTRAINT chk_example_ai_flavor_case_id CHECK (card_id ~ '^case-[a-z0-9]{20}$'), + CONSTRAINT chk_example_ai_flavor_case_source_sha CHECK (source_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_case_excerpt_sha CHECK (excerpt_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_case_inventory_sha CHECK (inventory_sha256 IS NULL OR inventory_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_case_json_arrays CHECK (jsonb_typeof(rule_candidate_ids) = 'array'), + CONSTRAINT chk_example_ai_flavor_case_research_only CHECK ( + source_license NOT IN ('research_only','unauthorized') + OR (excerpt = '' AND context = '' AND state = 'shadow') + ) +); +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_case_work_state + ON example_ai_flavor_case(tenant_id, work_ref, state) WHERE deleted = FALSE; +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_case_pattern + ON example_ai_flavor_case(tenant_id, ((observation->>'pattern_key'))) WHERE deleted = FALSE; +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_case_source_sha + ON example_ai_flavor_case(tenant_id, source_sha256); +CREATE OR REPLACE TRIGGER trg_example_ai_flavor_case_updated_at + BEFORE UPDATE ON example_ai_flavor_case FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TABLE IF NOT EXISTS example_ai_flavor_revalidation_batch ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + batch_ref VARCHAR(512) NOT NULL, + report_sha256 VARCHAR(64) NOT NULL, + schema_version VARCHAR(64) NOT NULL, + checked_on DATE NOT NULL, + source_root_ref VARCHAR(512), + usable BOOLEAN NOT NULL, + totals JSONB NOT NULL, + works JSONB NOT NULL DEFAULT '[]'::jsonb, + report_path VARCHAR(512), + creator VARCHAR(64) NOT NULL DEFAULT '1', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + tenant_id BIGINT NOT NULL DEFAULT 1, + CONSTRAINT uk_example_ai_flavor_revalidation_batch UNIQUE (tenant_id, report_sha256), + CONSTRAINT chk_example_ai_flavor_revalidation_batch_sha CHECK (report_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_revalidation_batch_schema CHECK (schema_version = 'ai-flavor-revalidation-v1'), + CONSTRAINT chk_example_ai_flavor_revalidation_batch_json CHECK ( + jsonb_typeof(totals) = 'object' AND jsonb_typeof(works) = 'array' + ) +); +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_revalidation_batch_date + ON example_ai_flavor_revalidation_batch(tenant_id, checked_on DESC, id DESC); + +CREATE TABLE IF NOT EXISTS example_ai_flavor_revalidation ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + batch_id BIGINT NOT NULL, + card_id VARCHAR(64) NOT NULL, + work_ref VARCHAR(256), + source_ref VARCHAR(512) NOT NULL DEFAULT '', + expected_source_sha256 VARCHAR(64) NOT NULL, + actual_source_sha256 VARCHAR(64), + status VARCHAR(20) NOT NULL, + reason VARCHAR(64) NOT NULL DEFAULT '', + checked_on DATE NOT NULL, + creator VARCHAR(64) NOT NULL DEFAULT '1', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + tenant_id BIGINT NOT NULL DEFAULT 1, + CONSTRAINT uk_example_ai_flavor_revalidation UNIQUE (tenant_id, batch_id, card_id), + CONSTRAINT chk_example_ai_flavor_revalidation_card CHECK (card_id ~ '^case-[a-z0-9]{20}$'), + CONSTRAINT chk_example_ai_flavor_revalidation_expected_sha CHECK (expected_source_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_revalidation_actual_sha CHECK (actual_source_sha256 IS NULL OR actual_source_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_ai_flavor_revalidation_status CHECK (status IN ('verified','stale','unavailable','card_mismatch')) +); +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_revalidation_card + ON example_ai_flavor_revalidation(tenant_id, card_id, checked_on DESC); +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_revalidation_status + ON example_ai_flavor_revalidation(tenant_id, status, checked_on DESC); +CREATE INDEX IF NOT EXISTS idx_example_ai_flavor_revalidation_batch + ON example_ai_flavor_revalidation(tenant_id, batch_id); + +CREATE OR REPLACE FUNCTION reject_example_ai_flavor_revalidation_mutation() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE EXCEPTION '% 是 append-only 表,禁止 UPDATE/DELETE', TG_TABLE_NAME; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_ai_flavor_revalidation_batch_append_only + BEFORE UPDATE OR DELETE ON example_ai_flavor_revalidation_batch + FOR EACH ROW EXECUTE FUNCTION reject_example_ai_flavor_revalidation_mutation(); +CREATE OR REPLACE TRIGGER trg_example_ai_flavor_revalidation_append_only + BEFORE UPDATE OR DELETE ON example_ai_flavor_revalidation + FOR EACH ROW EXECUTE FUNCTION reject_example_ai_flavor_revalidation_mutation(); +CREATE OR REPLACE TRIGGER trg_example_ai_flavor_revalidation_batch_no_truncate + BEFORE TRUNCATE ON example_ai_flavor_revalidation_batch + FOR EACH STATEMENT EXECUTE FUNCTION example_reject_truncate(); +CREATE OR REPLACE TRIGGER trg_example_ai_flavor_revalidation_no_truncate + BEFORE TRUNCATE ON example_ai_flavor_revalidation + FOR EACH STATEMENT EXECUTE FUNCTION example_reject_truncate(); + +GRANT SELECT ON example_ai_flavor_case TO muse_read; +GRANT SELECT ON example_ai_flavor_revalidation_batch TO muse_read; +GRANT SELECT ON example_ai_flavor_revalidation TO muse_read; diff --git a/db/ddl/105-example候选语义审查.sql b/db/ddl/105-example候选语义审查.sql new file mode 100644 index 0000000..bb2a801 --- /dev/null +++ b/db/ddl/105-example候选语义审查.sql @@ -0,0 +1,28 @@ +-- 先审后入加固(05 §5):候选接受必须同时通过机械门与语义审查。 +-- 背景:生产链曾在机械门通过后直接接受正文,语义 detector 未跑(双口径缺陷)。 +-- 本次把语义审查结果固化到候选行,write_canonical 接受通道在 DB 级兜底检查, +-- 不靠编排层自觉;冻结表同时补授权快照列,供接受前置检查做实时重读比对。 +-- 全部语句幂等:ADD COLUMN IF NOT EXISTS + 约束存在性检查,重复 apply 安全。 + +-- ══ example_candidate:补语义审查两列 ══ +-- semantic_status 是缓存位(权威是语义报告本身与 quality_result 账本),语义同 acceptance_eligible 的派生位模式。 +ALTER TABLE example_candidate ADD COLUMN IF NOT EXISTS semantic_status VARCHAR(20); +ALTER TABLE example_candidate ADD COLUMN IF NOT EXISTS semantic_report_sha256 CHAR(64); + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_example_candidate_semantic_status') THEN + ALTER TABLE example_candidate ADD CONSTRAINT chk_example_candidate_semantic_status + CHECK (semantic_status IS NULL + OR semantic_status IN ('passed','failed','needs_evidence','blocked')); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_example_candidate_semantic_sha256') THEN + ALTER TABLE example_candidate ADD CONSTRAINT chk_example_candidate_semantic_sha256 + CHECK (semantic_report_sha256 IS NULL OR semantic_report_sha256 ~ '^[0-9a-f]{64}$'); + END IF; +END +$$; + +-- ══ example_context_freeze:补授权快照列(接受前置检查实时重读用)══ +-- append-only 表加列不影响既有行;新冻结行落 authorizationSnapshot 全形(snapshotId/allowedPurpose/verifiedAt/sourceVersion)。 +ALTER TABLE example_context_freeze ADD COLUMN IF NOT EXISTS authorization_snapshot JSONB; diff --git a/db/ddl/106-example事实增量.sql b/db/ddl/106-example事实增量.sql new file mode 100644 index 0000000..120d8b5 --- /dev/null +++ b/db/ddl/106-example事实增量.sql @@ -0,0 +1,97 @@ +-- 事实增量两表:example_fact_delta(增量提案,可变状态机)+ example_fact_ledger(已接受正典事实,append-only)。 +-- 先审后入 + 结构化增量(05 §5、02/06 领域合同): +-- 模型只能提出有限类型化变更(DeltaProposal:人物位置/知情/关系/伏笔/时间线/设定),不能重写整份状态; +-- 提案必须带正文证据引文(确定性绑定到候选正文);抽取结果一律不自动升格; +-- 只有被批准的增量随正文在同一事务进账本(ChapterCommit),账本绑正文块 revision 与 command_id, +-- 正文被替换后按 source_block_revision 判 stale。 +-- example_fact_delta 可变(proposed→accepted/rejected/superseded)带公共列六件套; +-- example_fact_ledger 是 append-only 账本,只带归属列,不带 updater/update_time/deleted。 +-- 全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。 + +-- ══ example_fact_delta:增量提案(可变状态机)══ +CREATE TABLE IF NOT EXISTS example_fact_delta ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + work_id BIGINT NOT NULL, -- → muse_content_work.id(软引用) + target_chapter INTEGER NOT NULL, -- 提案所属章 + run_id VARCHAR(64), -- → example_run.run_id(软引用) + candidate_sha256 CHAR(64) NOT NULL, -- 绑候选正文:提案必须出自该正文的审查 + delta_id VARCHAR(64) NOT NULL, -- 本批提案内稳定 ID(delta-1…) + delta_type VARCHAR(40) NOT NULL, -- 类型闭集(见 chk) + subject_key VARCHAR(120), -- 变更主体(人物名/伏笔 ID/关系对),观察索引 + payload JSONB NOT NULL, -- 类型化变更内容(reducer 逐型校验) + evidence_quote TEXT, -- 正文证据引文(必须出现在候选正文中) + status VARCHAR(20) NOT NULL DEFAULT 'proposed', -- proposed/accepted/rejected/superseded + decided_by VARCHAR(64), -- 批准/驳回人(升格必须人批准) + decision_rationale TEXT, -- 理据 + source_revision INTEGER, -- 接受时正文块 revision(与账本登记同值) + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + CONSTRAINT uk_example_fact_delta UNIQUE (tenant_id, candidate_sha256, delta_id), + CONSTRAINT chk_example_fact_delta_type + CHECK (delta_type IN ('character_location_changed','character_knowledge_added', + 'relationship_changed','hook_advanced','timeline_event_added', + 'setting_added')), + CONSTRAINT chk_example_fact_delta_status + CHECK (status IN ('proposed','accepted','rejected','superseded')), + CONSTRAINT chk_example_fact_delta_sha256 + CHECK (candidate_sha256 ~ '^[0-9a-f]{64}$') +); +CREATE INDEX IF NOT EXISTS idx_example_fact_delta_work + ON example_fact_delta(tenant_id, work_id, target_chapter, status); +CREATE INDEX IF NOT EXISTS idx_example_fact_delta_subject + ON example_fact_delta(tenant_id, work_id, subject_key); +CREATE OR REPLACE TRIGGER trg_example_fact_delta_updated_at + BEFORE UPDATE ON example_fact_delta FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- ══ example_fact_ledger:已接受正典事实账本(append-only)══ +-- 一条 = 一个已随正文提交的事实增量;source_block_revision 是"这条事实由哪一版正文背书"的判据。 +CREATE TABLE IF NOT EXISTS example_fact_ledger ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + work_id BIGINT NOT NULL, -- → muse_content_work.id(软引用) + target_chapter INTEGER NOT NULL, -- 事实成立的章 + delta_ref BIGINT, -- → example_fact_delta.id(软引用) + delta_type VARCHAR(40) NOT NULL, + subject_key VARCHAR(120), + payload JSONB NOT NULL, + source_candidate_sha256 CHAR(64) NOT NULL, -- 产生该事实的候选正文哈希 + source_block_revision INTEGER NOT NULL, -- 同事务写入的正文块 revision + command_id VARCHAR(120), -- 与正文提交同一幂等键 + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + tenant_id BIGINT NOT NULL DEFAULT 0, + CONSTRAINT chk_example_fact_ledger_type + CHECK (delta_type IN ('character_location_changed','character_knowledge_added', + 'relationship_changed','hook_advanced','timeline_event_added', + 'setting_added')), + CONSTRAINT chk_example_fact_ledger_sha256 + CHECK (source_candidate_sha256 ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_fact_ledger_revision + CHECK (source_block_revision >= 1) +); +CREATE INDEX IF NOT EXISTS idx_example_fact_ledger_work + ON example_fact_ledger(tenant_id, work_id, target_chapter); +CREATE INDEX IF NOT EXISTS idx_example_fact_ledger_subject + ON example_fact_ledger(tenant_id, work_id, subject_key); +CREATE INDEX IF NOT EXISTS idx_example_fact_ledger_revision + ON example_fact_ledger(tenant_id, work_id, source_block_revision); + +CREATE OR REPLACE FUNCTION reject_example_fact_ledger_mutation() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE EXCEPTION 'example_fact_ledger 是 append-only 表,禁止 UPDATE/DELETE'; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_fact_ledger_append_only +BEFORE UPDATE OR DELETE ON example_fact_ledger +FOR EACH ROW EXECUTE FUNCTION reject_example_fact_ledger_mutation(); + +CREATE OR REPLACE TRIGGER trg_example_fact_ledger_no_truncate +BEFORE TRUNCATE ON example_fact_ledger +FOR EACH STATEMENT EXECUTE FUNCTION example_reject_truncate(); diff --git a/db/ddl/107-example投影登记.sql b/db/ddl/107-example投影登记.sql new file mode 100644 index 0000000..6750227 --- /dev/null +++ b/db/ddl/107-example投影登记.sql @@ -0,0 +1,66 @@ +-- 投影运行登记表:example_projection_run(可变注册表)。 +-- 摘要、handoff、embedding、章后抽取、看板缓存都只能是 Canonical 正文的幂等投影, +-- 不能反向成为事实源(08 数据权威)。本表把每个投影绑定到它派生自的正文 revision 与文本哈希: +-- 正文换新 revision 提交时,旧投影在同事务内翻 stale,新投影登记 pending; +-- 投影失败记 failed(带原因),绝不显示 completed;恢复走显式 retry(attempt+1)。 +-- 幂等键 (work, chapter, kind, revision) 唯一:同一提交重放不重复登记。 +-- 全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。 + +CREATE TABLE IF NOT EXISTS example_projection_run ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + work_id BIGINT NOT NULL, -- → muse_content_work.id(软引用) + target_chapter INTEGER, -- 章级投影填章号;书级投影为空 + kind VARCHAR(32) NOT NULL, -- 投影类型闭集(见 chk) + source_revision INTEGER NOT NULL, -- 派生自的正文块 revision + source_text_hash CHAR(64) NOT NULL, -- 派生时的正文文本哈希(裸 64 hex) + candidate_sha256 CHAR(64), -- 产生该正文的候选哈希(可空:非候选来源) + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/completed/failed/stale + attempt INTEGER NOT NULL DEFAULT 1, -- 重试次数(retry 时 +1) + detail JSONB, -- 失败原因/产物指针等安全摘要 + idempotency_key VARCHAR(160) NOT NULL, -- work:chapter:kind:revN(重放幂等) + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + CONSTRAINT uk_example_projection_run UNIQUE (tenant_id, idempotency_key), + CONSTRAINT chk_example_projection_run_kind + CHECK (kind IN ('summary','handoff','embedding','extraction','dashboard')), + CONSTRAINT chk_example_projection_run_status + CHECK (status IN ('pending','completed','failed','stale')), + CONSTRAINT chk_example_projection_run_counters + CHECK (attempt >= 1 AND source_revision >= 1), + CONSTRAINT chk_example_projection_run_text_hash + CHECK (source_text_hash ~ '^[0-9a-f]{64}$'), + CONSTRAINT chk_example_projection_run_candidate_hash + CHECK (candidate_sha256 IS NULL OR candidate_sha256 ~ '^[0-9a-f]{64}$') +); +CREATE INDEX IF NOT EXISTS idx_example_projection_run_work + ON example_projection_run(tenant_id, work_id, target_chapter, kind, status); +CREATE INDEX IF NOT EXISTS idx_example_projection_run_status + ON example_projection_run(tenant_id, status); +CREATE OR REPLACE TRIGGER trg_example_projection_run_updated_at + BEFORE UPDATE ON example_projection_run FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- 状态迁移闭集:pending→completed/failed/stale;failed→pending(retry);completed→stale; +-- stale 与 completed 不得退回 pending 之外的活动态由应用层保证,DB 只锁"终态不得伪造回写 completed"。 +CREATE OR REPLACE FUNCTION enforce_example_projection_run_transition() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + -- 只有 pending 的投影可以被报告完成;stale/failed/completed 不得直接"洗白"成 completed + IF NEW.status = 'completed' AND OLD.status <> 'pending' THEN + RAISE EXCEPTION '投影状态 % 不得直接置为 completed', OLD.status; + END IF; + IF OLD.status = 'stale' AND NEW.status IN ('completed','failed') THEN + RAISE EXCEPTION 'stale 投影不得再报告 completed/failed,必须先 retry 回 pending'; + END IF; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_projection_run_transition +BEFORE UPDATE ON example_projection_run +FOR EACH ROW EXECUTE FUNCTION enforce_example_projection_run_transition(); diff --git a/db/ddl/108-example经验升格.sql b/db/ddl/108-example经验升格.sql new file mode 100644 index 0000000..540a25c --- /dev/null +++ b/db/ddl/108-example经验升格.sql @@ -0,0 +1,67 @@ +-- 经验升格链登记表:example_lesson(可变状态机)。 +-- 质量与复利(06):运行中的教训(lesson)与成功(win)先登记为证据,绑定 run_id 与候选哈希; +-- 升格路径 proposed → reviewing → promoted/rejected,**只能人工批准**——脚本只负责登记与 +-- 状态流转,任何自动升格(单章结果直接污染全局范式/技能)都被状态机触发器拒绝。 +-- 升格目标三型:pattern(范式)/ skill(技能合同)/ tool(确定性工具),promoted 时必须指明。 +-- 全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。 + +CREATE TABLE IF NOT EXISTS example_lesson ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + work_id BIGINT, -- 可空:跨作品经验为空,单作品经验填作品 + run_id VARCHAR(64), -- → example_run.run_id(证据:哪次运行学到的) + candidate_sha256 CHAR(64), -- 证据:绑定候选正文哈希(可空:非正文类经验) + kind VARCHAR(20) NOT NULL, -- lesson(教训)/ win(成功经验) + title VARCHAR(200) NOT NULL, -- 一句话经验 + detail JSONB NOT NULL, -- 情境/行动/结果/证据指针(结构化) + status VARCHAR(20) NOT NULL DEFAULT 'proposed', -- proposed/reviewing/promoted/rejected + promotion_target_type VARCHAR(20), -- pattern/skill/tool(promoted 时必填) + promotion_target_ref VARCHAR(120), -- 升格落点(范式卡 ID / skill 名 / 工具路径) + decided_by VARCHAR(64), -- 批准/驳回人(升格必须人批准) + decision_rationale TEXT, -- 理据 + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + CONSTRAINT chk_example_lesson_kind CHECK (kind IN ('lesson','win')), + CONSTRAINT chk_example_lesson_status + CHECK (status IN ('proposed','reviewing','promoted','rejected')), + CONSTRAINT chk_example_lesson_target_type + CHECK (promotion_target_type IS NULL OR promotion_target_type IN ('pattern','skill','tool')), + CONSTRAINT chk_example_lesson_sha256 + CHECK (candidate_sha256 IS NULL OR candidate_sha256 ~ '^[0-9a-f]{64}$') +); +CREATE INDEX IF NOT EXISTS idx_example_lesson_work + ON example_lesson(tenant_id, work_id, status); +CREATE INDEX IF NOT EXISTS idx_example_lesson_status + ON example_lesson(tenant_id, status, kind); +CREATE OR REPLACE TRIGGER trg_example_lesson_updated_at + BEFORE UPDATE ON example_lesson FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- 升格状态机:proposed→reviewing/rejected;reviewing→promoted/rejected;promoted/rejected 终态。 +-- 跳过评审直接 promoted(自动升格)在 DB 级拒绝——升格必须经过人工 reviewing 环节。 +CREATE OR REPLACE FUNCTION enforce_example_lesson_transition() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + IF OLD.status = 'proposed' AND NEW.status NOT IN ('reviewing','rejected') THEN + RAISE EXCEPTION 'proposed 经验只能进 reviewing 或被 rejected,不得直接 %(禁止自动升格)', NEW.status; + END IF; + IF OLD.status = 'reviewing' AND NEW.status NOT IN ('promoted','rejected') THEN + RAISE EXCEPTION 'reviewing 经验只能 promoted 或 rejected,得到 %', NEW.status; + END IF; + IF OLD.status IN ('promoted','rejected') THEN + RAISE EXCEPTION '% 是经验终态,不得再流转', OLD.status; + END IF; + IF NEW.status = 'promoted' AND (NEW.promotion_target_type IS NULL OR NEW.promotion_target_ref IS NULL) THEN + RAISE EXCEPTION 'promoted 必须指明升格目标类型与落点'; + END IF; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_lesson_transition +BEFORE UPDATE ON example_lesson +FOR EACH ROW EXECUTE FUNCTION enforce_example_lesson_transition(); diff --git a/db/ddl/109-example候选状态CAS.sql b/db/ddl/109-example候选状态CAS.sql new file mode 100644 index 0000000..95a9943 --- /dev/null +++ b/db/ddl/109-example候选状态CAS.sql @@ -0,0 +1,97 @@ +-- 候选 CAS 状态链表:example_candidate_cas(可变注册表,一次运行一条链)。 +-- 目的:把 write-next-chapter/scripts/run_writer_pipeline.py 的内存 InMemoryCasStateStore +-- 持久化到 PostgreSQL,让重试、崩溃、旧候选写回和并发写可判断(05 §3 CAS 合同)。 +-- 链语义:run_id 唯一一条链;DRAFT→CHECKING→PASSED/REJECTED; +-- REJECTED 之后可由 start_next 开严格递增 attempt/candidate_version 的新一轮 DRAFT; +-- PASSED 是终态,不得再迁移。 +-- CAS 语义:所有迁移由写路径带期望 revision 的条件 UPDATE 完成,行计数 != 1 即竞争(返回 None, +-- 调用方收敛为 CAS_CONFLICT);DB 触发器再锁死合法迁移方向、revision 单调 +1 与身份不可变, +-- 即使调用方代码有缺陷也无法把链改成非法形状。 +-- 可变注册表带公共列六件套 + updated_at 触发器;全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。 + +CREATE TABLE IF NOT EXISTS example_candidate_cas ( + run_id VARCHAR(64) PRIMARY KEY, -- 运行标识(= example_run.run_id,一次运行一条 CAS 链) + work_id BIGINT, -- → muse_content_work.id(软引用,看板定位用) + target_chapter INTEGER, -- 目标章序(可空:非章级运行) + attempt INTEGER NOT NULL, -- 当前轮尝试号(start_next 严格递增) + candidate_version INTEGER NOT NULL, -- 当前轮候选版本(start_next 严格递增) + state VARCHAR(20) NOT NULL, -- DRAFT/CHECKING/PASSED/REJECTED + revision INTEGER NOT NULL DEFAULT 1, -- 链内单调号,每次迁移恰好 +1(CAS 期望值) + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + CONSTRAINT chk_example_candidate_cas_state + CHECK (state IN ('DRAFT','CHECKING','PASSED','REJECTED')), + CONSTRAINT chk_example_candidate_cas_counters + CHECK (revision >= 1 AND attempt >= 1 AND candidate_version >= 1) +); +CREATE INDEX IF NOT EXISTS idx_example_candidate_cas_work + ON example_candidate_cas(tenant_id, work_id, target_chapter); +CREATE OR REPLACE TRIGGER trg_example_candidate_cas_updated_at + BEFORE UPDATE ON example_candidate_cas FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- 初始行必须从 DRAFT / revision=1 开始,不允许凭空建中间态 +CREATE OR REPLACE FUNCTION enforce_example_candidate_cas_initial() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + IF NEW.state <> 'DRAFT' OR NEW.revision <> 1 THEN + RAISE EXCEPTION 'CAS 链必须以 DRAFT/revision=1 开始,得到 state=% revision=%', NEW.state, NEW.revision; + END IF; + IF NEW.attempt < 1 OR NEW.candidate_version < 1 THEN + RAISE EXCEPTION 'CAS 链初始 attempt/candidate_version 必须 >= 1'; + END IF; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_candidate_cas_initial +BEFORE INSERT ON example_candidate_cas +FOR EACH ROW EXECUTE FUNCTION enforce_example_candidate_cas_initial(); + +-- 迁移规则:身份不可变、revision 恰好 +1、方向闭集、新一轮必须严格递增 +CREATE OR REPLACE FUNCTION enforce_example_candidate_cas_transition() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + IF NEW.run_id <> OLD.run_id THEN + RAISE EXCEPTION 'CAS 链 run_id 不可变: %', OLD.run_id; + END IF; + IF OLD.deleted THEN + RAISE EXCEPTION 'CAS 链已软删,不得迁移: %', OLD.run_id; + END IF; + IF NEW.revision <> OLD.revision + 1 THEN + RAISE EXCEPTION 'CAS revision 必须恰好 +1: 当前 %,得到 %', OLD.revision, NEW.revision; + END IF; + IF OLD.state = 'PASSED' THEN + RAISE EXCEPTION 'PASSED 是 CAS 终态,不得再迁移'; + END IF; + IF OLD.state = 'DRAFT' AND NEW.state <> 'CHECKING' THEN + RAISE EXCEPTION 'DRAFT 只能迁移到 CHECKING,得到 %', NEW.state; + END IF; + IF OLD.state = 'CHECKING' AND NEW.state NOT IN ('PASSED','REJECTED') THEN + RAISE EXCEPTION 'CHECKING 只能迁移到 PASSED/REJECTED,得到 %', NEW.state; + END IF; + IF OLD.state = 'REJECTED' THEN + IF NEW.state <> 'DRAFT' THEN + RAISE EXCEPTION 'REJECTED 只能开新一轮 DRAFT,得到 %', NEW.state; + END IF; + IF NEW.attempt <= OLD.attempt OR NEW.candidate_version <= OLD.candidate_version THEN + RAISE EXCEPTION '新一轮 DRAFT 要求 attempt 与 candidate_version 严格递增: (% , %) -> (% , %)', + OLD.attempt, OLD.candidate_version, NEW.attempt, NEW.candidate_version; + END IF; + ELSIF NEW.attempt <> OLD.attempt OR NEW.candidate_version <> OLD.candidate_version THEN + RAISE EXCEPTION '同轮迁移不得改变 attempt/candidate_version'; + END IF; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE TRIGGER trg_example_candidate_cas_transition +BEFORE UPDATE ON example_candidate_cas +FOR EACH ROW EXECUTE FUNCTION enforce_example_candidate_cas_transition(); diff --git a/db/ddl/92-example清洗日志.sql b/db/ddl/92-example清洗日志.sql index 7ac7177..1b31e2f 100644 --- a/db/ddl/92-example清洗日志.sql +++ b/db/ddl/92-example清洗日志.sql @@ -1,4 +1,4 @@ --- 实验私货:LLM 辅助清洗的审计日志(清洗方案见 .claude/skills/clean/SKILL.md) +-- 实验私货:LLM 辅助清洗的审计日志(清洗方案见 .claude/skills/clean-book-text/SKILL.md) -- 每删一段留一行:删了什么、为什么、哪个模型建议、哪个批次——可回放、可回滚(源 txt 是不动的 SoT)。 CREATE TABLE example_clean_log ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, diff --git a/db/表映射.md b/db/表映射.md index f74b0bc..3641672 100644 --- a/db/表映射.md +++ b/db/表映射.md @@ -1,8 +1,8 @@ # muse-example 表 ↔ 主仓迁移来源映射(A2 审查面) > 口径(创始人拍板③ 2026-07-10):主仓表**原样不改列**;实验私货全进 `example_*` 前缀。 -> 建表方式:`db/ddl/` 下文件经 db skill `apply`,主仓部分为 `muse-cloud/sql/muse/` 原文拷贝或逐字摘录。 -> 已应用顺序:V1 → V3 → V5 → 90-ALTER摘录 → V26 → 91-example(2026-07-13)→ 97/98(2026-07-30)。库内表现状以 `db skill tables` 实时输出为准(当前 37 张;本文档清单可能滞后,94 作品面升格 5 表与 95 额度账本未在下文逐一登记)。 +> 建表方式:`db/ddl/` 下文件经 `access-database` skill `apply`,主仓部分为 `muse-cloud/sql/muse/` 原文拷贝或逐字摘录。 +> 已应用顺序:V1 → V3 → V5 → 90-ALTER摘录 → V26 → 91-example(2026-07-13)→ 97/98(2026-07-30)→ 104 AI 味案例(2026-08-14)→ 105/106/107/108/109 先审后入与复利闭环(2026-08-14)。库内表现状以 `access-database` skill `tables` 实时输出为准。 > 96 不启用:`96-example参考作品授权快照.sql` 已实现但**决定不 apply**(单用户本地不做多租户授权机制,2026-07-30 拍板,见领域索引 §9);库内无该表。 ## 主仓一致表(20 张) @@ -15,12 +15,12 @@ | muse_content_block_source_attribution | V1 | V30(authorization_snapshot_id→varchar(128)) | 暂空置;C 线来源归因 | | muse_content_import_task | V1 | — | B1 导入任务审计 | | muse_content_export_task | V1 | V9(+4 列) | 暂空置;C9 导出 | -| muse_content_command_log | V1 | — | 写命令幂等审计(import/confirm 用) | +| muse_content_command_log | V1 | — | 写命令幂等审计(import-book/decide-candidate 用) | | muse_meta_schema | V3 | — | 23 型结构本体行(A3 入库) | | muse_meta_schema_version | V3 | V10(active 唯一索引) | 型版本(A3:每型 v1 active) | | muse_meta_field | V3 | — | 逐型字段合同行(A3 入库) | | muse_meta_visibility_policy | V3 | — | aiContext 控制项落点(A3 入库) | -| muse_meta_protection_node | V3 | V10(+6 列) | 保护节点登记(read-context/confirm/judge) | +| muse_meta_protection_node | V3 | V10(+6 列) | 保护节点登记(assemble-context/decide-candidate/judge) | | muse_meta_function_chain | V3 | — | 功能链登记(meta/chains 的库内影子,随需入库) | | muse_knowledge_base | V5 | — | 私有参考书库 + 公共范式库(kb 行) | | muse_knowledge_document | V5 | — | 参考书原文档案指针(storage_ref 指仓内路径) | @@ -30,7 +30,7 @@ | muse_knowledge_draft | V5 | V14(两快照列→varchar(128)) | **草稿**知识行(B2 拆书产出落此) | | muse_knowledge_binding | V5 | V14(同上) | 作品↔库绑定(C3 起用) | -## 实验私货表(`db/ddl/91` + 92 + 93 + 97 + 98;94/95 见库内现状) +## 实验私货表(`db/ddl/91` + 92 + 93 + 97 + 98 + 104;94/95 见库内现状) | 表 | 用途 | |---|---| @@ -44,6 +44,15 @@ | example_run | 运行注册(98):一次运行一行(run_id 自然键 + 作品/触发/终态),运行级子表挂它 | | example_run_receipt | 运行身份回执(98):字段平移 runtime ExecutionReceipt ⊕ CAS 身份;append-only;唯一键 (run_id, sample_id, revision) | | example_quality_result | 质量评判账本(98):检测/评分/审核/实验,绑候选哈希 + 量表版本;append-only;有评判就记录就可见 | +| example_ai_flavor_case | AI 味案例卡当前投影(104):检测命中与创作反馈自动入库;shadow/canonical/rejected/archived 状态;研究限定来源只存 hash/位置/观察 | +| example_ai_flavor_revalidation_batch | AI 味来源重验证批次(104):一次检测/重验证运行的报告哈希、日期、汇总与可用性;append-only | +| example_ai_flavor_revalidation | AI 味逐卡重验证回执(104):verified/stale/unavailable/card_mismatch;append-only,按批次和卡片幂等 | +| example_candidate(105 加列) | 候选表补 `semantic_status`/`semantic_report_sha256`:语义审查结果固化,接受通道 DB 兜底复检(先审后入) | +| example_fact_delta | 事实增量提案(106):六型闭集 DeltaProposal,绑候选哈希 + 正文证据引文;proposed/accepted/rejected/superseded;抽取结果不自动升格 | +| example_fact_ledger | 正典事实账本(106):已批准增量随正文同事务入账,绑正文块 revision 与 command_id;append-only | +| example_projection_run | 投影登记(107):摘要/抽取/embedding 等派生物绑 source_revision+文本哈希;pending/completed/failed/stale;幂等键防重放,失败不冒充完成 | +| example_lesson | 经验升格登记(108):lesson/win 证据绑 run_id+候选哈希;proposed→reviewing→promoted/rejected,DB 触发器禁止跳过评审的自动升格 | +| example_candidate_cas | 候选 CAS 状态链(109):一次运行一条链(DRAFT/CHECKING/PASSED/REJECTED),revision 单调 +1,触发器锁方向闭集与身份不可变 | ## 暂缓建表登记(主仓有、实验现阶段未建;需要时按原样加建) @@ -63,6 +72,28 @@ - meta 字段行 sort_order 段位约定:1–9 基础字段,11 起特有字段。 - 双轨对应:B2 产出全落 `muse_knowledge_draft(status='pending')`;B5 管理员确认(仅创始人触发)= draft 翻 `confirmed` + 落 `muse_knowledge_entity(status='active')`;丢弃= draft 翻 `ignored`。 +## 知识卡查询速查(实际数据约定) + +> 本节是「现在库里实际长什么样、怎么查」,与上节「应该怎么建」互补。数字快照 2026-07-27。 + +1. **查公共范式卡,型藏在中文键里。** 公共范式的型不在 `draft_type` 列,而在 `draft_payload->>'型'`(中文键「型」)。查某型: + ```sql + SELECT count(*) FROM muse_knowledge_draft + WHERE work_id=0 AND deleted=false AND draft_payload->>'型'='craft'; + ``` + 五型分布(共 9269 张):trope 2300 / scene_pattern 2217 / craft 1892 / emotion 1549 / combat 1311。 + +2. **`draft_type` 列无区分力。** 全部活卡(19291 张)`draft_type` 恒为 `entity`,不能拿它筛型、也不能筛公共/作品。 + +3. **公共 vs 作品,看 `work_id` + payload「目标库」。** `work_id=0` + `目标库="公共范式库"` = 公共;`work_id=具体书`(深空之影=8)+ `目标库="本书作品库"` = 作品级。注意 `work_id=0` 是数字 0,不是 NULL。 + +4. **payload 型键有两套约定(坑)。** 公共范式卡用中文键 `型`(trope/craft…);作品级卡用英文键 `type`(character/item/faction…)。查作品卡型分布要用 `draft_payload->>'type'`。深空之影 991 张作品卡:character 527 / item 181 / character_relation 145 / location 89 / faction 42 / event 5 / power_system 2。 + +5. **style(文风画像)/pacing(节奏画像)已注册但 0 张卡。** 23 型 schema 全部入库且有激活版本,但拆书只抽五型(`deconstruct-book` 的 `PATTERN_TYPES` 写死)。style/pacing 是**书级画像**(整本书出一张),不属窗级范式五型管线,接入需另做书级画像抽取步骤,不要在五型枚举里硬加。 + - **作品级画像存储约定(创始人 2026-07-27 拍板)**:`work_id=8` + `目标库="本书作品库"` + 型键用**英文 `type`**(与作品卡一致,不用公共卡的中文 `型`),借此与 `work_id=0` 公共范式库隔离,并把两套键约定往统一靠。 + +6. **双轨后半段未跑:entity 表、binding 表当前都是空的。** `muse_knowledge_entity`、`muse_knowledge_binding` 均 0 行,所有卡 `status='pending'`。因此检索的「作品面」(仅已确认+已绑定)当前召不回东西,只有「管理面 admin」(含草稿)可用。公共卡与深空作品卡均已 100% 嵌入(9269/9269、991/991),嵌入不是瓶颈。 + ## 待应用实验表 | DDL | 表 | 用途 | 当前状态 | diff --git a/docs/2026-07-13-M3清洗拆书执行计划.md b/docs/2026-07-13-M3清洗拆书执行计划.md index 38dd018..7c216e0 100644 --- a/docs/2026-07-13-M3清洗拆书执行计划.md +++ b/docs/2026-07-13-M3清洗拆书执行计划.md @@ -30,7 +30,7 @@ flowchart LR ### P0 — llm skill 固化 + 首窗真跑(我自行决策,无确认门) -1. 新建 `.claude/skills/llm/`:封装 New-API chat 调用(默认 MiniMax-M3)——超时/重试、`` 剥离、JSON 提取容错、token 用量审计打印。所有后续调用走这一个入口,不裸调。 +1. 新建 `.claude/skills/call-content-model/`:封装 New-API chat 调用(默认 MiniMax-M3)——超时/重试、`` 剥离、JSON 提取容错、token 用量审计打印。所有后续调用走这一个入口,不裸调。 2. clean/parse-book 两个 skill 的文档与参数改口:模型标注 haiku→MiniMax-M3,编排方式从「Workflow 派子代理」改为「skill 脚本直调 M3」。 3. **首窗真跑**:机破星河(最脏书)win-001(第 1–39 章,10.1 万字)探测一次 → 验证窗口不超限、JSON 可解析、exact 逐字命中率。 - 若 10 万字窗超限或命中率差 → 我自行降窗(如 5 万字/窗,总调用数约 ×2,量级仍可接受),呈报时说明。 diff --git a/docs/2026-07-16-升格卡改造设计.md b/docs/2026-07-16-升格卡改造设计.md index cb359d0..29b546f 100644 --- a/docs/2026-07-16-升格卡改造设计.md +++ b/docs/2026-07-16-升格卡改造设计.md @@ -213,10 +213,10 @@ flowchart TB | `meta/schemas/faction.yaml` `location.yaml` `event.yaml` | 同上(建议同批补,避免二次改;见拍板点) | schema | | `meta/schemas/character.yaml` | 语义对齐:明确「成长弧线」=未来计划,已发生台阶归「演变历程」(是否给 character 也加演变历程见拍板点) | schema | | `meta/schemas/character_relation.yaml` | 「演变轨迹」说明里"章区间"沿用真实章号(本就如此),确认 aiContext 保持 `true` | schema | -| `.claude/skills/parse-book/scripts/parse_upgrade.py` | ①`APPEND_FIELDS` 加「演变历程」②observe/update 提示词改为记台阶+生命周期+真实章号+原文证据、禁窗号相对指代、当前态字段保持干净 ③合并/立卡/撤销逻辑适配里程碑对象(证据绑定、去重按内容、排序按章号)④判重步接语义近邻+M3 终判(P1) | prompt + 逻辑 | -| `.claude/skills/db/scripts/seed_schemas.py` | 不改脚本;schema YAML 改完后**需重跑一次**把新字段和 aiContext 灌进库(本稿不跑,实施时跑) | 种子 | -| `.claude/skills/read-context/SKILL.md` | Layer 2 知识卡裁剪说明里,点名"演变历程完整层仅一致性检查可见、续写不给"(机制已支持,补一句说明) | skill 文档 | -| `.claude/skills/detect/SKILL.md` | 检查项示例表补一行"演变历程 → 演变连续性/跳级穿帮"(实际自动生成,文档补例) | skill 文档 | +| `.claude/skills/deconstruct-book/scripts/parse_upgrade.py` | ①`APPEND_FIELDS` 加「演变历程」②observe/update 提示词改为记台阶+生命周期+真实章号+原文证据、禁窗号相对指代、当前态字段保持干净 ③合并/立卡/撤销逻辑适配里程碑对象(证据绑定、去重按内容、排序按章号)④判重步接语义近邻+M3 终判(P1) | prompt + 逻辑 | +| `.claude/skills/access-database/scripts/seed_schemas.py` | 不改脚本;schema YAML 改完后**需重跑一次**把新字段和 aiContext 灌进库(本稿不跑,实施时跑) | 种子 | +| `.claude/skills/assemble-context/SKILL.md` | Layer 2 知识卡裁剪说明里,点名"演变历程完整层仅一致性检查可见、续写不给"(机制已支持,补一句说明) | skill 文档 | +| `.claude/skills/check-content-consistency/SKILL.md` | 检查项示例表补一行"演变历程 → 演变连续性/跳级穿帮"(实际自动生成,文档补例) | skill 文档 | | 新增一次性迁移脚本(如 `docs/` 或 skill scripts 下) | 存量卡 [窗N] → 真实章号 + 字符串条目 → 里程碑对象 + 清脏(见 6.1) | 迁移 | **不需要改的**:数据库表结构(演变历程/前身/后继都是知识卡 JSON 里的字段,不需要 DDL 建表/改表);语义判重复用现成的 `example_knowledge_embedding` 向量表和 `embed`/`search` skill,不新建表。 diff --git a/docs/2026-07-16-批9c-收官与优化方案.md b/docs/2026-07-16-批9c-收官与优化方案.md index 346da24..d105321 100644 --- a/docs/2026-07-16-批9c-收官与优化方案.md +++ b/docs/2026-07-16-批9c-收官与优化方案.md @@ -26,7 +26,7 @@ ## 二、本轮踩坑与修复(勿回退) 1. **死锁解开**:原重审 wrapper 与深空残渣清洗钩子的 `pgrep -f` 交叉/自匹配互锁 → 杀钩子手动跑 `/tmp/muse-clean-debris.py 8` + 会话接管深空重审。**教训:解死锁最小动作优先**(曾多叠一个接管控制器、把正常在审的深空打断,纯属过度设计)。 -2. **`review_cards.py` 修 3 处**(`.claude/skills/review-cards/scripts/review_cards.py`,工作树已改未提交): +2. **`review_cards.py` 修 3 处**(`.claude/skills/review-knowledge-cards/scripts/review_cards.py`,工作树已改未提交): - **敏感降级链** `DEGRADE_CHAIN=["MiniMax-M3","MiniMax-M2.7","deepseek-v4-flash","glm-5.2"]` + `chat_degrade_review()`;全链失败标 blocked 不崩。 - **分片** `--shard i/N`(id%N==i),多进程并发各自独立事务。 - **连接拆「读/算/写」三段**:LLM 阶段不持 DB 连接、写库用全新短连接(治 Tailscale 长事务空转超时,实测星环/机动崩因);+ reviews 条目 `isinstance(r,dict)` 守卫(防模型把条目返成 list 崩)。 @@ -42,7 +42,7 @@ - 已知:New-API `/api/usage/token/` 聚合口径异步滞后且高频轮询会限流返非 JSON(与计价正确性无关,勿据此判错)。 - 下方 spec 为实现依据,保留备查。 -全局改 llm skill(**只改 `.claude/skills/llm/scripts/llm.py` + 新建 `example_llm_quota` 库表 DDL 落 `db/ddl/`**): +全局改 llm skill(**只改 `.claude/skills/call-content-model/scripts/llm.py` + 新建 `example_llm_quota` 库表 DDL 落 `db/ddl/`**): - **以钱为主**。每次调用:`cost_usd = model_ratio × ((prompt_tokens - cached_tokens) + cached_tokens×cache_ratio + completion_tokens×completion_ratio) / 500000`(New-API 口径,$1=500000 配额单位)。费率从 `GET http://100.64.0.8:3000/api/pricing` 取,硬编码兜底:**M3=0.15/4/0.2、M2.7=0.15/4/0.2、glm-5.2=0.5634/3.5/0.25、deepseek-v4-flash=0.07/2/0.0714**(model_ratio/completion_ratio/cache_ratio)。 - **时间窗**:按钟点 floor 到 00/05/10/15/20(每天 5 窗,末窗 20–24=4h),每窗独立、跨窗清零。用 `datetime.now()`(普通 python 可用)。 diff --git a/docs/2026-07-19-回放评测-细纲首跑设计与计划.md b/docs/2026-07-19-回放评测-细纲首跑设计与计划.md index e91aab9..45515aa 100644 --- a/docs/2026-07-19-回放评测-细纲首跑设计与计划.md +++ b/docs/2026-07-19-回放评测-细纲首跑设计与计划.md @@ -20,7 +20,7 @@ - 修复后重新装配并运行真实 dry-run:授权有效,三臂公共输入哈希一致,仅卡注入分区不同,`leakageAudit.findingCount=0`,状态为 `ready`。 - 已尝试启动三臂模型实跑,但第一臂 planner 前被 Claude 推理网关 `503 No available accounts` 阻断;随后极小 Opus 连通性探测再次得到相同 503。两次均为 0 输入 token、0 输出 token、0 费用,未产生候选细纲,后两臂、detector 和双 judge 均未执行。 - 当前状态:**授权、真实装配、上游章号证据绑定和真实 dry-run 已通;模型实跑仅被外部 Opus 通道阻塞**。通道恢复后必须从完整三臂重新执行,不能复用这次未完成的第一臂,也不能换模型污染变量控制。 -- `.claude/skills/llm/scripts/test_quota.py` 已统一到当前 `$24/6000` 契约:预算场景使用 `$24.5`,调用上限场景使用 `6000`,并增加策略常量断言。 +- `.claude/skills/call-content-model/scripts/test_quota.py` 已统一到当前 `$24/6000` 契约:预算场景使用 `$24.5`,调用上限场景使用 `6000`,并增加策略常量断言。 - 当前允许的结论是“授权、真实数据装配、冻结、变量控制、上游里程碑证据绑定、内容级泄露阻断、候选结构门、detector/judge 双评编排和安全摘要机制已通”;不能说细纲智能体或知识卡已通过。489 样本等待 Opus 通道恢复后执行,430/550 样本仍待执行。 --- @@ -63,10 +63,10 @@ | `design-docs/专题-03-AI编排上下文与质量评测实现规范.md` §4、§5.3、§8 | Context Assembly 四层、用途授权、来源版本、授权快照、运行权限包和评测输入追踪不可省略 | 每臂生成输入 manifest;缺 source/version/evidence/authorization 的块不进 prompt | | `design-docs/专题-04-生成质量门控与创作健康度设计方案.md` §10 | 离线评估要绑定 AI task、agent/prompt/context/authorization/source/quality policy 版本 | 运行 ID 绑定 planner 版本、skill 版本、快照哈希、评委 rubric 版本 | | `design-docs/专题-06-元数据驱动的智能体架构.md` §6.2、§6.4、§7 | 参考作品面是系统侧证据;读取器按 `planning` purpose 做字段裁剪;演变历程只在规划侧可见 | 卡视图只取 `章 <= N` 的历史,并重新推导 N 时点状态;不把终态存储字段直接透传 | -| `.claude/skills/planning/SKILL.md` | 规划产出按 schema 字段全覆盖,写不出必须显式标“字段存疑”;产物是草稿,不写正文 | 细纲候选使用 `chapter`/`outline` 已有字段,不新造“细纲卡”一级模型 | -| `.claude/skills/read-context/SKILL.md` | 所有作品上下文必须经过统一读取器;planner purpose 看全局规划面;输出裁剪回显 | 回放脚本负责冻结和登记,实际 prompt 采用 read-context 兼容的四层包结构 | -| `.claude/skills/detect/SKILL.md` | 检查项由 schema 的 detection 字段驱动;只产报告,不修改候选 | 审查细纲目标、事件顺序、伏笔动作、实体当前态和未来信息来源 | -| `.claude/skills/quality-gate/SKILL.md` | 评分必须有引文证据,评委不可自行放宽标准 | 为规划回放增加独立 rubric profile;不把正文 8 维直接复用到细纲 | +| `.claude/skills/plan-story/SKILL.md` | 规划产出按 schema 字段全覆盖,写不出必须显式标“字段存疑”;产物是草稿,不写正文 | 细纲候选使用 `chapter`/`outline` 已有字段,不新造“细纲卡”一级模型 | +| `.claude/skills/assemble-context/SKILL.md` | 所有作品上下文必须经过统一读取器;planner purpose 看全局规划面;输出裁剪回显 | 回放脚本负责冻结和登记,实际 prompt 采用 read-context 兼容的四层包结构 | +| `.claude/skills/check-content-consistency/SKILL.md` | 检查项由 schema 的 detection 字段驱动;只产报告,不修改候选 | 审查细纲目标、事件顺序、伏笔动作、实体当前态和未来信息来源 | +| `.claude/skills/score-content-quality/SKILL.md` | 评分必须有引文证据,评委不可自行放宽标准 | 为规划回放增加独立 rubric profile;不把正文 8 维直接复用到细纲 | | `docs/2026-07-16-批9c-收官与优化方案.md` run3 | 卡是索引,不能挤掉原文;n=1 不下结论;内容级错位可能造成泄露 | 规划层把这三条变成输入冻结、样本分层和结论门禁 | ### 2.1 现状证据 @@ -290,14 +290,14 @@ arm ∈ {outline_only, outline_plus_cards, outline_plus_placebo_cards} **Files:** -- Create: `.claude/skills/replay-eval/SKILL.md` -- Create: `.claude/skills/replay-eval/scripts/build_snapshot.py` -- Create: `.claude/skills/replay-eval/scripts/check_snapshot.py` -- Create: `.claude/skills/replay-eval/scripts/audit_leakage.py` -- Create: `.claude/skills/replay-eval/scripts/load_reference_work.py` -- Test: `.claude/skills/replay-eval/scripts/test_snapshot.py` -- Test: `.claude/skills/replay-eval/scripts/test_audit_leakage.py` -- Test: `.claude/skills/replay-eval/scripts/test_load_reference_work.py` +- Create: `.claude/skills/evaluate-frozen-replay/SKILL.md` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/build_snapshot.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/check_snapshot.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/audit_leakage.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/load_reference_work.py` +- Test: `.claude/skills/evaluate-frozen-replay/scripts/test_snapshot.py` +- Test: `.claude/skills/evaluate-frozen-replay/scripts/test_audit_leakage.py` +- Test: `.claude/skills/evaluate-frozen-replay/scripts/test_load_reference_work.py` - [x] 建立 `next_fine_outline_replay_v0` manifest:作品、N、目标章、评估集版本、策略版本、授权快照、来源版本、运行权限包和臂配置。 - [x] 按绝对章号构造 L0/L1/L2;完整窗口必须使用 `to_order <= N`,不能依赖 `window_no`。 @@ -311,10 +311,10 @@ arm ∈ {outline_only, outline_plus_cards, outline_plus_placebo_cards} **Files:** -- Create: `.claude/skills/fine-outline/SKILL.md` +- Create: `.claude/skills/plan-chapter/SKILL.md` - Modify: `.claude/agents/planner.md` - Modify: `meta/chains/README.md` -- Test: `.claude/skills/fine-outline/scripts/test_contract.py` +- Test: `.claude/skills/plan-chapter/scripts/test_contract.py` - [x] 把细纲定义为 planner 的一个 planning 场景,不新造一级知识模型或新 agent 类型。 - [x] 固定候选字段、未知项纪律、不得写正文/正式规划的 Shadow 边界。 @@ -325,10 +325,10 @@ arm ∈ {outline_only, outline_plus_cards, outline_plus_placebo_cards} **Files:** -- Modify: `.claude/skills/detect/SKILL.md` -- Modify: `.claude/skills/quality-gate/SKILL.md` +- Modify: `.claude/skills/check-content-consistency/SKILL.md` +- Modify: `.claude/skills/score-content-quality/SKILL.md` - Modify: `.claude/agents/judge.md` -- Test: `.claude/skills/replay-eval/scripts/test_rubric.py` +- Test: `.claude/skills/evaluate-frozen-replay/scripts/test_rubric.py` - [x] 给 detector 增加细纲候选的检查对象、严重度和证据格式。 - [x] 给 judge 增加 `fine_outline_replay` rubric profile,明确不评正文文风和文笔。 @@ -339,9 +339,9 @@ arm ∈ {outline_only, outline_plus_cards, outline_plus_placebo_cards} **Files:** -- Create: `.claude/skills/replay-eval/scripts/run_replay.py` -- Create: `.claude/skills/replay-eval/scripts/write_report.py` -- Test: `.claude/skills/replay-eval/scripts/test_run_replay.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/run_replay.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/write_report.py` +- Test: `.claude/skills/evaluate-frozen-replay/scripts/test_run_replay.py` - Create: `docs/replay/-summary.md` - [x] 先用合成 fixture 完成机制 dry-run,并用 fake planner 验证 execute 链路;真实 489 样本已完成确定性 dry-run 并因内容泄露正确阻断。 diff --git a/docs/2026-07-24-Gate-A-正文智能体职责重构记录.md b/docs/2026-07-24-Gate-A-正文智能体职责重构记录.md index 4a054ca..e51b104 100644 --- a/docs/2026-07-24-Gate-A-正文智能体职责重构记录.md +++ b/docs/2026-07-24-Gate-A-正文智能体职责重构记录.md @@ -9,7 +9,7 @@ - Writer、冻结和输出合同:[父仓专题-03](../../design-docs/专题-03-AI编排上下文与质量评测实现规范.md)。 - Gate 裁决:[父仓专题-04](../../design-docs/专题-04-生成质量门控与创作健康度设计方案.md)。 - adapter 隔离和执行边界:[父仓专题-05](../../design-docs/专题-05-AI统一交互协议与外部AgentAdapter设计.md)。 -- Gate A 具体执行配置:`.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json`。 +- Gate A 具体执行配置:`.claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json`。 ## 背景 diff --git a/docs/2026-07-30-落库与看板设计.md b/docs/2026-07-30-落库与看板设计.md index 0b66555..7726975 100644 --- a/docs/2026-07-30-落库与看板设计.md +++ b/docs/2026-07-30-落库与看板设计.md @@ -39,7 +39,7 @@ - **append-only 表**(回执/决策/冻结/raw/质量结果)用 BEFORE UPDATE OR DELETE 触发器禁改写(仿 `96`);可变表(候选/规划)带 `trg_*_updated_at`。 - 唯一键含归属、命名 `uk_example_*`;CHECK `chk_example_*`;索引 `idx_example_*`。 - **哈希归一规则**:runtime/CAS 系用 `sha256:`(71 字符),snapshot 系用裸 64 hex。**入库一律剥 `sha256:` 前缀存裸 64 hex**,列统一 `CHAR(64)`;读取按列语义决定是否复原。所有 `*_sha256` 列同此。 -- 一律 `.venv/bin/python .claude/skills/db/scripts/db.py apply db/ddl/<文件>`,整文件单事务回滚。 +- 一律 `.venv/bin/python .claude/skills/access-database/scripts/db.py apply db/ddl/<文件>`,整文件单事务回滚。 ## 2. 表设计 diff --git a/docs/superpowers/plans/2026-07-20-writer-agent-v1.md b/docs/superpowers/plans/2026-07-20-writer-agent-v1.md index 7f01c96..8ce1238 100644 --- a/docs/superpowers/plans/2026-07-20-writer-agent-v1.md +++ b/docs/superpowers/plans/2026-07-20-writer-agent-v1.md @@ -95,8 +95,8 @@ git -C /Users/qingse/Sync/local-git/oh-my-muse commit \ - Modify: `meta/schemas/generation_context.yaml` - Modify: `meta/schemas/outline.yaml` - Modify: `meta/schemas/README.md` -- Create: `.claude/skills/read-context/scripts/writer_contract.py` -- Create: `.claude/skills/read-context/scripts/test_writer_contract.py` +- Create: `.claude/skills/assemble-context/scripts/writer_contract.py` +- Create: `.claude/skills/assemble-context/scripts/test_writer_contract.py` - [ ] 先写失败测试,覆盖: - `runId` 不参与 manifest identity;相同输入得到相同 hash。 @@ -108,7 +108,7 @@ git -C /Users/qingse/Sync/local-git/oh-my-muse commit \ ```bash .venv/bin/python \ - .claude/skills/read-context/scripts/test_writer_contract.py -v + .claude/skills/assemble-context/scripts/test_writer_contract.py -v ``` - [ ] 实现 `normalize_text()`、`canonical_json()`、`retrieval_identity()`、`han_count()`、`calculate_target_chars()`。 @@ -120,18 +120,18 @@ git -C /Users/qingse/Sync/local-git/oh-my-muse commit \ ```bash git add meta/schemas/generation_context.yaml meta/schemas/outline.yaml meta/schemas/README.md \ - .claude/skills/read-context/scripts/writer_contract.py \ - .claude/skills/read-context/scripts/test_writer_contract.py + .claude/skills/assemble-context/scripts/writer_contract.py \ + .claude/skills/assemble-context/scripts/test_writer_contract.py git commit -m "实现: 建立正文上下文与输出严格契约" ``` ## 任务 2:实现确定性的卡检索和冻结原文读取 **Files:** -- Modify: `.claude/skills/search/scripts/search.py` -- Modify: `.claude/skills/replay-eval/scripts/load_reference_work.py` -- Create: `.claude/skills/read-context/scripts/retrieve_writer_sources.py` -- Create: `.claude/skills/read-context/scripts/test_retrieve_writer_sources.py` +- Modify: `.claude/skills/search-knowledge/scripts/search.py` +- Modify: `.claude/skills/evaluate-frozen-replay/scripts/load_reference_work.py` +- Create: `.claude/skills/assemble-context/scripts/retrieve_writer_sources.py` +- Create: `.claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py` - [ ] 先写失败测试,使用 fake repository 覆盖: - 卡片按 `score DESC, source_version ASC, source_id ASC, source_offset ASC` 稳定排序。 @@ -145,9 +145,9 @@ git commit -m "实现: 建立正文上下文与输出严格契约" ```bash .venv/bin/python \ - .claude/skills/read-context/scripts/test_retrieve_writer_sources.py -v + .claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py -v .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_load_reference_work.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_load_reference_work.py -v ``` - [ ] 从 `search.py` 提取可复用的 `search_cards()`,CLI 继续调用同一实现,避免两套检索语义。 @@ -158,19 +158,19 @@ git commit -m "实现: 建立正文上下文与输出严格契约" - [ ] Commit: ```bash -git add .claude/skills/search/scripts/search.py \ - .claude/skills/replay-eval/scripts/load_reference_work.py \ - .claude/skills/read-context/scripts/retrieve_writer_sources.py \ - .claude/skills/read-context/scripts/test_retrieve_writer_sources.py +git add .claude/skills/search-knowledge/scripts/search.py \ + .claude/skills/evaluate-frozen-replay/scripts/load_reference_work.py \ + .claude/skills/assemble-context/scripts/retrieve_writer_sources.py \ + .claude/skills/assemble-context/scripts/test_retrieve_writer_sources.py git commit -m "实现: 卡索引驱动冻结原文检索" ``` ## 任务 3:组装双证据 WriterContext **Files:** -- Create: `.claude/skills/read-context/scripts/assemble_writer_context.py` -- Create: `.claude/skills/read-context/scripts/test_assemble_writer_context.py` -- Modify: `.claude/skills/read-context/SKILL.md` +- Create: `.claude/skills/assemble-context/scripts/assemble_writer_context.py` +- Create: `.claude/skills/assemble-context/scripts/test_assemble_writer_context.py` +- Modify: `.claude/skills/assemble-context/SKILL.md` - [ ] 先写失败测试,覆盖: - 连续前 4 章全文作为基础 prose evidence;不足 4 章时从第一章开始,不重复。 @@ -182,7 +182,7 @@ git commit -m "实现: 卡索引驱动冻结原文检索" ```bash .venv/bin/python \ - .claude/skills/read-context/scripts/test_assemble_writer_context.py -v + .claude/skills/assemble-context/scripts/test_assemble_writer_context.py -v ``` - [ ] 实现确定性的 `assemble_context()`,输出 JSON 和可供人审阅的 Markdown manifest。 @@ -192,9 +192,9 @@ git commit -m "实现: 卡索引驱动冻结原文检索" - [ ] Commit: ```bash -git add .claude/skills/read-context/SKILL.md \ - .claude/skills/read-context/scripts/assemble_writer_context.py \ - .claude/skills/read-context/scripts/test_assemble_writer_context.py +git add .claude/skills/assemble-context/SKILL.md \ + .claude/skills/assemble-context/scripts/assemble_writer_context.py \ + .claude/skills/assemble-context/scripts/test_assemble_writer_context.py git commit -m "实现: 组装正文事实与文风双证据上下文" ``` @@ -202,10 +202,10 @@ git commit -m "实现: 组装正文事实与文风双证据上下文" **Files:** - Modify: `.claude/agents/writer.md` -- Modify: `.claude/skills/continuation/SKILL.md` +- Modify: `.claude/skills/write-next-chapter/SKILL.md` - Modify: `README.md` -- Create: `.claude/skills/continuation/scripts/run_writer.py` -- Create: `.claude/skills/continuation/scripts/test_run_writer.py` +- Create: `.claude/skills/write-next-chapter/scripts/run_writer.py` +- Create: `.claude/skills/write-next-chapter/scripts/test_run_writer.py` - [ ] 先写失败测试,fake subprocess 断言: - adapter 使用 `--tools ""` 和 `--no-session-persistence`。 @@ -217,7 +217,7 @@ git commit -m "实现: 组装正文事实与文风双证据上下文" ```bash .venv/bin/python \ - .claude/skills/continuation/scripts/test_run_writer.py -v + .claude/skills/write-next-chapter/scripts/test_run_writer.py -v ``` - [ ] 从 `writer.md` 移除 `Read/Write/Grep/Glob` 工具声明,明确禁止自行检索和写文件;adapter 以参数列表调用 CLI,禁止 shell 字符串拼接。 @@ -229,9 +229,9 @@ git commit -m "实现: 组装正文事实与文风双证据上下文" - [ ] Commit: ```bash -git add .claude/agents/writer.md .claude/skills/continuation/SKILL.md README.md \ - .claude/skills/continuation/scripts/run_writer.py \ - .claude/skills/continuation/scripts/test_run_writer.py +git add .claude/agents/writer.md .claude/skills/write-next-chapter/SKILL.md README.md \ + .claude/skills/write-next-chapter/scripts/run_writer.py \ + .claude/skills/write-next-chapter/scripts/test_run_writer.py git commit -m "实现: 无工具正文写手与动态篇幅控制" ``` @@ -239,11 +239,11 @@ git commit -m "实现: 无工具正文写手与动态篇幅控制" **Files:** - Modify: `.claude/agents/detector.md` -- Modify: `.claude/skills/detect/SKILL.md` -- Create: `.claude/skills/detect/scripts/check_writer_candidate.py` -- Create: `.claude/skills/detect/scripts/test_check_writer_candidate.py` -- Create: `.claude/skills/continuation/scripts/run_writer_pipeline.py` -- Create: `.claude/skills/continuation/scripts/test_run_writer_pipeline.py` +- Modify: `.claude/skills/check-content-consistency/SKILL.md` +- Create: `.claude/skills/check-content-consistency/scripts/check_writer_candidate.py` +- Create: `.claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py` +- Create: `.claude/skills/write-next-chapter/scripts/run_writer_pipeline.py` +- Create: `.claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py` - [ ] 先写失败测试,覆盖: - 细纲关键事件、角色、伏笔、章末钩子是硬约束,缺一项即不通过。 @@ -255,9 +255,9 @@ git commit -m "实现: 无工具正文写手与动态篇幅控制" ```bash .venv/bin/python \ - .claude/skills/detect/scripts/test_check_writer_candidate.py -v + .claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py -v .venv/bin/python \ - .claude/skills/continuation/scripts/test_run_writer_pipeline.py -v + .claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py -v ``` - [ ] 实现机械校验器,模型 detector 只负责无法机械判定的语义项。 @@ -267,21 +267,21 @@ git commit -m "实现: 无工具正文写手与动态篇幅控制" - [ ] Commit: ```bash -git add .claude/agents/detector.md .claude/skills/detect/SKILL.md \ - .claude/skills/detect/scripts/check_writer_candidate.py \ - .claude/skills/detect/scripts/test_check_writer_candidate.py \ - .claude/skills/continuation/scripts/run_writer_pipeline.py \ - .claude/skills/continuation/scripts/test_run_writer_pipeline.py +git add .claude/agents/detector.md .claude/skills/check-content-consistency/SKILL.md \ + .claude/skills/check-content-consistency/scripts/check_writer_candidate.py \ + .claude/skills/check-content-consistency/scripts/test_check_writer_candidate.py \ + .claude/skills/write-next-chapter/scripts/run_writer_pipeline.py \ + .claude/skills/write-next-chapter/scripts/test_run_writer_pipeline.py git commit -m "实现: 正文细纲硬审查与有限收敛循环" ``` ## 任务 6:实现接受前置检查,隔离评测产物 **Files:** -- Modify: `.claude/skills/confirm/SKILL.md` +- Modify: `.claude/skills/decide-candidate/SKILL.md` - Modify: `meta/chains/README.md` -- Create: `.claude/skills/confirm/scripts/check_writer_acceptance.py` -- Create: `.claude/skills/confirm/scripts/test_writer_acceptance.py` +- Create: `.claude/skills/decide-candidate/scripts/check_writer_acceptance.py` +- Create: `.claude/skills/decide-candidate/scripts/test_writer_acceptance.py` - [ ] 先写失败测试,覆盖: - `acceptanceEligible=false` 的诊断/评测候选不可接受。 @@ -292,7 +292,7 @@ git commit -m "实现: 正文细纲硬审查与有限收敛循环" ```bash .venv/bin/python \ - .claude/skills/confirm/scripts/test_writer_acceptance.py -v + .claude/skills/decide-candidate/scripts/test_writer_acceptance.py -v ``` - [ ] 实现纯函数 preflight,实验台仅验证状态转换,不写正式正文库。 @@ -301,9 +301,9 @@ git commit -m "实现: 正文细纲硬审查与有限收敛循环" - [ ] Commit: ```bash -git add .claude/skills/confirm/SKILL.md meta/chains/README.md \ - .claude/skills/confirm/scripts/check_writer_acceptance.py \ - .claude/skills/confirm/scripts/test_writer_acceptance.py +git add .claude/skills/decide-candidate/SKILL.md meta/chains/README.md \ + .claude/skills/decide-candidate/scripts/check_writer_acceptance.py \ + .claude/skills/decide-candidate/scripts/test_writer_acceptance.py git commit -m "实现: 正文候选接受前置检查" ``` @@ -311,10 +311,10 @@ git commit -m "实现: 正文候选接受前置检查" **Files:** - Modify: `.claude/agents/judge.md` -- Modify: `.claude/skills/eval/SKILL.md` -- Modify: `.claude/skills/quality-gate/SKILL.md` -- Create: `.claude/skills/replay-eval/scripts/writer_rubric.py` -- Create: `.claude/skills/replay-eval/scripts/test_writer_rubric.py` +- Modify: `.claude/skills/optimize-content-quality/SKILL.md` +- Modify: `.claude/skills/score-content-quality/SKILL.md` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/writer_rubric.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/test_writer_rubric.py` - [ ] 先写失败测试,覆盖五维各 0-10 分、0.5 步长: - 设定与实体保真。 @@ -329,9 +329,9 @@ git commit -m "实现: 正文候选接受前置检查" ```bash .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_writer_rubric.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_writer_rubric.py -v .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_rubric.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_rubric.py -v ``` - [ ] 同步 judge/eval/quality-gate 的维度、阈值和无效样本语义。 @@ -339,18 +339,18 @@ git commit -m "实现: 正文候选接受前置检查" - [ ] Commit: ```bash -git add .claude/agents/judge.md .claude/skills/eval/SKILL.md \ - .claude/skills/quality-gate/SKILL.md \ - .claude/skills/replay-eval/scripts/writer_rubric.py \ - .claude/skills/replay-eval/scripts/test_writer_rubric.py +git add .claude/agents/judge.md .claude/skills/optimize-content-quality/SKILL.md \ + .claude/skills/score-content-quality/SKILL.md \ + .claude/skills/evaluate-frozen-replay/scripts/writer_rubric.py \ + .claude/skills/evaluate-frozen-replay/scripts/test_writer_rubric.py git commit -m "实现: 正文五维盲评与稳定性仲裁" ``` ## 任务 8:实现 A/B/C 同条件回放编排 **Files:** -- Create: `.claude/skills/replay-eval/scripts/run_writer_replay.py` -- Create: `.claude/skills/replay-eval/scripts/test_run_writer_replay.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py` - [ ] 先写失败测试,验证三臂唯一变量: - A:仅历史原文,无卡索引,诊断候选。 @@ -363,9 +363,9 @@ git commit -m "实现: 正文五维盲评与稳定性仲裁" ```bash .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_run_writer_replay.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py -v .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_run_replay.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_run_replay.py -v ``` - [ ] 实现 dry-run:只生成计划、manifest 和上下文摘要,不调用模型。 @@ -376,16 +376,16 @@ git commit -m "实现: 正文五维盲评与稳定性仲裁" - [ ] Commit: ```bash -git add .claude/skills/replay-eval/scripts/run_writer_replay.py \ - .claude/skills/replay-eval/scripts/test_run_writer_replay.py +git add .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py \ + .claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py git commit -m "实现: 正文三臂冻结回放编排" ``` ## 任务 9:实现 Gate A/B 唯一判定器 **Files:** -- Create: `.claude/skills/replay-eval/scripts/writer_gate.py` -- Create: `.claude/skills/replay-eval/scripts/test_writer_gate.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/writer_gate.py` +- Create: `.claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py` - [ ] 先写表驱动失败测试,固定终态优先级。 - [ ] Gate A 判定顺序:有效样本 `<5` -> `insufficient_evidence`;否则只要存在 schema 非法、未来泄漏、系统失败、C 臂 detector 高严重度残留或细纲硬约束覆盖率 `<100%` -> `failed`;其余 -> `passed`。 @@ -395,23 +395,23 @@ git commit -m "实现: 正文三臂冻结回放编排" ```bash .venv/bin/python \ - .claude/skills/replay-eval/scripts/test_writer_gate.py -v + .claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py -v ``` - [ ] 实现判定器并测试转绿。 - [ ] Commit: ```bash -git add .claude/skills/replay-eval/scripts/writer_gate.py \ - .claude/skills/replay-eval/scripts/test_writer_gate.py +git add .claude/skills/evaluate-frozen-replay/scripts/writer_gate.py \ + .claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py git commit -m "实现: 正文 Gate A B 唯一判定器" ``` ## 任务 10:预注册 Gate A 样本并完成全量机械验证 **Files:** -- Create: `.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json` -- Modify: `.claude/skills/replay-eval/SKILL.md` +- Create: `.claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json` +- Modify: `.claude/skills/evaluate-frozen-replay/SKILL.md` - Modify: `docs/2026-07-20-正文智能体正式优化设计与计划.md` - [ ] 预注册深空之影 5 个目标章,固定为:第 489 章“圣蒂曼围攻/加特朗战”=战斗;第 321 章“莫妮卡道别与朋友确认”=人物对话;第 544 章“迷途之地内应反水”=转折;第 199 章“赛莉丝身份与联赛锁死真相”=信息揭示;第 523 章“伊蕾莉雅与旧部重逢”=老角色回归。不得根据生成结果换章或改分类。 @@ -421,15 +421,15 @@ git commit -m "实现: 正文 Gate A B 唯一判定器" ```bash .venv/bin/python -m unittest discover \ - -s .claude/skills/read-context/scripts -p 'test_*.py' -v + -s .claude/skills/assemble-context/scripts -p 'test_*.py' -v .venv/bin/python -m unittest discover \ - -s .claude/skills/continuation/scripts -p 'test_*.py' -v + -s .claude/skills/write-next-chapter/scripts -p 'test_*.py' -v .venv/bin/python -m unittest discover \ - -s .claude/skills/detect/scripts -p 'test_*.py' -v + -s .claude/skills/check-content-consistency/scripts -p 'test_*.py' -v .venv/bin/python -m unittest discover \ - -s .claude/skills/confirm/scripts -p 'test_*.py' -v + -s .claude/skills/decide-candidate/scripts -p 'test_*.py' -v .venv/bin/python -m unittest discover \ - -s .claude/skills/replay-eval/scripts -p 'test_*.py' -v + -s .claude/skills/evaluate-frozen-replay/scripts -p 'test_*.py' -v ``` - [ ] 运行静态检查: @@ -437,8 +437,8 @@ git commit -m "实现: 正文 Gate A B 唯一判定器" ```bash git diff --check if rg -n "TODO|TBD|implement later" \ - .claude/skills/read-context .claude/skills/continuation .claude/skills/detect \ - .claude/skills/confirm .claude/skills/replay-eval meta/schemas; then + .claude/skills/assemble-context .claude/skills/write-next-chapter .claude/skills/check-content-consistency \ + .claude/skills/decide-candidate .claude/skills/evaluate-frozen-replay meta/schemas; then echo "发现未完成占位文本" >&2 exit 1 fi @@ -448,8 +448,8 @@ fi ```bash .venv/bin/python \ - .claude/skills/replay-eval/scripts/run_writer_replay.py \ - --config .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ + .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ --dry-run ``` @@ -457,8 +457,8 @@ fi - [ ] Commit: ```bash -git add .claude/skills/replay-eval/SKILL.md \ - .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ +git add .claude/skills/evaluate-frozen-replay/SKILL.md \ + .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ docs/2026-07-20-正文智能体正式优化设计与计划.md git commit -m "验证: 预注册正文 Gate A 回放样本" ``` @@ -478,10 +478,10 @@ git commit -m "验证: 预注册正文 Gate A 回放样本" ```bash RUN_ID="writer-gate-a-$(date -u +%Y%m%dT%H%M%SZ)" RUN_DIR="/private/tmp/muse-writer-replay/$RUN_ID" -.venv/bin/python .claude/skills/replay-eval/scripts/run_writer_replay.py \ - --config .claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-a-deep-space-v1.json \ --run-id "$RUN_ID" --output-dir "$RUN_DIR" --execute -.venv/bin/python .claude/skills/replay-eval/scripts/writer_gate.py \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/writer_gate.py \ --run-dir "$RUN_DIR" --summary-output "docs/replay/$RUN_ID-writer-summary.md" ``` @@ -499,9 +499,9 @@ git commit -m "验证: 裁决正文智能体 Gate A" ## 任务 12:预注册 Gate B 多作品评估集 **Files:** -- Create: `.claude/skills/replay-eval/configs/writer-gate-b-v1.json` -- Modify: `.claude/skills/replay-eval/scripts/test_run_writer_replay.py` -- Modify: `.claude/skills/replay-eval/scripts/test_writer_gate.py` +- Create: `.claude/skills/evaluate-frozen-replay/configs/writer-gate-b-v1.json` +- Modify: `.claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py` +- Modify: `.claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py` - [ ] 只有 Gate A=`passed` 才执行本任务;否则保持配置未启用并修正文层。 - [ ] 固定 2 本书、每书 5 章、总计 10 章,且两书都覆盖五类场景: @@ -512,10 +512,10 @@ git commit -m "验证: 裁决正文智能体 Gate A" - [ ] 增加判定测试:不稳定样本占比、平均退化、单样本大幅退化、保真平均增量和正向比例均按任务 9 的唯一顺序裁决。 ```bash -.venv/bin/python .claude/skills/replay-eval/scripts/test_run_writer_replay.py -v -.venv/bin/python .claude/skills/replay-eval/scripts/test_writer_gate.py -v -.venv/bin/python .claude/skills/replay-eval/scripts/run_writer_replay.py \ - --config .claude/skills/replay-eval/configs/writer-gate-b-v1.json \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py -v +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py -v +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-b-v1.json \ --dry-run ``` @@ -523,9 +523,9 @@ git commit -m "验证: 裁决正文智能体 Gate A" - [ ] Commit: ```bash -git add .claude/skills/replay-eval/configs/writer-gate-b-v1.json \ - .claude/skills/replay-eval/scripts/test_run_writer_replay.py \ - .claude/skills/replay-eval/scripts/test_writer_gate.py +git add .claude/skills/evaluate-frozen-replay/configs/writer-gate-b-v1.json \ + .claude/skills/evaluate-frozen-replay/scripts/test_run_writer_replay.py \ + .claude/skills/evaluate-frozen-replay/scripts/test_writer_gate.py git commit -m "验证: 预注册正文 Gate B 多作品评估集" ``` @@ -542,10 +542,10 @@ git commit -m "验证: 预注册正文 Gate B 多作品评估集" ```bash RUN_ID="writer-gate-b-$(date -u +%Y%m%dT%H%M%SZ)" RUN_DIR="/private/tmp/muse-writer-replay/$RUN_ID" -.venv/bin/python .claude/skills/replay-eval/scripts/run_writer_replay.py \ - --config .claude/skills/replay-eval/configs/writer-gate-b-v1.json \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/run_writer_replay.py \ + --config .claude/skills/evaluate-frozen-replay/configs/writer-gate-b-v1.json \ --run-id "$RUN_ID" --output-dir "$RUN_DIR" --execute -.venv/bin/python .claude/skills/replay-eval/scripts/writer_gate.py \ +.venv/bin/python .claude/skills/evaluate-frozen-replay/scripts/writer_gate.py \ --run-dir "$RUN_DIR" --summary-output "docs/replay/$RUN_ID-writer-gate-b-summary.md" ``` - [ ] 人工抽查两书各至少 2 章的 manifest、冻结边界、卡到原文指针、claim ledger 和评委归因;发现泄露或控制变量漂移则整批作废。 diff --git a/docs/write-chapter/probe_sandbox.py b/docs/write-chapter/probe_sandbox.py new file mode 100644 index 0000000..cdfcbc8 --- /dev/null +++ b/docs/write-chapter/probe_sandbox.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""sandbox 探针:走官方 run_claude(sandbox-exec + 最小环境)真调一次 claude。 + +目的:核实 claude_runtime 的 sandbox 路径在本机能否跑通(前会话实测空 envelope / +EPERM,但未留档原始 stdout/stderr)。本探针用一个只记录不改行为的 runner 包住 +subprocess,把完整 command / env 键 / returncode / stdout / stderr 落 artifacts, +失败即可定位 claude 在 sandbox 里到底缺什么。 +""" +import json +import subprocess +import sys +import uuid +from datetime import datetime +from decimal import Decimal +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +SKILLS = SCRIPT_DIR.parents[1] / ".claude" / "skills" +for sub in ( + "write-next-chapter/scripts", + "execute-claude-task/scripts", + "assemble-context/scripts", +): + p = str(SKILLS / sub) + if p not in sys.path: + sys.path.insert(0, p) + +from claude_runtime import ClaudeRuntimeError, run_claude # noqa: E402 +from run_writer import build_writer_execution_profile # noqa: E402 +from writer_contract import validate_writer_draft # noqa: E402 + +CLAUDE_BIN = "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude" +CLAUDE_SHA256 = "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c" +GATE_A = json.loads((SKILLS / "evaluate-frozen-replay" / "configs" / + "writer-gate-a-deep-space-v1.json").read_text(encoding="utf-8")) +WRITER_PROFILE_CFG = GATE_A["executionProfiles"]["writer"] +ARTIFACTS = SCRIPT_DIR / "artifacts" +ARTIFACTS.mkdir(exist_ok=True) + +LOG_PATH = ARTIFACTS / f"probe-sandbox-{uuid.uuid4().hex[:8]}.json" + + +def logging_runner(command, *, input, text, capture_output, timeout, check, cwd, env, + start_new_session): + """透传 subprocess.run,只把现场原样记到留档文件。""" + record = {"command": list(command), "envKeys": sorted(env.keys()), + "cwd": cwd, "inputChars": len(input), "startedAt": str(datetime.now())} + try: + completed = subprocess.run(list(command), input=input, text=text, + capture_output=capture_output, timeout=timeout, + check=check, cwd=cwd, env=dict(env), + start_new_session=start_new_session) + except subprocess.TimeoutExpired as exc: + record["exception"] = f"TimeoutExpired: {exc}" + LOG_PATH.write_text(json.dumps(record, ensure_ascii=False, indent=1), encoding="utf-8") + raise + record.update({"returncode": completed.returncode, + "stdout": completed.stdout or "", "stderr": completed.stderr or ""}) + LOG_PATH.write_text(json.dumps(record, ensure_ascii=False, indent=1), encoding="utf-8") + return completed + + +def main(): + profile = build_writer_execution_profile( + claude_executable_path=CLAUDE_BIN, claude_executable_sha256=CLAUDE_SHA256, + claude_cli_version="2.1.211", resolved_model_id="claude-opus-4-8[1m]", + effort="high", max_budget_usd_per_call=Decimal("1.0"), timeout_seconds=300, + max_context_chars=10000, system_prompt=WRITER_PROFILE_CFG["systemPrompt"]) + probe_input = {"fineOutline": {"hardConstraints": ["写一句至少二十字的测试正文。"], + "adjustableBeats": [], "declaredNewFacts": []}, + "narrativeState": {"time": "", "location": "", "characterPositions": {}, + "immediateSituation": "探针"}, + "factConstraints": [], "proseExcerpts": [], "patternReferences": [], + "lengthContract": {"targetChars": 40, "minChars": 10, "maxChars": 200, + "frontmatterRequired": False}, + "styleConstraints": []} + try: + invocation = run_claude(profile, probe_input, runner=logging_runner, + business_validator=validate_writer_draft) + except ClaudeRuntimeError as exc: + print(f"[探针失败] primary={exc.primary_code}") + print(f" causes: {sorted(exc.causes)}") + receipt = exc.details.get("executionReceipt") or {} + print(f" receipt: exitCode={receipt.get('exitCode')} " + f"actualModelId={receipt.get('actualModelId')} " + f"terminalReason={receipt.get('terminalReason')}") + print(f" 现场留档: {LOG_PATH}") + raise SystemExit(1) + print("[探针通过] sandbox 路径可跑通") + print(f" structured_output: {json.dumps(invocation.structured_output, ensure_ascii=False)[:200]}") + print(f" actual_model_id: {invocation.receipt.actual_model_id}") + print(f" total_cost_usd: {invocation.receipt.total_cost_usd}") + print(f" 现场留档: {LOG_PATH}") + + +if __name__ == "__main__": + main() diff --git a/docs/write-chapter/step1_setup_work.py b/docs/write-chapter/step1_setup_work.py new file mode 100644 index 0000000..3dee1a9 --- /dev/null +++ b/docs/write-chapter/step1_setup_work.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""真写一章 · 阶段一:建立作品 + 第 1 章 + 设定/大纲/细纲(规划落库)。 + +新建一部科幻机甲小说《深渊机神》(宇宙/星空/机甲/人类/异族/战斗/舰队/巨舰/天才/ +生物机甲/微恐/克苏鲁/黑暗/养成/收集/生存/升级/机战),创建第 1 章, +并把设定包/大纲/章级细纲经 persist_planning 落库(shadow→confirmed)。 +""" +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +DB_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "access-database" / "scripts" +PLANNING_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "plan-story" / "scripts" +for p in (str(DB_SCRIPTS), str(PLANNING_SCRIPTS)): + if p not in sys.path: + sys.path.insert(0, p) + +from db import connect # noqa: E402 +from persist_planning import write_section, confirm_section # noqa: E402 + +WORK_TITLE = "深渊机神" +GENRE = "科幻·机甲·克苏鲁" +CHAPTER1_TITLE = "渊临" + +# 设定包(作品核心) +SETTING_PAYLOAD = { + "title": WORK_TITLE, + "genre": GENRE, + "logline": "人类舰队深入深渊星域,天才机师驾驶生物机甲对抗克苏鲁式异族,在黑暗宇宙中生存、收集、升级。", + "core_premise": "深渊星域浮现克苏鲁式异族,人类以生物机甲对抗。主角是天才机师,从底层养成机甲、收集异种核心、在黑暗森林般的星域生存升级。", + "world": "人类文明扩张至深渊星域,遭遇沉睡的克苏鲁式异族。生物机甲以异种核心为源,机师与机甲精神链接。宇宙是黑暗森林,舰队巨舰是人类的移动堡垒。", + "power_system": "生物机甲以异种核心驱动,机师精神链接深度决定机甲出力。代价:链接过深会被异族精神污染(微恐/克苏鲁)。升级=融合更强异种核心。", + "style": "冷峻、克制、微恐。战斗场面宏大(舰队/巨舰/机甲),心理描写细腻(克苏鲁式精神压迫)。句式偏短,节奏紧。", + "themes": ["生存", "升级", "收集", "养成", "黑暗森林", "克苏鲁", "机甲", "舰队"], +} + +# 大纲(总纲) +OUTLINE_PAYLOAD = { + "title": WORK_TITLE, + "overall_arc": "主角从底层机师成长为驾驶神级生物机甲的存在,对抗深渊克苏鲁异族,揭开宇宙黑暗森林的真相。", + "volume_1": "第一卷·渊临:主角初遇深渊异族,觉醒生物机甲,在舰队中生存立足。", + "volume_1_chapters": "第1章 渊临(开篇:深渊异族初现,主角觉醒)→ 后续章节展开舰队生存与机甲养成。", +} + +# 第 1 章细纲 +FINE_OUTLINE_PAYLOAD = { + "targetChapter": 1, + "targetChars": 4000, + "chapterGoal": "开篇:深渊星域异族初现,主角(天才机师)在危机中觉醒生物机甲,建立微恐/克苏鲁基调与生存压力。", + "hardConstraints": [ + "主角是天才机师,初次驾驶生物机甲。", + "深渊异族(克苏鲁式)首次出现,营造微恐/精神压迫。", + "建立黑暗森林/舰队/巨舰的世界观基调。", + "章末钩子:主角觉醒生物机甲的代价(精神污染的预兆)。", + ], + "keyEvents": [ + "主角所在的舰队侦察船在深渊星域遭遇异常信号。", + "克苏鲁式异族生物现身,舰队遭受精神压迫(微恐)。", + "主角在危机中被迫启动未完成的生物机甲。", + "主角与生物机甲精神链接,击退异族,但出现精神污染预兆。", + ], + "adjustableBeats": [ + "开场节奏(侦察船日常 → 异常信号 → 异族现身)可调整。", + "精神压迫的描写强度可调整(微恐基调)。", + ], + "declaredNewFacts": [ + "主角:天才机师,名字待定(建议:林深)。", + "生物机甲:以异种核心驱动,精神链接。", + "深渊异族:克苏鲁式,精神污染。", + ], + "mustAppearEntities": ["主角(林深)", "生物机甲", "深渊异族", "侦察船"], + "chapterEndHook": "主角击退异族后,发现精神链接留下污染预兆——机甲的核心在低语。", + "sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"}, +} + + +def main(): + with connect() as conn: + # 1) 建作品 + work_id = conn.execute( + "INSERT INTO muse_content_work(owner_user_id, title, description, genre, status, " + "summary, word_count, chapter_count, revision, creator, updater) " + "VALUES (1,%s,%s,%s,'writing',%s,0,0,1,'setup','setup') RETURNING id", + (WORK_TITLE, SETTING_PAYLOAD["logline"], GENRE, SETTING_PAYLOAD["core_premise"])).fetchone()[0] + print(f"建作品: id={work_id} 《{WORK_TITLE}》") + + # 2) 建第 1 章 + chapter_id = conn.execute( + "INSERT INTO muse_content_chapter(work_id, title, order_no, status, revision, creator, updater) " + "VALUES (%s,%s,1,'draft',1,'setup','setup') RETURNING id", + (work_id, CHAPTER1_TITLE)).fetchone()[0] + print(f"建第 1 章: id={chapter_id} 《{CHAPTER1_TITLE}》") + conn.commit() + + # 3) 设定/大纲(书级,shadow→confirmed) + setting = write_section(work_id, section_type="setting", schema_type="novel_work", payload=SETTING_PAYLOAD) + confirm_section(setting["id"]) + print(f"设定包: section id={setting['id']} → confirmed") + outline = write_section(work_id, section_type="outline", schema_type="outline", payload=OUTLINE_PAYLOAD) + confirm_section(outline["id"]) + print(f"大纲: section id={outline['id']} → confirmed") + + # 4) 章级细纲(章级,shadow→confirmed) + fine = write_section(work_id, section_type="fine_outline", schema_type="outline", + target_chapter=1, payload=FINE_OUTLINE_PAYLOAD) + confirm_section(fine["id"]) + print(f"第1章细纲: section id={fine['id']} → confirmed") + + print(f"\n阶段一完成: work_id={work_id}, chapter_id={chapter_id}") + print(f"WORK_ID={work_id}") + print(f"CHAPTER_ID={chapter_id}") + + +if __name__ == "__main__": + main() diff --git a/docs/write-chapter/step2_setup_chapter2.py b/docs/write-chapter/step2_setup_chapter2.py new file mode 100644 index 0000000..72477b6 --- /dev/null +++ b/docs/write-chapter/step2_setup_chapter2.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""真写一章 · 阶段二前置:建第 2 章 + 章级细纲(规划落库)。 + +镜像 step1 的建章 + persist_planning 模式。第 2 章细纲按总纲「舰队生存与机甲养成」 +展开,硬骨架接住第 1 章已确立的事实(林深/何岚/薄雾号/左眼黑纹/脑内声音/被称量)。 + +细纲 payload 形状说明: +- 合同四件套(sourceRef/hardConstraints/adjustableBeats/declaredNewFacts)满足 + assemble 的 _outline_contract 与 WriterContext 严格校验;declaredNewFacts 是 + {factId,text,sourceRef} 对象(第 1 章 payload 的纯字符串形状只用于不过合同的基线章)。 +- chapterGoal/keyEvents/mustAppearEntities/chapterEndHook 是规划侧字段,assemble 会裁剪, + 留给人工审查与机械门的锚点映射。 +- entities 供 build_retrieval_plan 生成检索查询(新书无卡,检索会诚实返空)。 +""" +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +DB_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "access-database" / "scripts" +PLANNING_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "plan-story" / "scripts" +for p in (str(DB_SCRIPTS), str(PLANNING_SCRIPTS)): + if p not in sys.path: + sys.path.insert(0, p) + +from db import connect # noqa: E402 +from persist_planning import write_section, confirm_section # noqa: E402 + +WORK_ID = 12 +CHAPTER2_TITLE = "称量" + +FINE_OUTLINE_PAYLOAD = { + "targetChapter": 2, + "targetChars": 4000, + "chapterGoal": ("林深带着污染征兆回到主力舰队,被隔离审讯。舰队按黑暗森林逻辑" + "裁决他:武器还是污染源;他主动隐瞒脑内的声音,污染却在加重;" + "机甲「茧」的异种核心露出饥饿,埋下升级线。"), + "hardConstraints": [ + "衔接第1章结尾:林深击退异族后回到薄雾号,左眼有扩散的黑纹,脑内留有异族的声音。", + "林深进入主力舰队控制范围,被隔离和审讯;舰队态度体现黑暗森林逻辑(称量他的价值与污染风险)。", + "污染检测查不出异常,林深主动隐瞒脑内的声音;同时污染出现新的恶化征兆。", + "机甲「茧」被舰队封存研究,异种核心表现出对同类核心的饥饿(升级线伏笔:融合更强异种核心)。", + "章末钩子:舰队决定让「茧」与林深执行实战任务——或林深察觉自己已不愿离开链接。", + ], + "keyEvents": [ + "林深被舰队接走并立即隔离。", + "审讯:军方追问战斗细节与污染状态,何岚作证。", + "污染检测与隐瞒:仪器查不出异常,他没有说出脑内的声音。", + "林深独自面对「茧」,发现核心对异族残骸的反应(饥饿)。", + ], + "adjustableBeats": [ + "审讯与检测场景的次序和比重可调整。", + "污染恶化的具体表现可调整(须与第1章结尾的左眼黑纹、脑内声音一致)。", + ], + "declaredNewFacts": [ + {"factId": "fact-ch2-1", + "text": "舰队设特别调查组(军方隔离审讯机构),负责污染裁决。", + "sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"}}, + {"factId": "fact-ch2-2", + "text": "异种核心以同类核心为食:融合更强的异种核心,机甲升级,链接加深。", + "sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"}}, + ], + "mustAppearEntities": ["林深", "何岚", "生物机甲茧", "主力舰队"], + "chapterEndHook": ("舰队下达实战调令,让「茧」出击;林深在隔离舱里听见核心" + "满意的搏动——它等到了下一顿。"), + "sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"}, + "entities": [ + {"id": "entity-ch2-char-linshen", "type": "character", "name": "林深"}, + {"id": "entity-ch2-char-helan", "type": "character", "name": "何岚"}, + {"id": "entity-ch2-item-cocoon", "type": "item", "name": "生物机甲茧"}, + {"id": "entity-ch2-loc-fleet", "type": "location", "name": "主力舰队"}, + ], +} + + +def main(): + with connect() as conn: + chapter_id = conn.execute( + "INSERT INTO muse_content_chapter(work_id, title, order_no, status, revision, " + "creator, updater) VALUES (%s,%s,2,'draft',1,'setup','setup') RETURNING id", + (WORK_ID, CHAPTER2_TITLE)).fetchone()[0] + print(f"建第 2 章: id={chapter_id} 《{CHAPTER2_TITLE}》") + conn.commit() + + fine = write_section(WORK_ID, section_type="fine_outline", schema_type="outline", + target_chapter=2, payload=FINE_OUTLINE_PAYLOAD) + confirm_section(fine["id"]) + print(f"第2章细纲: section id={fine['id']} → confirmed") + print(f"\nCHAPTER2_ID={chapter_id}\nFINE_SECTION_ID={fine['id']}") + + +if __name__ == "__main__": + main() diff --git a/docs/write-chapter/step2_setup_chapter3.py b/docs/write-chapter/step2_setup_chapter3.py index 01db0a6..34709a1 100644 --- a/docs/write-chapter/step2_setup_chapter3.py +++ b/docs/write-chapter/step2_setup_chapter3.py @@ -18,8 +18,8 @@ import sys from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent -DB_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "db" / "scripts" -PLANNING_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "planning" / "scripts" +DB_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "access-database" / "scripts" +PLANNING_SCRIPTS = SCRIPT_DIR.parents[1] / ".claude" / "skills" / "plan-story" / "scripts" for p in (str(DB_SCRIPTS), str(PLANNING_SCRIPTS)): if p not in sys.path: sys.path.insert(0, p) diff --git a/docs/write-chapter/step2_write_chapter.py b/docs/write-chapter/step2_write_chapter.py index 611f938..067b720 100644 --- a/docs/write-chapter/step2_write_chapter.py +++ b/docs/write-chapter/step2_write_chapter.py @@ -1,20 +1,23 @@ #!/usr/bin/env python3 """真写一章 · 阶段二:走完整生产链写下一章(默认写目标作品的下一章)。 -生产链(与 persist_writer_run 文档登记的顺序一致): +生产链(meta/chains continuation 登记的保护节点序列落地): 读已确认细纲 + 前章正文基线 → build_retrieval_plan + retrieve_writer_sources(生产仓储;新书无卡诚实返空) → assemble_context 冻结 WriterContext v1 - → run_writer_with_receipt(sandbox 隔离 + 冻结 profile + 运行号随上下文 → - runtime 自动把模型输入/输出原文与调用明细原子落库) - → 篇幅越界自动重抽(最多三遍) - → check_writer_candidate 机械门(锚点合同按章登记) - → persist_writer_execution(上下文冻结 + 运行注册 + 候选 + 回执 + 质量评判一次落库) - → confirm.accept 接受为正式正文 + → run_writer_pipeline(持久 CAS 状态链 + 机械门 + 语义 detector,有限补证/重写合同) + writer 真调经 run_writer_with_receipt:runtime 自动把模型输入/输出原文与调用明细原子落库; + 篇幅越界在 writer 适配层自动重抽(最多三遍);语义 detector 走冻结 detector profile 真调; + 生产链暂未建补证重组装,证据缺口失败关闭(拒绝候选,留痕待人工处理) + → persist_writer_execution(冻结 + 运行注册 + 候选[含语义状态] + 回执 + 机械/语义质量证据一次落库) + → accept_preflight(check_writer_acceptance 纯函数 + acceptance_state 实时重读) + → confirm.accept 接受为正式正文(DB 级兜底:state=passed 且 semantic_status=passed 才可写) -用法:.venv/bin/python docs/write-chapter/step2_write_chapter.py [目标章号] +用法:.venv/bin/python docs/write-chapter/step2_write_chapter.py [目标章号] [--dry-run] 缺省写下一章(库内最大章序 +1)。前置:该章已建且有 confirmed 细纲(镜像 step2_setup_chapter2.py 建章 + 落细纲),且门锚合同 GATE_ANCHORS 已登记该章。 +--dry-run:全链试跑——writer 与语义 detector 真调、候选与证据真实落 Shadow、接受事务试跑后 +整体回滚,正典正文不动;候选留库待用户三决策。 """ import hashlib import json @@ -24,12 +27,20 @@ import uuid from datetime import datetime, timezone from decimal import Decimal from pathlib import Path +from typing import Any, Mapping SCRIPT_DIR = Path(__file__).resolve().parent AGENT_ROOT = SCRIPT_DIR.parents[1] SKILLS = AGENT_ROOT / ".claude" / "skills" -for sub in ("read-context/scripts", "continuation/scripts", "runtime/scripts", - "db/scripts", "detect/scripts", "confirm/scripts"): +for sub in ( + "assemble-context/scripts", + "write-next-chapter/scripts", + "execute-claude-task/scripts", + "record-run-evidence/scripts", + "access-database/scripts", + "check-content-consistency/scripts", + "decide-candidate/scripts", +): p = str(SKILLS / sub) if p not in sys.path: sys.path.insert(0, p) @@ -45,21 +56,33 @@ from run_writer import ( # noqa: E402 WriterAdapterError, build_writer_execution_profile, calculate_dynamic_output_contract, run_writer_with_receipt, ) +from run_writer_pipeline import PipelineError, run_writer_pipeline # noqa: E402 +from candidate_cas import PostgresCasStateStore # noqa: E402 +from run_writer_semantic_detector import ( # noqa: E402 + SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA, build_safe_semantic_diagnostic, + build_semantic_input_v3, run_writer_semantic_detector, +) +from claude_runtime import ExecutionProfile, run_claude, sha256_json # noqa: E402 from persist_llm_call import persist_call as persist_llm_event # noqa: E402 from persist_writer_run import persist_writer_execution # noqa: E402 -from check_writer_candidate import check_writer_candidate # noqa: E402 +from run_registry import finish_run, start_run # noqa: E402 +from check_writer_acceptance import AcceptanceError, check_writer_acceptance # noqa: E402 +from acceptance_state import LiveStateError, build_live_acceptance_state # noqa: E402 from write_canonical import accept # noqa: E402 WORK_ID = 12 +# 2026-08-14 运行绑定再登记:本机 Claude CLI 自动升级 2.1.211→2.1.231, +# 仅同步可执行文件版本与哈希(模型/预算/提示词/schema 一律未动)。 CLAUDE_BIN = "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude" -CLAUDE_SHA256 = "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629" -CLAUDE_VERSION = "2.1.211" +CLAUDE_SHA256 = "ba790279cab6ef77b713864d4bf5f764fcea87d3a3eb7591a41f741e45212b5c" +CLAUDE_VERSION = "2.1.231" RESOLVED_MODEL_ID = "claude-opus-4-8[1m]" MAX_ATTEMPTS = 3 # 篇幅越界重抽上限(编排层响应,对齐 replay 自纠环语义) -GATE_A_CONFIG = json.loads((SKILLS / "replay-eval" / "configs" / +GATE_A_CONFIG = json.loads((SKILLS / "evaluate-frozen-replay" / "configs" / "writer-gate-a-deep-space-v1.json").read_text(encoding="utf-8")) GATE_A_WRITER = GATE_A_CONFIG["executionProfiles"]["writer"] +GATE_A_DETECTOR = GATE_A_CONFIG["executionProfiles"]["semantic_detector"] SYSTEM_PROMPT = GATE_A_WRITER["systemPrompt"] SYSTEM_PROMPT_ID = GATE_A_WRITER["systemPromptId"] SYSTEM_PROMPT_SHA256 = "sha256:" + hashlib.sha256(SYSTEM_PROMPT.encode("utf-8")).hexdigest() @@ -137,6 +160,79 @@ def make_logging_runner(log_path: Path): return runner +def make_sequential_logging_runner(directory: Path, prefix: str): + """多次模型调用(重抽 / detector 纠错环)逐次留档,不互相覆盖。""" + + counter = {"n": 0} + + def runner(command, *, input, text, capture_output, timeout, check, cwd, env, + start_new_session): + counter["n"] += 1 + return make_logging_runner(directory / f"{prefix}-{counter['n']}.json")( + command, input=input, text=text, capture_output=capture_output, + timeout=timeout, check=check, cwd=cwd, env=env, + start_new_session=start_new_session) + + return runner + + +def build_semantic_detector_profile() -> ExecutionProfile: + """从冻结配置构造语义 detector 的 ExecutionProfile(哈希在构造时自检,漂移即停)。""" + + cfg = GATE_A_DETECTOR + assert sha256_json(SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA) == cfg["jsonSchemaSha256"], \ + "语义 detector schema 与冻结配置漂移,停止" + return ExecutionProfile( + profile_version=cfg["profileVersion"], + adapter_role="semantic_detector", + claude_executable_path=cfg["claudeExecutablePath"], + claude_executable_sha256=cfg["claudeExecutableSha256"], + claude_cli_version=cfg["claudeCliVersion"], + model_alias=cfg["modelAlias"], + resolved_model_id=cfg["resolvedModelId"], + effort=cfg["effort"], + max_budget_usd_per_call=Decimal(cfg["maxBudgetUsdPerCall"]), + timeout_seconds=cfg["timeoutSeconds"], + max_context_chars=cfg["maxContextChars"], + json_schema_id=cfg["jsonSchemaId"], + json_schema=SEMANTIC_DETECTOR_REPORT_JSON_SCHEMA, + json_schema_sha256=cfg["jsonSchemaSha256"], + system_prompt_id=cfg["systemPromptId"], + system_prompt=cfg["systemPrompt"], + system_prompt_sha256=cfg["systemPromptSha256"], + normal_terminal_reasons=tuple(cfg["normalTerminalReasons"]), + ) + + +class ProductionSemanticRunner: + """语义 detector 生产 runner:与 ClaudeRuntimeModelRunner 同合同,但调用随 run_id 落库。""" + + def __init__(self, profile: ExecutionProfile, *, run_id: str, log_runner) -> None: + self.profile = profile + self.run_id = run_id + self.log_runner = log_runner + + def run(self, *, adapter_role: str, model_input: Mapping[str, Any], + output_schema: Mapping[str, Any]) -> Mapping[str, Any]: + if self.profile.adapter_role != adapter_role or self.profile.json_schema != output_schema: + raise PipelineError("SEMANTIC_DETECTOR_FAILED", "ExecutionProfile 与语义 detector 适配不一致") + + def persist_detector_event(event): + # runtime 默认把非 writer 调用标为 evaluation;生产链语义审查改标 production_detection。 + event = dict(event) + event["purpose"] = "production_detection" + return persist_llm_event(event) + + result = run_claude( + self.profile, model_input, runner=self.log_runner, + run_id=self.run_id, caller="semantic_detector", + persist_call=persist_detector_event) + receipt = result.receipt + receipt_dict = receipt.as_dict() if hasattr(receipt, "as_dict") else receipt + return {"structuredOutput": dict(result.structured_output), + "modelReceiptSha256": sha256_json(receipt_dict)} + + def resolve_target_chapter(requested: int | None) -> int: with connect(readonly=True) as conn: if requested is not None: @@ -147,8 +243,14 @@ def resolve_target_chapter(requested: int | None) -> int: return int(row[0]) +def _dump(path: Path, value: Any) -> None: + path.write_text(json.dumps(value, ensure_ascii=False, indent=1), encoding="utf-8") + + def main(): - target = resolve_target_chapter(int(sys.argv[1]) if len(sys.argv) > 1 else None) + args = [arg for arg in sys.argv[1:] if not arg.startswith("--")] + dry_run = "--dry-run" in sys.argv[1:] + target = resolve_target_chapter(int(args[0]) if args else None) as_of = target - 1 run_id = f"run-prod-work12-ch{target}-{uuid.uuid4().hex[:8]}" generated_at = datetime.now(timezone.utc).isoformat() @@ -194,10 +296,9 @@ def main(): retrieval = retrieve_writer_sources( plan=plan, card_repository=ProductionCardIndexRepository(), prose_repository=FrozenProseRepository(dsn=DSN, tenant_id=0)) - (ARTIFACTS / f"{run_id}-retrieval.json").write_text(json.dumps( - {"plan": plan, "resultCounts": {k: len(v) for k, v in retrieval.items() - if isinstance(v, list)}}, - ensure_ascii=False, indent=1), encoding="utf-8") + _dump(ARTIFACTS / f"{run_id}-retrieval.json", + {"plan": plan, "resultCounts": {k: len(v) for k, v in retrieval.items() + if isinstance(v, list)}}) print(f"检索: 卡={len(retrieval['cards'])} 事实={len(retrieval['factEvidence'])} " f"原文={len(retrieval['proseEvidence'])}") @@ -233,55 +334,178 @@ def main(): f"(目标 {output_contract['targetChars']}),范式绑定 {len(pattern_references)} 张," f"文风约束 {len(style_constraints)} 条") - # 4) writer 真调(运行号随上下文 → runtime 自动落模型输入输出与调用明细);越界重抽 - profile = build_writer_execution_profile( + # 4) 生产 pipeline:持久 CAS + writer 真调 + 机械门 + 语义 detector(先审后入) + writer_profile = build_writer_execution_profile( claude_executable_path=CLAUDE_BIN, claude_executable_sha256=CLAUDE_SHA256, claude_cli_version=CLAUDE_VERSION, resolved_model_id=RESOLVED_MODEL_ID, effort="high", max_budget_usd_per_call=Decimal("5.0"), timeout_seconds=1200, max_context_chars=token_budget["maxContextChars"], system_prompt=SYSTEM_PROMPT) - candidate = receipt = None - for attempt in range(1, MAX_ATTEMPTS + 1): - try: - candidate, receipt = run_writer_with_receipt( - writer_context, profile=profile, candidate_version=1, - runner=make_logging_runner(ARTIFACTS / f"{run_id}-sandbox-call-{attempt}.json"), - persist_call=persist_llm_event) - break - except WriterAdapterError as exc: - if exc.code != "candidate_length_out_of_range" or attempt == MAX_ATTEMPTS: - raise - print(f" 第{attempt}遍越界({exc.details.get('actualHanChars')} 字),重抽……") + detector_profile = build_semantic_detector_profile() + semantic_runner = ProductionSemanticRunner( + detector_profile, run_id=run_id, + log_runner=make_sequential_logging_runner(ARTIFACTS, f"{run_id}-semantic-call")) + state_store = PostgresCasStateStore( + work_id=WORK_ID, target_chapter=target, creator="continuation") + receipts_by_version: dict[int, Any] = {} + candidates_by_version: dict[int, dict] = {} + contexts_by_attempt: dict[int, dict] = {} - # 5) 机械门 - gate = check_writer_candidate(writer_context, candidate, GATE_ANCHORS[target]) + def production_writer(current_context: Mapping[str, Any], candidate_version: int): + """writer 适配:篇幅越界自动重抽(≤3 遍),成功回执按候选版本留证。""" + + contexts_by_attempt[current_context["attempt"]] = dict(current_context) + for retry in range(1, MAX_ATTEMPTS + 1): + try: + candidate, receipt = run_writer_with_receipt( + current_context, profile=writer_profile, + candidate_version=candidate_version, + runner=make_sequential_logging_runner( + ARTIFACTS, f"{run_id}-sandbox-call-v{candidate_version}"), + persist_call=persist_llm_event) + receipts_by_version[candidate_version] = receipt + candidates_by_version[candidate_version] = candidate + return candidate + except WriterAdapterError as exc: + if exc.code != "candidate_length_out_of_range" or retry == MAX_ATTEMPTS: + raise PipelineError(exc.code, f"writer 真调失败: {exc}", + details=exc.details) from exc + print(f" 第{retry}遍越界({exc.details.get('actualHanChars')} 字),重抽……") + + def production_semantic_detector(current_context, candidate, mechanical_report): + """语义 detector 适配:构造冻结输入、真调模型、留档输入输出。""" + + version = candidate["candidateVersion"] + detector_input = build_semantic_input_v3( + run_id=run_id, sample_id=f"writer-ch{target}", opaque_arm_id="production", + writer_context=current_context, candidate=candidate) + _dump(ARTIFACTS / f"{run_id}-semantic-input-v{version}.json", detector_input) + outcome = run_writer_semantic_detector(detector_input, model_runner=semantic_runner) + _dump(ARTIFACTS / f"{run_id}-semantic-output-v{version}.json", outcome) + if outcome.get("ok") is not True or not isinstance(outcome.get("report"), Mapping): + diagnostic = build_safe_semantic_diagnostic(outcome) + raise PipelineError( + "SEMANTIC_DETECTOR_FAILED", + f"语义 detector 未产生有效报告: {diagnostic['primaryCode']}", + details={"safeDiagnostic": diagnostic}) + print(f"语义 detector(v{version}): status={outcome['status']} " + f"调用={outcome['attemptCount']}次 纠错={outcome['correctionCount']}次") + return outcome["report"] + + def production_evidence_provider(_context, gaps, _attempt): + """生产链补证重组装未建:证据缺口失败关闭,候选拒绝留痕,交人工处理。""" + + raise PipelineError( + "PRODUCTION_EVIDENCE_SUPPLEMENT_UNBUILT", + f"语义审查发现 {len(gaps)} 个证据缺口,生产链暂不支持补证重组装,候选拒绝待人工处理") + + # 候选版本接续:候选表对 (作品,章,candidate_version) 唯一,重跑同章必须从已有最大版本+1 起, + # 否则与上一轮留库的被拒候选撞版本。 + with connect(readonly=True) as conn: + max_version_row = conn.execute( + "SELECT COALESCE(MAX(CASE WHEN candidate_version ~ '^[0-9]+$' " + "THEN candidate_version::integer END),0) FROM example_candidate " + "WHERE tenant_id=0 AND work_id=%s AND target_chapter=%s AND deleted=false", + (WORK_ID, target)).fetchone() + initial_candidate_version = int(max_version_row[0]) + 1 + + start_run(run_id=run_id, work_id=WORK_ID, target_chapter=target, + trigger_detail={"stage": "writer-production-pipeline", + "contextSha256": writer_context["contextSnapshot"]["contextSha256"]}, + creator="continuation") + try: + pipeline_result = run_writer_pipeline( + context=writer_context, + requirements=GATE_ANCHORS[target], + writer=production_writer, + evidence_provider=production_evidence_provider, + semantic_detector=production_semantic_detector, + state_store=state_store, + result_path=ARTIFACTS / f"{run_id}-pipeline-result.json", + initial_candidate_version=initial_candidate_version, + ) + except PipelineError as exc: + # 被拒候选留痕:凡跑过机械门的版本都落 Shadow(state=rejected + 机械/语义证据) + trace = (exc.result or {}).get("trace") or [] + audit_entry = next((entry for entry in reversed(trace) + if isinstance(entry.get("mechanicalReport"), Mapping)), None) + if audit_entry is not None: + version = audit_entry.get("candidateVersion") + failed_candidate = candidates_by_version.get(version) + failed_receipt = receipts_by_version.get(version) + if failed_candidate is not None and failed_receipt is not None: + try: + persisted = persist_writer_execution( + contexts_by_attempt.get(failed_candidate.get("attempt"), writer_context), + failed_candidate, failed_receipt, audit_entry["mechanicalReport"], + semantic_report=audit_entry.get("semanticReport"), + assemble_result=assembled) + print(f"[被拒候选留库] candidate_id={persisted['candidate_id']} " + f"state={persisted['state']} semantic={persisted.get('semantic_status')}") + except Exception as persist_exc: # 留痕失败不掩盖原始失败码 + print(f"[警告] 被拒候选留库失败: {persist_exc}", file=sys.stderr) + finish_run(run_id, "failed", creator="continuation", + trigger_detail={"stage": "writer-production-pipeline", "failureCode": exc.code}) + print(f"[停止] 生产 pipeline 未通过: code={exc.code};{exc}") + print(f"复核 artifacts/{run_id}-pipeline-result.json 后决定下一步。") + print(f"\nRUN_ID={run_id}") + raise SystemExit(1) + except Exception as exc: + # 非 PipelineError(库连接断、适配层异常等)也要收口运行态,不留 running 悬挂 + try: + finish_run(run_id, "failed", creator="continuation", + trigger_detail={"stage": "writer-production-pipeline", + "error": type(exc).__name__}) + except Exception: + pass + raise + + # 5) pipeline 通过:机械门 + 语义 detector 双证据落库(候选 semantic_status=passed) + candidate = pipeline_result["candidateArtifact"] + final_context = contexts_by_attempt.get(pipeline_result["attempt"], writer_context) + final_trace = pipeline_result["trace"][-1] + receipt = receipts_by_version[pipeline_result["candidateVersion"]] + persisted = persist_writer_execution( + final_context, candidate, receipt, final_trace["mechanicalReport"], + semantic_report=final_trace.get("semanticReport"), assemble_result=assembled) + cand_id = persisted["candidate_id"] print(f"writer 产出: sha256={candidate['candidateSha256'][:24]}..., " f"实际模型={receipt.actual_model_id}, 成本=${receipt.total_cost_usd}") - print(f"机械门: passed={gate['passed']} " - f"blocking={[f['code'] for f in gate.get('blockingFailures', [])]}") - (ARTIFACTS / f"{run_id}-gate-report.json").write_text( - json.dumps(gate, ensure_ascii=False, indent=1), encoding="utf-8") - - # 6) 一次落库:冻结 + 运行注册 + 候选 + 回执 + 质量评判(persist_writer_execution 单通道) - persisted = persist_writer_execution( - writer_context, candidate, receipt, gate, assemble_result=assembled) - cand_id = persisted["candidate_id"] print(f"落库: candidate_id={cand_id}, receipt_id={persisted['receipt_id']}, " - f"raw_content_id={persisted['raw_content_id']}, state={persisted['state']}") + f"raw_content_id={persisted['raw_content_id']}, state={persisted['state']}, " + f"semantic={persisted['semantic_status']}") - if not gate["passed"]: - print("[停止] 机械门未通过,候选留库 rejected,不接受为正文。复核 artifacts/" - f"{run_id}-gate-report.json 后决定下一步。") + # 6) 接受前置检查:实时状态重读 + 纯函数全检(上下文/授权/来源/有效期/detector 终态) + try: + live_state = build_live_acceptance_state(final_context) + preflight = check_writer_acceptance( + decision="accept", confirmed=True, context=final_context, candidate=candidate, + detector_result=pipeline_result, live_state=live_state, + expected_revision=live_state["canonicalRevision"]) + except (LiveStateError, AcceptanceError) as exc: + finish_run(run_id, "failed", creator="continuation", + trigger_detail={"stage": "accept-preflight", + "error": getattr(exc, "code", type(exc).__name__)}) + print(f"[停止] 接受前置检查未通过: {getattr(exc, 'code', '')} {exc}") + print(f"候选 {cand_id} 已留库(state=passed, semantic=passed),人工复核后决定。") print(f"\nCANDIDATE_ID={cand_id}\nRUN_ID={run_id}") raise SystemExit(1) + print(f"接受前置检查: {preflight['status']} canonicalRevision={live_state['canonicalRevision']}") - # 7) 接受为正式正文(confirm 唯一通道)——新章无正文块,expected_revision=0 + # 7) 接受为正式正文(confirm 唯一通道;DB 兜底复检 state + semantic_status) + semantic_report_sha = str(final_trace.get("semanticReport", {}).get("reportSha256") or "")[:24] accept_result = accept( cand_id, decided_by="1", rationale=(f"第{target}章生产路径:冻结上下文(asOf={as_of}) → writer sandbox 真调 → " - "机械门锚点检查通过。语义 detector 未跑(本轮链路到机械门为止)。"), + f"机械门通过 → 语义 detector 通过(报告sha256={semantic_report_sha}...) " + f"→ accept_preflight {preflight['status']}。"), basis_ref=f"细纲@v1 (第{target}章 confirmed) + 前章正文基线", - expected_revision=0, command_id=f"accept-work12-ch{target}-{run_id}") + expected_revision=live_state["canonicalRevision"], + command_id=f"accept-work12-ch{target}-{run_id}", + projection_kinds=("extraction",), # 提交后副作用:章后抽取投影随提交登记,异步执行 + dry_run=dry_run) print(f"接受结果: {json.dumps(accept_result, ensure_ascii=False)}") + if dry_run: + print("[试跑] 全链验证完成:正文未动,候选已留 Shadow,等待用户三决策。") print(f"\nCANDIDATE_ID={cand_id}\nRUN_ID={run_id}") diff --git a/docs/write-chapter/step2a_retrofit_ch1_context.py b/docs/write-chapter/step2a_retrofit_ch1_context.py new file mode 100644 index 0000000..105be92 --- /dev/null +++ b/docs/write-chapter/step2a_retrofit_ch1_context.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""真写一章 · 阶段二a 补建:第 1 章的开篇上下文与冻结记录。 + +原则:不存在没有上下文的写作。第 1 章之前也有设定、大纲、细纲——它的上下文是 +asOf=0(开篇前冻结线):证据只有设定包与细纲宣告的新事实,历史正文基线为空。 +此前第 1 章按"基线章无上下文"的旧口径写成,看板运行详情因此缺一块上下文冻结; +本脚本按合同补建真实的第 1 章上下文并落冻结行(append-only,幂等),补齐记账。 + +只组装 + 落冻结,不改写第 1 章正文(正文早已接受为正式内容)。 +""" +import json +import sys +import uuid +from datetime import datetime, timezone +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +AGENT_ROOT = SCRIPT_DIR.parents[1] +SKILLS = AGENT_ROOT / ".claude" / "skills" +for sub in ( + "assemble-context/scripts", + "write-next-chapter/scripts", + "record-run-evidence/scripts", + "access-database/scripts", +): + p = str(SKILLS / sub) + if p not in sys.path: + sys.path.insert(0, p) + +from db import connect, DSN # noqa: E402 +from assemble_writer_context import assemble_context # noqa: E402 +from retrieve_writer_sources import ( # noqa: E402 + ProductionCardIndexRepository, FrozenProseRepository, + build_retrieval_plan, retrieve_writer_sources, +) +from persist_context_freeze import persist_freeze # noqa: E402 +from run_writer import calculate_dynamic_output_contract # noqa: E402 + +WORK_ID = 12 +TARGET_CHAPTER = 1 +AS_OF = 0 +SETTING_SOURCE_REF = {"sourceId": "planning-section:3", "sourceVersion": "v1"} +OUTLINE_SOURCE_REF = {"sourceId": "fine-outline:work12-ch1", "sourceVersion": "v1"} + + +def main(): + run_id = f"retrofit-work12-ch1-context-{uuid.uuid4().hex[:8]}" + generated_at = datetime.now(timezone.utc).isoformat() + + with connect(readonly=True) as conn: + setting_row = conn.execute( + "SELECT payload FROM example_planning_section WHERE id=3 AND work_id=%s " + "AND section_type='setting' AND state='confirmed' AND deleted=false", + (WORK_ID,)).fetchone() + outline_row = conn.execute( + "SELECT payload FROM example_planning_section WHERE work_id=%s AND " + "section_type='fine_outline' AND target_chapter=1 AND state='confirmed' AND " + "deleted=false ORDER BY version DESC LIMIT 1", (WORK_ID,)).fetchone() + if not setting_row or not outline_row: + raise SystemExit("设定包或第1章细纲缺失,无法补建上下文。") + setting = setting_row[0] if isinstance(setting_row[0], dict) else json.loads(setting_row[0]) + fine_outline = outline_row[0] if isinstance(outline_row[0], dict) else json.loads(outline_row[0]) + + # 第1章细纲的 declaredNewFacts 是旧形状(纯字符串);合同要 {factId,text,sourceRef}。 + # 不重写已确认规划行,在读取侧按合同投影。 + normalized_declared = [] + for index, fact in enumerate(fine_outline.get("declaredNewFacts", []), 1): + if isinstance(fact, dict): + normalized_declared.append(fact) + else: + normalized_declared.append({ + "factId": f"fine-outline-ch1:{index}", "text": str(fact), + "sourceRef": OUTLINE_SOURCE_REF, + }) + fine_outline = {**fine_outline, "declaredNewFacts": normalized_declared} + + # 权威事实证据:设定包(正式设定)+ 细纲宣告的新事实 + authoritative_facts = [] + for key, label in (("world", "世界观"), ("power_system", "力量体系"), + ("core_premise", "核心设定")): + if setting.get(key): + authoritative_facts.append({ + "factId": f"setting:{key}", "fact": f"{label}:{setting[key]}", + "sourceType": "formal_setting", "sourceRef": SETTING_SOURCE_REF, + "riskLevel": "low", + }) + for index, fact in enumerate(fine_outline.get("declaredNewFacts", []), 1): + text = fact["text"] if isinstance(fact, dict) else str(fact) + authoritative_facts.append({ + "factId": f"fine-outline-ch1:{index}", "fact": text, + "sourceType": "fine_outline_declared_new", "sourceRef": OUTLINE_SOURCE_REF, + "riskLevel": "low", + }) + + # 检索计划与执行(asOf=0;新书无卡,卡线诚实返空;事实线取权威事实) + token_budget = {"maxContextChars": 200000} + plan = build_retrieval_plan( + run_id=run_id, work_id=WORK_ID, target_chapter=TARGET_CHAPTER, as_of=AS_OF, + fine_outline=fine_outline, card_index_version="knowledge-index-v1", + prose_index_version="content-block-v1", token_budget=token_budget) + retrieval = retrieve_writer_sources( + plan=plan, card_repository=ProductionCardIndexRepository(), + prose_repository=FrozenProseRepository(dsn=DSN, tenant_id=0), + authoritative_facts=authoritative_facts) + print(f"检索: 卡={len(retrieval['cards'])} 事实={len(retrieval['factEvidence'])} " + f"原文={len(retrieval['proseEvidence'])}(asOf=0,历史正文必为空)") + + # 组装第 1 章上下文(asOf=0,无历史正文基线)并落冻结 + output_contract = calculate_dynamic_output_contract( + fine_outline=fine_outline, recent_chapter_bodies=[]) + narrative_state = { + "time": "故事开篇之前", + "location": "深渊星域", + "characterPositions": {}, + "immediateSituation": "一切尚未发生;按设定与第1章细纲展开。", + } + authorization_snapshot = {"snapshotId": "auth-work12-production-v1", + "allowedPurpose": "production_generation", + "verifiedAt": generated_at, "sourceVersion": "v1"} + assembled = assemble_context( + run_id=run_id, attempt=1, mode="production", purpose="production", + quality_policy_version="writer-production-v1", work_id=WORK_ID, + target_chapter=TARGET_CHAPTER, as_of=AS_OF, source_version="outline@v1", + authorization_snapshot=authorization_snapshot, source_status="active", + retrieval_plan=plan, retrieval_result=retrieval, fine_outline=fine_outline, + narrative_state=narrative_state, recent_chapters=[], + output_contract=output_contract, token_budget=token_budget, + pattern_references=[], generated_at=generated_at, + evidence_strategy="production_dual_evidence") + (SCRIPT_DIR / "artifacts").mkdir(exist_ok=True) + (SCRIPT_DIR / "artifacts" / f"{run_id}-writer-context.json").write_text( + assembled["contextJson"], encoding="utf-8") + frozen = persist_freeze(assembled) + print(f"第1章上下文冻结: {json.dumps(frozen, ensure_ascii=False)}") + print(f"事实证据 {len(assembled['context']['factEvidence'])} 条," + f"正文证据 {len(assembled['context']['proseEvidence'])} 条(应为 0)") + print(f"RUN_ID={run_id}") + + +if __name__ == "__main__": + main() diff --git a/docs/write-chapter/step2a_write_chapter1.py b/docs/write-chapter/step2a_write_chapter1.py new file mode 100644 index 0000000..c7c7ba7 --- /dev/null +++ b/docs/write-chapter/step2a_write_chapter1.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +"""真写一章 · 阶段二a:writer 真子进程写第 1 章正文(基线章)。 + +为什么第 1 章单独走这条路(而不是生产组装): +- read-context 合同要求 asOf>=1 且 asOf/SKILL.md`,每 scenario 一只):这次"做什么、按什么步骤、什么输出合同"——派发时只加载**本次功能**的 skill,其余功能的指令不进上下文(防指令稀释);每只功能 skill 必须有「元数据驱动」节,写明 schema 怎么控制该功能; -- **L0 任务特化**(read-context 组装):本回合参数。 +- **功能指令段**(**载体=功能 Skill**,由下表显式登记 scenario→Skill):这次"做什么、按什么步骤、什么输出合同"——派发时只加载**本次功能**的 Skill,其余功能指令不进上下文;每只功能 Skill 必须有「元数据驱动」节,写明 schema 怎么控制该功能; +- **L0 任务特化**(`assemble-context` 组装):本回合参数。 换功能不换 agent、换 agent 不换功能指令,两轴正交。功能指令是元数据:改指令产出立变、agent 一行不改。对应 muse:身份段=agent_version.config 的 prompt;功能指令段=功能链节点/槽位的场景合同;同一 agent 经 slot_bindings 绑多个功能槽位。本目录是**链登记表**:哪条 scenario、经过哪些保护节点、挂哪个槽位、功能合同在哪只 skill。 @@ -16,22 +16,24 @@ agent 的提示词按**变化轴**拆三段,不做"一个 agent 一个大 prom | scenario | 功能 skill | 槽位/agent | purpose | 保护节点序列(简化) | 状态 | |---|---|---|---|---|---| -| continuation 续写 | `continuation` | 写作→writer | generation | read-context→无工具 writer→机械门→语义 detector→Shadow→用户三决策→accept_preflight→正式写入→异步抽取 | v1 接口与机械门已建;真实语义 detector、正式写入和抽取接线未建 | -| rewrite 改写 | `rewrite` | 写作→writer | generation | 同上+expectedRevision 核对 | 已建 | -| expansion 扩写 | `expansion` | 写作→writer | generation | 同 continuation | 已建 | -| polish 润色 | `polish` | 写作→writer | generation | 同 continuation(只动表达层) | 已建 | -| planning 规划 | `planning` | 规划→planner | planning | read-context(planning 视图)→槽位→用户确认(confirm) | 已建 | -| fine_outline 细纲规划 | `fine-outline` | 规划→planner | planning | read-context(fine_outline 冻结视图)→槽位→detect→quality-gate | 评测版首建(2026-07-19) | -| full_parse 拆书 | `parse-book` | 分析→extractor | extraction | import 分章→逐章槽位→db 落 draft→管理员确认(G3 门) | 已建;B2 PG 版首验 | -| extraction 章后抽取 | `extract-knowledge` | 分析→extractor | extraction | 采纳后触发→槽位→草稿/冲突队列→confirm | 已建;C5 首验 | -| validation / consistency_check 检测 | `detect` | 检测→detector | detection | read-context(detection 视图)→槽位→报告落评审/ | 已建 | -| quality_gate 评分 | `quality-gate` | 保护节点→judge | 基线包=writer 视图 | 基线包→judge→eval 环(有限重写) | 已建 | +| continuation 续写 | `write-next-chapter` | 写作→writer | generation | assemble-context→无工具 writer→机械门→语义 detector→Shadow→用户三决策→accept_preflight→正式写入→异步抽取 | 生产链已闭环:机械门+真实语义 detector+持久 CAS 链+accept_preflight+正式写入(write_canonical 单事务,含已批准事实增量与投影登记);生产补证重组装与章后抽取的异步执行未建(抽取投影只登记 pending) | +| rewrite 改写 | `rewrite-selection` | 写作→writer | generation | 同上+expectedRevision 核对 | 已建 | +| expansion 扩写 | `expand-scene` | 写作→writer | generation | 同 continuation | 已建 | +| polish 润色 | `polish-prose` | 写作→writer | generation | 同 continuation(只动表达层) | 已建 | +| setting_init 作品设定初始化 | `design-story-foundation` | 规划→planner | planning | 用户冻结根设定与对话→隔离候选→离线机械门→用户选择→交给 planning | 手工前置流程首建;候选不落库、不进 Canonical | +| planning 规划 | `plan-story` | 规划→planner | planning | assemble-context(planning 视图)→槽位→用户确认(decide-candidate) | 已建 | +| fine_outline 细纲规划 | `plan-chapter` | 规划→planner | planning | assemble-context(fine_outline 冻结视图)→槽位→check-content-consistency→score-content-quality | 评测版首建(2026-07-19) | +| full_parse 拆书 | `deconstruct-book` | 分析→extractor | extraction | import-book 分章→逐章槽位→access-database 落 draft→管理员确认(G3 门) | 已建;B2 PG 版首验 | +| ai_flavor_capture AI 味案例回填/反馈 | `capture-ai-flavor-cases` | 检测→detector | detection | 作品/创作反馈→检测完成自动落库→hash 与位置门→Shadow 案例卡→人工标注→样例/规则候选评审 | 已建;默认写入 muse-example,`--offline` 仅作显式离线回放 | +| extraction 章后抽取 | `extract-chapter-knowledge` | 分析→extractor | extraction | 采纳后触发→槽位→草稿/冲突队列→decide-candidate | 已建;C5 首验 | +| validation / consistency_check 检测 | `check-content-consistency` | 检测→detector | detection | assemble-context(detection 视图)→槽位→报告落评审/ | 已建 | +| quality_gate 评分 | `score-content-quality` | 保护节点→judge | 基线包=writer 视图 | 基线包→judge→optimize-content-quality 环(有限重写) | 已建 | ## 公约 - 功能细节约束一律住功能 skill,agent 身份段只留跨功能部分;新增 scenario 先在此登记再建 skill; - 递送机制(阶段一):功能 skill 由**主会话内联**——派发时读 SKILL.md 正文塞进派发 prompt 的功能指令段位;不用子代理 `skills:` 预加载(它会把列出的全部注入,违反「一次只带本次功能」),子代理无需 Skill 工具; -- 拼装位置:身份段之后、增量段之前(见 read-context 拼装序);同功能多回合间稳定,吃缓存; +- 拼装位置:身份段之后、增量段之前(见 assemble-context 拼装序);同功能多回合间稳定,吃缓存; - purpose 由 scenario 映射(生成类→generation、抽取类→extraction、规划→planning、检测类→detection),purpose 定字段可见集(aiContext),scenario 定功能 skill 与 L0 形态,两者不混; - 链登记与实况漂移=G2 治理缺陷,发现即修。 @@ -40,4 +42,4 @@ agent 的提示词按**变化轴**拆三段,不做"一个 agent 一个大 prom - `accept`:用户明确确认后,对当前候选运行实时 `accept_preflight`;`expectedRevision`、detector、上下文、策略、授权、来源或有效期任一不满足即失败关闭。 - `merge`:用户编辑必须生成严格下一 `candidateVersion` 和新正文 hash,重新运行 detector 后再进入 Shadow;禁止“修改后直接合并”。 - `discard`:用户明确确认后只关闭候选,不返回抽取命令意图。 -- 实验台 `check_writer_acceptance.py` 是无副作用纯函数,不写 Canonical。`accept/merge` 通过时只返回当前不可派发的提交后副作用意图(`allowed=false`、`requiresCanonicalCommit=true`);正式提交层验证 Canonical 提交凭证后才可异步排队章后抽取。本轮没有接入正式正文库或抽取进程。 +- `check_writer_acceptance.py` 是无副作用纯函数,不写 Canonical,只做接受前置检查(实时状态重读由 `acceptance_state.py` 提供)。`accept/merge` 通过时返回提交后副作用意图(`allowed=false`、`requiresCanonicalCommit=true`);正式提交层 `write_canonical.accept` 在 DB 级兜底复检 `state=passed` 且 `semantic_status=passed`(先审后入),同事务合并已批准事实增量并登记章后抽取投影——投影只登记 pending,异步执行未建。 diff --git a/meta/schemas/README.md b/meta/schemas/README.md index b25c6b6..96ee8d7 100644 --- a/meta/schemas/README.md +++ b/meta/schemas/README.md @@ -45,4 +45,4 @@ ## A3 入库状态(2026-07-13) -23 型已全部入库 `muse_meta_schema`(+version/field/visibility_policy),此后拆书/抽取一律读**库内** schema(经 db skill),本目录 YAML 退为设计稿与种子来源;字段增删先改 YAML 再重跑 `seed_schemas.py`(幂等),保持两侧一致。 +23 型已全部入库 `muse_meta_schema`(+version/field/visibility_policy),此后拆书/抽取一律读**库内** schema(经 `access-database` Skill),本目录 YAML 退为设计稿与种子来源;字段增删先改 YAML 再重跑 `seed_schemas.py`(幂等),保持两侧一致。 diff --git a/meta/schemas/fine_outline.yaml b/meta/schemas/fine_outline.yaml index dfb29d4..181cf30 100644 --- a/meta/schemas/fine_outline.yaml +++ b/meta/schemas/fine_outline.yaml @@ -6,7 +6,7 @@ scope: chapter 状态: 启用 判据: 单章的结构骨架(续写的直接依据) ‖ 全书/卷层计划→outline;戏内已定事实→event;跨作品写法公式→trope 实例落点: example_planning_section(section_type=fine_outline;schema_type=fine_outline;必须带 target_chapter;shadow→confirmed 才进生成上下文) -# 本文件是章级细纲 payload 的唯一字段权威。planning 技能(fine-outline SKILL)按此形状产出; +# 本文件是章级细纲 payload 的唯一字段权威。plan-chapter Skill 按此形状产出; # persist_planning 落库时按此做字段覆盖门禁;assemble_writer_context 从中抽取 writer 需要的子集。 # 三处同一合同;结束此前「技能产出形」与「装配消费形」字段不相交的漂移。 # 字段说明文字一律避开 ASCII 逗号与花括号(流式值里它们是 YAML 结构符);用中文顿号/分号/圆括号。 diff --git a/meta/schemas/generation_context.yaml b/meta/schemas/generation_context.yaml index 29b6570..e34d142 100644 --- a/meta/schemas/generation_context.yaml +++ b/meta/schemas/generation_context.yaml @@ -71,4 +71,4 @@ scope: agent indexHints用途: 只用于诊断卡索引替代效应,卡负责定位原文而不替代原文事实,不是事实证据,claimLedger不得引用 接受边界: evaluation、diagnostic、diagnostic_only 一律 acceptanceEligible=false 稳定排序: score DESC, sourceVersion ASC, sourceId ASC, sourceOffset ASC -实现校验器: .claude/skills/read-context/scripts/writer_contract.py +实现校验器: .claude/skills/assemble-context/scripts/writer_contract.py