一、技能重组(动作-对象命名) - 旧目录 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)按"框架与创作分开"未入本提交。
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
||
"""升格作品命令共用的 PostgreSQL 会话级 advisory lock。
|
||
|
||
锁连接是独立 autocommit 会话,只执行加锁和解锁 SQL,不参与任何业务事务。调用方可让该
|
||
context manager 跨越 LLM/嵌入调用持续存活;退出受保护区时,无论成功或异常都会显式解锁并
|
||
关闭会话。backup 的 restore/rehearse 可直接导入 ``upgrade_work_lock`` 复用同一互斥边界。
|
||
"""
|
||
|
||
import hashlib
|
||
import struct
|
||
from contextlib import contextmanager
|
||
|
||
import psycopg
|
||
|
||
|
||
LOCK_NAMESPACE = b"muse:upgrade-work-lock:v1"
|
||
|
||
|
||
class UpgradeWorkLockUnavailable(RuntimeError):
|
||
"""同租户、同作品已有另一条升格命令持锁。"""
|
||
|
||
def __init__(self, tenant_id, work_id):
|
||
self.tenant_id = tenant_id
|
||
self.work_id = work_id
|
||
super().__init__(
|
||
f"作品 tenant={tenant_id} work={work_id} 已有升格命令正在处理;"
|
||
"本次未执行任何业务操作"
|
||
)
|
||
|
||
|
||
def advisory_lock_keys(tenant_id, work_id):
|
||
"""把租户与作品稳定映射为 PostgreSQL 接受的两个有符号 int32 锁键。"""
|
||
|
||
identity = f"{int(tenant_id)}:{int(work_id)}".encode("ascii")
|
||
digest = hashlib.sha256(LOCK_NAMESPACE + b"\0" + identity).digest()
|
||
return struct.unpack(">ii", digest[:8])
|
||
|
||
|
||
@contextmanager
|
||
def upgrade_work_lock(dsn, tenant_id, work_id, *, connect=None):
|
||
"""非阻塞获取同书会话锁,失败立即抛错,finally 中释放并关闭专用连接。
|
||
|
||
``connect`` 仅供离线测试注入;生产默认使用 ``psycopg.connect``。返回的两个锁键可用于
|
||
调试记录,调用方不得使用该专用连接执行任何业务 SQL。
|
||
"""
|
||
|
||
connector = connect or psycopg.connect
|
||
keys = advisory_lock_keys(tenant_id, work_id)
|
||
connection = connector(dsn, autocommit=True)
|
||
acquired = False
|
||
try:
|
||
row = connection.execute(
|
||
"SELECT pg_try_advisory_lock(%s, %s)", keys
|
||
).fetchone()
|
||
acquired = bool(row and row[0] is True)
|
||
if not acquired:
|
||
raise UpgradeWorkLockUnavailable(tenant_id, work_id)
|
||
yield keys
|
||
finally:
|
||
try:
|
||
if acquired:
|
||
connection.execute("SELECT pg_advisory_unlock(%s, %s)", keys)
|
||
finally:
|
||
# 即使显式解锁 SQL 失败,关闭会话也会由 PostgreSQL 释放会话级锁。
|
||
connection.close()
|