Files
adminandClaude Fable 5 4866b0408a feat(bioflow): extension lifecycle backend (P0) - spec whitelist, state machine, AI build, intent layers
服务端生命周期部分(契约 §1/§2/§4/§5/§6;Dev-A):
- extension_spec.py: spec_v2 白名单 12 占位符 + {output_dir} 必含 + 白名单外拒绝
- extensions.py: 生命周期状态机(契约 §2 枚举) + {extensions}/{ext_id} 文件存储
  + ExtensionExecutor 接口占位(Dev-B 实现 extension_exec.py, inject_executor 注入)
  + extension_* 事件帧经 EventBus 发布(events.py 注册, 不改 job_* 语义)
- extension_llm.py: build/repair AI 转换(DOCGEN_LLM_* 优先, BIOFLOW_EXTENSION_* 别名)
  LLM 不可用/坏输出一律回退规则版(_detect_requirements/_command_to_template 移植), 绝不 500
- routers/extension.py: 契约 §6 端点全集; trial/run 执行器未接入返回 503
- intent.py: 契约 §5 两层判定纯函数(A 创建词库 × B 已发布扩展打分 + 歧义消解 + 漏判安全)
  chat/stream 接线留给 Dev-C 主分支整合
- config/schemas/main: upload_ext_allow 加 .zip/.py/.r/.sh; extensions_dir +
  extension_max_repair_rounds(20) + LLM 键注释; 模型; router 注册
- tests: 5 个测试文件 71 用例; 全仓 286 用例通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-14 13:48:37 +08:00

82 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""main.py —— 生信服务应用入口(uvicorn 入口,明文白名单文件)。
- 独立部署树 bioflow_service/(FR-BIO-028:独立进程 8098 + 独立 SQLite)。
- lifespan:init_db(幂等迁移)→ init_context(kernel/catalog/planner/runner)
→ recover(服务重启恢复 in-flight 续跑,FR-BIO-031)→ shutdown 清理。
- 端点:health(/api/bioflow/health,前端探活 FR-BIO-025)。
- 不依赖 BFF/生产服务(8090/8091 等绝不探测/连接)。
"""
from __future__ import annotations
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .config import get_settings
from .context import context, init_context, shutdown_context
from .db import close_db, init_db
from .routers import bioarchives, bioflow, biojobs, extension
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_app() -> FastAPI:
settings = get_settings()
app = FastAPI(title=settings.app_name, version=settings.version)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生信服务直连形态(FR-BIO-027);BFF 反代为同源
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
@asynccontextmanager
async def _lifespan(_app: FastAPI):
# 测试注入:context.db 已由测试预置(build_context 注入的临时库),
# 跳过启动初始化(不替换全局 db、不加载真实映射表、不 recover)。
# 注意经模块属性动态读取(monkeypatch 替换的是 context 模块的全局)。
from . import context as context_mod
injected = context_mod.context.db is not None
if not injected:
try:
await init_db()
except Exception:
# SQLite 初始化失败不阻断服务启动(health 报 db 降级;端点返回 INTERNAL)
logger.exception("main: 数据库初始化失败(health 将报 db 降级)")
try:
await init_context()
except Exception:
logger.exception("main: context 初始化失败(kernel 映射表缺失时 docker 执行不可用)")
try:
if context_mod.context.runner is not None:
recovered = await context_mod.context.runner.recover()
if recovered:
logger.info("main: 重启恢复 %d 个 in-flight 任务续跑", len(recovered))
except Exception:
logger.exception("main: 重启恢复扫描失败(任务不重跑,等待后续操作)")
try:
yield
finally:
await shutdown_context()
try:
await close_db()
except Exception:
pass
app.router.lifespan_context = _lifespan
app.include_router(bioflow.router)
app.include_router(biojobs.router)
app.include_router(bioarchives.router)
app.include_router(extension.router)
return app
app = create_app()