chore(v2): 从兴邦预审v2白名单抽取独立可运行基座
按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单, 从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。 - 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等 4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/ ori_file)、Office 临时文件 - 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案 - 未部署、未启动服务、未调用模型 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""Transactional JSON storage. Connections are closed, including error paths."""
|
||||
import json
|
||||
import sqlite3
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timezone
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def uid(prefix):
|
||||
return prefix + "-" + uuid4().hex
|
||||
|
||||
|
||||
def digest(value):
|
||||
return sha256(json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()).hexdigest()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transaction(path):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
db = sqlite3.connect(path, timeout=15)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("PRAGMA busy_timeout=15000")
|
||||
try:
|
||||
with db:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def initialize(path):
|
||||
with transaction(path) as db:
|
||||
db.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS assistant_objects (
|
||||
kind TEXT NOT NULL, id TEXT NOT NULL, project_id TEXT NOT NULL,
|
||||
revision INTEGER NOT NULL, body TEXT NOT NULL, PRIMARY KEY(kind,id));
|
||||
CREATE INDEX IF NOT EXISTS assistant_scope ON assistant_objects(kind,project_id);
|
||||
CREATE TABLE IF NOT EXISTS assistant_events (
|
||||
seq INTEGER PRIMARY KEY AUTOINCREMENT, project_id TEXT NOT NULL,
|
||||
run_id TEXT, body TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS assistant_idempotency (
|
||||
actor TEXT, operation TEXT, key TEXT, request_hash TEXT, object_id TEXT,
|
||||
PRIMARY KEY(actor,operation,key));
|
||||
CREATE TABLE IF NOT EXISTS assistant_jobs (
|
||||
id TEXT PRIMARY KEY, project_id TEXT, state TEXT, owner TEXT,
|
||||
lease_until REAL NOT NULL DEFAULT 0, attempts INTEGER NOT NULL DEFAULT 0);
|
||||
CREATE TABLE IF NOT EXISTS assistant_meta (key TEXT PRIMARY KEY, value TEXT);
|
||||
""")
|
||||
schema = Path(__file__).resolve().parents[1] / "fixtures" / "xingbang_demo" / "business_flow_schema.sql"
|
||||
if schema.exists():
|
||||
db.executescript(schema.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def get(db, kind, object_id):
|
||||
row = db.execute("SELECT body FROM assistant_objects WHERE kind=? AND id=?", (kind, object_id)).fetchone()
|
||||
if row is None:
|
||||
raise KeyError(f"{kind} 不存在")
|
||||
return json.loads(row[0])
|
||||
|
||||
|
||||
def listing(db, kind, project_id=None):
|
||||
if project_id is None:
|
||||
rows = db.execute("SELECT body FROM assistant_objects WHERE kind=? ORDER BY rowid DESC", (kind,))
|
||||
else:
|
||||
rows = db.execute("SELECT body FROM assistant_objects WHERE kind=? AND project_id=? ORDER BY rowid DESC", (kind, project_id))
|
||||
return [json.loads(row[0]) for row in rows]
|
||||
|
||||
|
||||
def put(db, kind, body, expected=None, immutable=False):
|
||||
old = db.execute("SELECT revision FROM assistant_objects WHERE kind=? AND id=?", (kind, body["id"])).fetchone()
|
||||
if immutable and old:
|
||||
raise ValueError("发布版本和历史快照不可覆盖")
|
||||
if expected is not None and (not old or old[0] != expected):
|
||||
raise Conflict("内容已更新,请刷新后重试")
|
||||
body = dict(body, revision=(old[0] + 1 if old else 1))
|
||||
db.execute("INSERT INTO assistant_objects VALUES (?,?,?,?,?) ON CONFLICT(kind,id) DO UPDATE SET revision=excluded.revision,body=excluded.body",
|
||||
(kind, body["id"], body.get("project_id", ""), body["revision"], json.dumps(body, ensure_ascii=False, allow_nan=False)))
|
||||
return body
|
||||
|
||||
|
||||
def event(db, project_id, action, actor, detail=None, run_id=None):
|
||||
body = {"time": now(), "action": action, "actor": actor, "detail": detail or {}}
|
||||
db.execute("INSERT INTO assistant_events(project_id,run_id,body) VALUES (?,?,?)", (project_id, run_id, json.dumps(body, ensure_ascii=False)))
|
||||
|
||||
|
||||
def time_event(db, project_id, phase_code, phase_name, role, category, actor, source_type, source_id,
|
||||
status="running", started_at=None, ended_at=None, detail=None, event_id=None):
|
||||
"""Create an immutable phase interval; callers close it with finish_time_event."""
|
||||
event_id = event_id or uid("time")
|
||||
db.execute(
|
||||
"INSERT INTO business_time_events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(event_id, project_id, phase_code, phase_name, role, category, status,
|
||||
started_at or now(), ended_at, actor, source_type, source_id,
|
||||
json.dumps(detail or {}, ensure_ascii=False)),
|
||||
)
|
||||
return event_id
|
||||
|
||||
|
||||
def finish_time_event(db, source_type, source_id, status="completed", ended_at=None):
|
||||
db.execute(
|
||||
"UPDATE business_time_events SET status=?,ended_at=? WHERE source_type=? AND source_id=? AND status='running'",
|
||||
(status, ended_at or now(), source_type, source_id),
|
||||
)
|
||||
|
||||
|
||||
def time_listing(db, project_ids=None):
|
||||
query = "SELECT * FROM business_time_events"
|
||||
params = []
|
||||
if project_ids is not None:
|
||||
if not project_ids:
|
||||
return []
|
||||
query += " WHERE project_id IN (" + ",".join("?" for _ in project_ids) + ")"
|
||||
params.extend(project_ids)
|
||||
query += " ORDER BY started_at"
|
||||
result = []
|
||||
for row in db.execute(query, params):
|
||||
item = dict(row)
|
||||
item["detail"] = json.loads(item["detail"] or "{}")
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
class Conflict(ValueError):
|
||||
pass
|
||||
Reference in New Issue
Block a user