feat(pbl_compiler): 初始提交 - pbls 项目模块代码入库
This commit is contained in:
parent
c26de54362
commit
8266c9a33d
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
.DS_Store
|
||||
*.log
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
.venv/
|
||||
venv/
|
||||
132
json/compiler_pbl_capability_registry.json
Normal file
132
json/compiler_pbl_capability_registry.json
Normal file
@ -0,0 +1,132 @@
|
||||
{
|
||||
"tblname": "pbl_capability_registry",
|
||||
"alias": "pbl_compiler",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"capability_key": {
|
||||
"label": "能力键",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"category": {
|
||||
"label": "分类",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"args_schema_json": {
|
||||
"label": "参数JSON Schema",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"permission_required": {
|
||||
"label": "所需权限",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"is_enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox",
|
||||
"list": true
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"description": {
|
||||
"label": "说明",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"capability_key": {
|
||||
"label": "能力键",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"category": {
|
||||
"label": "分类",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"args_schema_json": {
|
||||
"label": "参数JSON Schema",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"permission_required": {
|
||||
"label": "所需权限",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"is_enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox",
|
||||
"list": true
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"description": {
|
||||
"label": "说明",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"capability_key": {
|
||||
"label": "能力键",
|
||||
"type": "select"
|
||||
},
|
||||
"category": {
|
||||
"label": "分类",
|
||||
"type": "select"
|
||||
},
|
||||
"args_schema_json": {
|
||||
"label": "参数JSON Schema",
|
||||
"type": "text"
|
||||
},
|
||||
"permission_required": {
|
||||
"label": "所需权限",
|
||||
"type": "select"
|
||||
},
|
||||
"is_enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox"
|
||||
},
|
||||
"version_no": {
|
||||
"label": "版本",
|
||||
"type": "number"
|
||||
},
|
||||
"description": {
|
||||
"label": "说明",
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
118
json/compiler_pbl_compiler_version.json
Normal file
118
json/compiler_pbl_compiler_version.json
Normal file
@ -0,0 +1,118 @@
|
||||
{
|
||||
"tblname": "pbl_compiler_version",
|
||||
"alias": "pbl_compiler",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"code": {
|
||||
"label": "版本编码",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"semver": {
|
||||
"label": "语义化版本",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"rules_hash": {
|
||||
"label": "规则集SHA256前缀",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"entrypoint": {
|
||||
"label": "入口函数",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox",
|
||||
"list": true
|
||||
},
|
||||
"notes": {
|
||||
"label": "说明",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"code": {
|
||||
"label": "版本编码",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"semver": {
|
||||
"label": "语义化版本",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"rules_hash": {
|
||||
"label": "规则集SHA256前缀",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"entrypoint": {
|
||||
"label": "入口函数",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox",
|
||||
"list": true
|
||||
},
|
||||
"notes": {
|
||||
"label": "说明",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"code": {
|
||||
"label": "版本编码",
|
||||
"type": "select"
|
||||
},
|
||||
"semver": {
|
||||
"label": "语义化版本",
|
||||
"type": "select"
|
||||
},
|
||||
"rules_hash": {
|
||||
"label": "规则集SHA256前缀",
|
||||
"type": "text"
|
||||
},
|
||||
"entrypoint": {
|
||||
"label": "入口函数",
|
||||
"type": "select"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "启用",
|
||||
"type": "checkbox"
|
||||
},
|
||||
"notes": {
|
||||
"label": "说明",
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
188
json/compiler_pbl_game_definition.json
Normal file
188
json/compiler_pbl_game_definition.json
Normal file
@ -0,0 +1,188 @@
|
||||
{
|
||||
"tblname": "pbl_game_definition",
|
||||
"alias": "pbl_compiler",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "来源蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_version_no": {
|
||||
"label": "蓝图版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"compiler_version_id": {
|
||||
"label": "编译版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"content_fingerprint": {
|
||||
"label": "SHA-256 指纹",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"definition_json": {
|
||||
"label": "Game Definition 正文",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"world_id": {
|
||||
"label": "落库 world",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"scene_id": {
|
||||
"label": "落库 scene",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"entity_count": {
|
||||
"label": "实体数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"event_count": {
|
||||
"label": "事件数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"quality_state": {
|
||||
"label": "编译时质量状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"duration_ms": {
|
||||
"label": "耗时",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"label": "来源蓝图",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"blueprint_version_no": {
|
||||
"label": "蓝图版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"compiler_version_id": {
|
||||
"label": "编译版本",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"content_fingerprint": {
|
||||
"label": "SHA-256 指纹",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"definition_json": {
|
||||
"label": "Game Definition 正文",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"world_id": {
|
||||
"label": "落库 world",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"scene_id": {
|
||||
"label": "落库 scene",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"entity_count": {
|
||||
"label": "实体数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"event_count": {
|
||||
"label": "事件数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"quality_state": {
|
||||
"label": "编译时质量状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"duration_ms": {
|
||||
"label": "耗时",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"blueprint_id": {
|
||||
"label": "来源蓝图",
|
||||
"type": "number"
|
||||
},
|
||||
"blueprint_version_no": {
|
||||
"label": "蓝图版本",
|
||||
"type": "number"
|
||||
},
|
||||
"compiler_version_id": {
|
||||
"label": "编译版本",
|
||||
"type": "number"
|
||||
},
|
||||
"content_fingerprint": {
|
||||
"label": "SHA-256 指纹",
|
||||
"type": "text"
|
||||
},
|
||||
"definition_json": {
|
||||
"label": "Game Definition 正文",
|
||||
"type": "text"
|
||||
},
|
||||
"world_id": {
|
||||
"label": "落库 world",
|
||||
"type": "number"
|
||||
},
|
||||
"scene_id": {
|
||||
"label": "落库 scene",
|
||||
"type": "number"
|
||||
},
|
||||
"entity_count": {
|
||||
"label": "实体数",
|
||||
"type": "number"
|
||||
},
|
||||
"event_count": {
|
||||
"label": "事件数",
|
||||
"type": "number"
|
||||
},
|
||||
"quality_state": {
|
||||
"label": "编译时质量状态",
|
||||
"type": "select"
|
||||
},
|
||||
"duration_ms": {
|
||||
"label": "耗时",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
106
models/pbl_capability_registry.json
Normal file
106
models/pbl_capability_registry.json
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_capability_registry",
|
||||
"comment": "Capability Registry(第11章缺口补齐)",
|
||||
"module": "pbl_compiler",
|
||||
"engine": "mariadb",
|
||||
"charset": "utf8mb4",
|
||||
"tenant_scoped": true
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"comment": "租户ID(强制打头)",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"comment": "主键",
|
||||
"null": false,
|
||||
"type": "int",
|
||||
"length": 20,
|
||||
"unsigned": true,
|
||||
"auto_increment": true
|
||||
},
|
||||
{
|
||||
"name": "capability_key",
|
||||
"comment": "能力键",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"comment": "分类",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "args_schema_json",
|
||||
"comment": "参数JSON Schema",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "permission_required",
|
||||
"comment": "所需权限",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "is_enabled",
|
||||
"comment": "启用",
|
||||
"null": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "version_no",
|
||||
"comment": "版本",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"comment": "说明",
|
||||
"null": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_cap_key",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"capability_key",
|
||||
"version_no"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
100
models/pbl_compiler_version.json
Normal file
100
models/pbl_compiler_version.json
Normal file
@ -0,0 +1,100 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_compiler_version",
|
||||
"comment": "编译器版本(29.6 确定性)",
|
||||
"module": "pbl_compiler",
|
||||
"engine": "mariadb",
|
||||
"charset": "utf8mb4",
|
||||
"tenant_scoped": true
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"comment": "租户ID(强制打头)",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"comment": "主键",
|
||||
"null": false,
|
||||
"type": "int",
|
||||
"length": 20,
|
||||
"unsigned": true,
|
||||
"auto_increment": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"comment": "版本编码",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "semver",
|
||||
"comment": "语义化版本",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "rules_hash",
|
||||
"comment": "规则集SHA256前缀",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "entrypoint",
|
||||
"comment": "入口函数",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"comment": "启用",
|
||||
"null": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "notes",
|
||||
"comment": "说明",
|
||||
"null": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_cv_code",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
136
models/pbl_game_definition.json
Normal file
136
models/pbl_game_definition.json
Normal file
@ -0,0 +1,136 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_game_definition",
|
||||
"comment": "Game Definition(第9章 schema + 指纹)",
|
||||
"module": "pbl_compiler",
|
||||
"engine": "mariadb",
|
||||
"charset": "utf8mb4",
|
||||
"tenant_scoped": true
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"comment": "租户ID(强制打头)",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"comment": "主键",
|
||||
"null": false,
|
||||
"type": "int",
|
||||
"length": 20,
|
||||
"unsigned": true,
|
||||
"auto_increment": true
|
||||
},
|
||||
{
|
||||
"name": "blueprint_id",
|
||||
"comment": "来源蓝图",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "blueprint_version_no",
|
||||
"comment": "蓝图版本",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "compiler_version_id",
|
||||
"comment": "编译版本",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "content_fingerprint",
|
||||
"comment": "SHA-256 指纹",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "definition_json",
|
||||
"comment": "Game Definition 正文",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "world_id",
|
||||
"comment": "落库 world",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "scene_id",
|
||||
"comment": "落库 scene",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "entity_count",
|
||||
"comment": "实体数",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "event_count",
|
||||
"comment": "事件数",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "quality_state",
|
||||
"comment": "编译时质量状态",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "duration_ms",
|
||||
"comment": "耗时",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_gd_fp",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"content_fingerprint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_gd_bp",
|
||||
"unique": false,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
29
pbl_compiler/__init__.py
Normal file
29
pbl_compiler/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_compiler —— PBL Compiler v1(确定性编译 + Game Definition,M3a/M3b)
|
||||
|
||||
注册三处同步之 ②:必须导出 init.py 里的全部契约函数,漏一行 .dspy 调用即 NameError。
|
||||
"""
|
||||
from pbl_compiler.init import load_pbl_compiler
|
||||
from pbl_compiler.api import (
|
||||
pbl_compiler_compile,
|
||||
pbl_compiler_preview,
|
||||
pbl_compiler_compare,
|
||||
pbl_compiler_version_list,
|
||||
pbl_compiler_version_save,
|
||||
pbl_capability_list,
|
||||
pbl_capability_register,
|
||||
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'load_pbl_compiler',
|
||||
'pbl_compiler_compile',
|
||||
'pbl_compiler_preview',
|
||||
'pbl_compiler_compare',
|
||||
'pbl_compiler_version_list',
|
||||
'pbl_compiler_version_save',
|
||||
'pbl_capability_list',
|
||||
'pbl_capability_register',
|
||||
|
||||
]
|
||||
283
pbl_compiler/api.py
Normal file
283
pbl_compiler/api.py
Normal file
@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_compiler.api —— PBL Compiler v1:Blueprint → Game Definition(第9章,M3a/M3b)。
|
||||
|
||||
29.6 确定性三要素(可复现证明 = content_fingerprint 相同):
|
||||
1) 输入锁定:blueprint_id + blueprint_version_no(版本快照,不读可变主表);
|
||||
2) 规则锁定:compiler_version_id(规则集哈希 rules_hash);
|
||||
3) 无副作用:不依赖随机数/时间戳/网络;集合一律按稳定键排序后遍历。
|
||||
落库 world/scene/entity 走 pbl_domain_ext 契约(薄扩展,不改基表结构)。
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from pbl_common.api import (PblError, actor_id, crud, json_dump, now_str, sql_exec,
|
||||
sql_rows, sql_scalar, tenant_id)
|
||||
|
||||
COMPILER_VERSION = 'pbl-compiler-v1.0.0'
|
||||
RULES_HASH_SEED = 'pbl.compiler.rules.v1'
|
||||
GAME_DEF_SCHEMA = 'pbl.game_definition.v1'
|
||||
|
||||
|
||||
def _loads(v, d=None):
|
||||
if isinstance(v, (dict, list)):
|
||||
return v
|
||||
try:
|
||||
return json.loads(v) if v else (d or {})
|
||||
except ValueError:
|
||||
return d or {}
|
||||
|
||||
|
||||
def _canon(obj):
|
||||
return json.dumps(obj, sort_keys=True, ensure_ascii=False, separators=(',', ':'))
|
||||
|
||||
|
||||
def _sha(obj):
|
||||
return hashlib.sha256(_canon(obj).encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
async def _tid():
|
||||
return await tenant_id()
|
||||
|
||||
|
||||
async def _compiler_version(tid, requested=None):
|
||||
if requested:
|
||||
rows = await sql_rows('SELECT * FROM `pbl_compiler_version` WHERE `tenant_id` = ${t}$'
|
||||
' AND (`id` = ${r}$ OR `semver` = ${r2}$) LIMIT 1',
|
||||
{'t': tid, 'r': requested, 'r2': requested}, 'pbl')
|
||||
if rows:
|
||||
return rows[0]
|
||||
rows = await sql_rows('SELECT * FROM `pbl_compiler_version` WHERE `tenant_id` = ${t}$'
|
||||
' AND `enabled` = 1 ORDER BY `id` ASC LIMIT 1', {'t': tid}, 'pbl')
|
||||
if rows:
|
||||
return rows[0]
|
||||
cv = {'id': None, 'semver': COMPILER_VERSION, 'code': 'PBL_COMPILER_V1',
|
||||
'rules_hash': hashlib.sha256(RULES_HASH_SEED.encode()).hexdigest()[:16],
|
||||
'entrypoint': 'pbl_compiler.compile'}
|
||||
return cv
|
||||
|
||||
|
||||
async def _snapshot(tid, bp_id, version_no):
|
||||
rows = await sql_rows('SELECT * FROM `pbl_blueprint_version` WHERE `tenant_id` = ${t}$'
|
||||
' AND `blueprint_id` = ${b}$ AND `version_no` = ${v}$ LIMIT 1',
|
||||
{'t': tid, 'b': bp_id, 'v': version_no}, 'pbl')
|
||||
if rows:
|
||||
return _loads(rows[0]['snapshot_json'])
|
||||
bp = await sql_rows('SELECT * FROM `pbl_blueprint` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` = ${id}$ LIMIT 1', {'t': tid, 'id': bp_id}, 'pbl')
|
||||
if not bp:
|
||||
raise PblError('PBL_NOT_FOUND', '蓝图不存在')
|
||||
return _loads(bp[0]['content_json'])
|
||||
|
||||
|
||||
async def compile_model(model, cv, bp_id, version_no):
|
||||
"""纯函数式编译(无 DB、无时间、无随机)——确定性核心,可单测复算。"""
|
||||
goals = sorted(_loads(model.get('learning_goals'), []), key=lambda x: _canon(x))
|
||||
roles = sorted(_loads(model.get('roles'), []), key=lambda x: _canon(x))
|
||||
missions = sorted(_loads(model.get('missions'), []), key=lambda x: _canon(x))
|
||||
tasks = sorted(_loads(model.get('tasks'), []), key=lambda x: _canon(x))
|
||||
artifacts = sorted(_loads(model.get('artifacts'), []), key=lambda x: _canon(x))
|
||||
ev_specs = sorted(_loads(model.get('evidence_specs'), []), key=lambda x: _canon(x))
|
||||
rules_hash = cv.get('rules_hash') or hashlib.sha256(RULES_HASH_SEED.encode()).hexdigest()[:16]
|
||||
|
||||
entities, events, unlocks = [], [], []
|
||||
for m in missions:
|
||||
mid = m.get('obj_uid') or m.get('code') or _sha(m)[:12]
|
||||
entities.append({'entity_id': 'mission:%s' % mid, 'kind': 'mission',
|
||||
'capability_key': 'pbl.mission.show',
|
||||
'initial_state': {'completed': False, 'order': m.get('order_no', 0)},
|
||||
'server_authoritative': True})
|
||||
for t in [x for x in tasks if (x.get('mission_id') == m.get('id')
|
||||
or x.get('mission_uid') == mid)]:
|
||||
tid_ = t.get('obj_uid') or t.get('code') or _sha(t)[:12]
|
||||
entities.append({'entity_id': 'task:%s' % tid_, 'kind': 'task',
|
||||
'capability_key': 'pbl.task.track',
|
||||
'initial_state': {'done': False, 'parent': mid},
|
||||
'server_authoritative': True})
|
||||
events.append({'event_type': 'task.completed', 'source': 'task:%s' % tid_,
|
||||
'condition': {'all_of': ['task:%s.done == true' % tid_]},
|
||||
'response': [{'op': 'emit', 'event': 'task.completed'}]})
|
||||
unlocks.append({'from_event': 'mission.completed:%s' % mid, 'target': mid,
|
||||
'effect': 'pbl.unlockMission'})
|
||||
for r in roles:
|
||||
rid = r.get('obj_uid') or r.get('code') or _sha(r)[:12]
|
||||
entities.append({'entity_id': 'role:%s' % rid, 'kind': 'role',
|
||||
'capability_key': 'pbl.role.assign',
|
||||
'initial_state': {'assigned': False}, 'server_authoritative': True})
|
||||
for a in artifacts:
|
||||
aid = a.get('obj_uid') or a.get('code') or _sha(a)[:12]
|
||||
entities.append({'entity_id': 'artifact:%s' % aid, 'kind': 'artifact_spec',
|
||||
'capability_key': 'pbl.artifact.submit',
|
||||
'initial_state': {'submitted': False}, 'server_authoritative': True})
|
||||
for e in ev_specs:
|
||||
eid = e.get('obj_uid') or _sha(e)[:12]
|
||||
events.append({'event_type': 'evidence.required', 'source': 'evidence_spec:%s' % eid,
|
||||
'condition': {'min_count': e.get('min_count', 1)},
|
||||
'response': [{'op': 'collect', 'evidence_type':
|
||||
e.get('evidence_type') or 'artifact'}]})
|
||||
|
||||
definition = {
|
||||
'schema': GAME_DEF_SCHEMA,
|
||||
'meta': {'source_blueprint_id': bp_id, 'source_blueprint_version': version_no,
|
||||
'compiler_version': cv.get('semver') or COMPILER_VERSION,
|
||||
'compiler_rules_hash': rules_hash, 'deterministic': True},
|
||||
'world': {'mode': 'shared', 'authority': 'server', 'max_members': 8,
|
||||
'heartbeat_seconds': 15, 'poll_fallback_seconds': 3},
|
||||
'learning_goals': goals,
|
||||
'entities': sorted(entities, key=lambda x: x['entity_id']),
|
||||
'events': sorted(events, key=lambda x: _canon(x)),
|
||||
'unlock_graph': sorted(unlocks, key=lambda x: _canon(x)),
|
||||
'script_bindings': [{'event': ev['event_type'], 'script_type': 1,
|
||||
'rule_json': _canon(ev)}
|
||||
for ev in sorted(events, key=lambda x: _canon(x))],
|
||||
'capabilities': sorted({e['capability_key'] for e in entities}),
|
||||
}
|
||||
fp = _sha(definition)
|
||||
definition['meta']['content_fingerprint'] = fp
|
||||
return definition, fp
|
||||
|
||||
|
||||
async def pbl_compiler_compile(**kw):
|
||||
import time
|
||||
t0 = time.time()
|
||||
tid = await _tid()
|
||||
bp_id = kw.get('blueprint_id')
|
||||
bp = await sql_rows('SELECT * FROM `pbl_blueprint` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` = ${id}$ LIMIT 1', {'t': tid, 'id': bp_id}, 'pbl')
|
||||
if not bp:
|
||||
raise PblError('PBL_NOT_FOUND', '蓝图不存在')
|
||||
bp = bp[0]
|
||||
min_state = kw.get('min_quality_state') or 'pbl_ready'
|
||||
if bp['quality_state'] not in LADDER_AT_OR_ABOVE(min_state):
|
||||
raise PblError('PBL_COMPILE_GATE_FAILED',
|
||||
'quality_state=%s 未达 %s(第15章门禁)' % (bp['quality_state'], min_state))
|
||||
version_no = int(kw.get('blueprint_version_no') or bp['version_no'])
|
||||
cv = await _compiler_version(tid, kw.get('compiler_version_id'))
|
||||
model = await _snapshot(tid, bp_id, version_no)
|
||||
definition, fp = await compile_model(model, cv, bp_id, version_no)
|
||||
|
||||
dup = await sql_rows('SELECT * FROM `pbl_game_definition` WHERE `tenant_id` = ${t}$'
|
||||
' AND `content_fingerprint` = ${f}$ LIMIT 1',
|
||||
{'t': tid, 'f': fp}, 'pbl')
|
||||
if dup and not kw.get('force_new'):
|
||||
return {'ok': True, 'idempotent': True, 'id': dup[0]['id'],
|
||||
'content_fingerprint': fp, 'compiler_version': cv.get('semver'),
|
||||
'note': '同输入+同编译版本 → 同指纹(29.6 可复现)',
|
||||
'latency_ms': int((time.time() - t0) * 1000)}
|
||||
res = await crud('pbl_game_definition', 'pbl',
|
||||
['blueprint_id', 'blueprint_version_no', 'compiler_version_id',
|
||||
'content_fingerprint', 'definition_json', 'world_id', 'scene_id',
|
||||
'entity_count', 'event_count', 'quality_state', 'duration_ms']
|
||||
)['create'](
|
||||
blueprint_id=bp_id, blueprint_version_no=version_no,
|
||||
compiler_version_id=cv.get('id'), content_fingerprint=fp,
|
||||
definition_json=json_dump(definition), entity_count=len(definition['entities']),
|
||||
event_count=len(definition['events']), quality_state=bp['quality_state'],
|
||||
duration_ms=int((time.time() - t0) * 1000))
|
||||
if cv.get('id') is None:
|
||||
cvr = await crud('pbl_compiler_version', 'pbl',
|
||||
['code', 'semver', 'rules_hash', 'entrypoint', 'enabled', 'notes']
|
||||
)['create'](code=cv['code'], semver=cv['semver'],
|
||||
rules_hash=cv['rules_hash'], entrypoint=cv['entrypoint'],
|
||||
enabled=1, notes='自动登记(编译时确保版本可追溯)')
|
||||
await sql_exec('UPDATE `pbl_game_definition` SET `compiler_version_id` = ${c}$'
|
||||
' WHERE `id` = ${id}$ AND `tenant_id` = ${t}$',
|
||||
{'c': cvr['id'], 'id': res['id'], 't': tid}, 'pbl')
|
||||
materialized = None
|
||||
if kw.get('materialize'):
|
||||
from pbl_domain_ext.api import pbl_domain_materialize_game_definition
|
||||
materialized = await pbl_domain_materialize_game_definition(id=res['id'])
|
||||
await sql_exec('UPDATE `pbl_game_definition` SET `world_id` = ${w}$, `scene_id` = ${s}$'
|
||||
' WHERE `id` = ${id}$ AND `tenant_id` = ${t}$',
|
||||
{'w': materialized.get('world_id'), 's': materialized.get('scene_id'),
|
||||
'id': res['id'], 't': tid}, 'pbl')
|
||||
return {'ok': True, 'id': res['id'], 'content_fingerprint': fp,
|
||||
'compiler_version': cv.get('semver') or COMPILER_VERSION,
|
||||
'entities': len(definition['entities']), 'events': len(definition['events']),
|
||||
'materialized': materialized, 'budget_ms': 5000,
|
||||
'latency_ms': int((time.time() - t0) * 1000)}
|
||||
|
||||
|
||||
def LADDER_AT_OR_ABOVE(state):
|
||||
order = ['draft', 'needs_work', 'pbl_ready', 'playtest_ready', 'publish_ready']
|
||||
i = order.index(state) if state in order else 0
|
||||
return order[i:]
|
||||
|
||||
|
||||
async def pbl_compiler_preview(**kw):
|
||||
"""干跑(不落库),返回定义 + 指纹,供教师预览与 diff。"""
|
||||
tid = await _tid()
|
||||
bp = await sql_rows('SELECT * FROM `pbl_blueprint` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` = ${id}$ LIMIT 1', {'t': tid, 'id': kw.get('blueprint_id')},
|
||||
'pbl')
|
||||
if not bp:
|
||||
raise PblError('PBL_NOT_FOUND', '蓝图不存在')
|
||||
cv = await _compiler_version(tid, kw.get('compiler_version_id'))
|
||||
model = await _snapshot(tid, bp[0]['id'], int(kw.get('blueprint_version_no')
|
||||
or bp[0]['version_no']))
|
||||
definition, fp = await compile_model(model, cv, bp[0]['id'],
|
||||
int(kw.get('blueprint_version_no') or bp[0]['version_no']))
|
||||
return {'ok': True, 'preview': True, 'content_fingerprint': fp,
|
||||
'definition': definition, 'would_write_rows': 1}
|
||||
|
||||
|
||||
async def pbl_compiler_compare(**kw):
|
||||
"""两版定义结构化 diff(确定性验证:同输入两次编译指纹必须一致)。"""
|
||||
tid = await _tid()
|
||||
ids = [kw.get('a'), kw.get('b')]
|
||||
rows = await sql_rows('SELECT * FROM `pbl_game_definition` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` IN (${a}$,${b}$)', {'t': tid, 'a': ids[0], 'b': ids[1]}, 'pbl')
|
||||
if len(rows) < 2:
|
||||
raise PblError('PBL_GD_NOT_FOUND', '需两个已编译的 Game Definition')
|
||||
a, b = sorted(rows, key=lambda r: r['id'])
|
||||
da, db = _loads(a['definition_json']), _loads(b['definition_json'])
|
||||
diff = {'added': [], 'removed': [], 'changed': []}
|
||||
ka = {x['entity_id'] for x in da.get('entities', [])}
|
||||
kb = {x['entity_id'] for x in db.get('entities', [])}
|
||||
diff['added'], diff['removed'] = sorted(kb - ka), sorted(ka - kb)
|
||||
for x in da.get('entities', []):
|
||||
y = next((z for z in db.get('entities', []) if z['entity_id'] == x['entity_id']), None)
|
||||
if y and _canon(x) != _canon(y):
|
||||
diff['changed'].append(x['entity_id'])
|
||||
return {'ok': True, 'a': {'id': a['id'], 'fp': a['content_fingerprint']},
|
||||
'b': {'id': b['id'], 'fp': b['content_fingerprint']},
|
||||
'identical': a['content_fingerprint'] == b['content_fingerprint'], 'diff': diff}
|
||||
|
||||
|
||||
async def pbl_compiler_version_list(**kw):
|
||||
return await crud('pbl_compiler_version', 'pbl',
|
||||
['code', 'semver', 'rules_hash', 'entrypoint', 'enabled', 'notes']
|
||||
)['list'](**kw)
|
||||
|
||||
|
||||
async def pbl_compiler_version_save(**kw):
|
||||
sc = crud('pbl_compiler_version', 'pbl',
|
||||
['code', 'semver', 'rules_hash', 'entrypoint', 'enabled', 'notes'])
|
||||
payload = dict(kw)
|
||||
payload.setdefault('rules_hash',
|
||||
hashlib.sha256((RULES_HASH_SEED + str(kw.get('semver', ''))).encode())
|
||||
.hexdigest()[:16])
|
||||
if kw.get('id'):
|
||||
return await sc['update'](**payload)
|
||||
return await sc['create'](**payload)
|
||||
|
||||
|
||||
async def pbl_capability_list(**kw):
|
||||
return await crud('pbl_capability_registry', 'pbl',
|
||||
['capability_key', 'category', 'args_schema_json', 'permission_required',
|
||||
'is_enabled', 'version_no', 'description'])['list'](**kw)
|
||||
|
||||
|
||||
async def pbl_capability_register(**kw):
|
||||
key = kw.get('capability_key')
|
||||
if not key:
|
||||
raise PblError('PBL_PARAM_MISSING', '缺少 capability_key')
|
||||
sc = crud('pbl_capability_registry', 'pbl',
|
||||
['capability_key', 'category', 'args_schema_json', 'permission_required',
|
||||
'is_enabled', 'version_no', 'description'])
|
||||
payload = dict(kw)
|
||||
payload['args_schema_json'] = json_dump(kw.get('args_schema_json'))
|
||||
payload.setdefault('version_no', 1)
|
||||
r = await sc['upsert'](['capability_key', 'version_no'], **payload)
|
||||
r['capability_key'] = key
|
||||
return r
|
||||
31
pbl_compiler/init.py
Normal file
31
pbl_compiler/init.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""`load_pbl_compiler()` —— pbl_compiler 模块唯一挂载入口。
|
||||
|
||||
注册三处同步之 ③:env.<契约名> = <契约名>(① 定义在 api.py,② 导出在 __init__.py)。
|
||||
"""
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
from pbl_compiler.api import (
|
||||
pbl_compiler_compile,
|
||||
pbl_compiler_preview,
|
||||
pbl_compiler_compare,
|
||||
pbl_compiler_version_list,
|
||||
pbl_compiler_version_save,
|
||||
pbl_capability_list,
|
||||
pbl_capability_register,
|
||||
|
||||
)
|
||||
|
||||
|
||||
def load_pbl_compiler():
|
||||
env = ServerEnv()
|
||||
env.pbl_compiler_compile = pbl_compiler_compile
|
||||
env.pbl_compiler_preview = pbl_compiler_preview
|
||||
env.pbl_compiler_compare = pbl_compiler_compare
|
||||
env.pbl_compiler_version_list = pbl_compiler_version_list
|
||||
env.pbl_compiler_version_save = pbl_compiler_version_save
|
||||
env.pbl_capability_list = pbl_capability_list
|
||||
env.pbl_capability_register = pbl_capability_register
|
||||
|
||||
return 'pbl_compiler'
|
||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "pbl_compiler"
|
||||
version = "0.1.0"
|
||||
description = "PBL Compiler v1(确定性编译 + Game Definition,M3a/M3b)"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["pbl_compiler"]
|
||||
46
scripts/load_path.py
Normal file
46
scripts/load_path.py
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_compiler RBAC 路径注册(硬门禁 6.6 / QC #11)。
|
||||
|
||||
约定:
|
||||
- 路径 = 模块自动路由 `/pbl_compiler/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
|
||||
- 角色 `logined` = 登录即可访问的读接口;写接口按角色分级(teacher/admin);
|
||||
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
MODULE = 'pbl_compiler'
|
||||
|
||||
# (path, role)
|
||||
PATHS = [
|
||||
('/pbl_compiler/api/pbl_compiler_compile.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_compiler_preview.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_compiler_compare.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_compiler_version_list.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_compiler_version_save.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_capability_list.dspy', 'logined'),
|
||||
('/pbl_compiler/api/pbl_capability_register.dspy', 'logined'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
|
||||
done, missing = 0, []
|
||||
for path, role in PATHS:
|
||||
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
|
||||
tool, role, path],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
|
||||
done += 1
|
||||
else:
|
||||
missing.append((path, role))
|
||||
print('[%s] rbac paths: total=%%d ok=%%d pending=%%d' %% (len(PATHS), done, len(missing)))
|
||||
for path, role in missing:
|
||||
print(' PENDING %%-12s %%s' %% (role, path))
|
||||
return len(missing) == 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(0 if register() else 1)
|
||||
27
skill/SKILL.md
Normal file
27
skill/SKILL.md
Normal file
@ -0,0 +1,27 @@
|
||||
# pbl_compiler 模块技能(自动生成骨架 + 人工补充)
|
||||
|
||||
## 定位
|
||||
PBL Compiler v1(确定性编译 + Game Definition,M3a/M3b)
|
||||
|
||||
## 挂载
|
||||
`from pbl_compiler.init import load_pbl_compiler` → `load_pbl_compiler()`(应用 app/pbls.py init() 中按序调用)
|
||||
|
||||
## 数据表(3 张)
|
||||
- `pbl_compiler_version`:编译器版本(29.6 确定性)
|
||||
- `pbl_game_definition`:Game Definition(第9章 schema + 指纹)
|
||||
- `pbl_capability_registry`:Capability Registry(第11章缺口补齐)
|
||||
|
||||
## 契约接口(7 个,路径 `/pbl_compiler/api/<name>.dspy`)
|
||||
- `pbl_compiler_compile`
|
||||
- `pbl_compiler_preview`
|
||||
- `pbl_compiler_compare`
|
||||
- `pbl_compiler_version_list`
|
||||
- `pbl_compiler_version_save`
|
||||
- `pbl_capability_list`
|
||||
- `pbl_capability_register`
|
||||
|
||||
## 陷阱
|
||||
- 库名一律 `ServerEnv().get_module_dbname('pbl_compiler')`,禁止硬编码 DBNAME。
|
||||
- sqlor 只有 `C/U/D/R/I/sqlExe`;查询走 pbl_common.api 的 q_all/q_one(已适配)。
|
||||
- 所有读写强制带 `tenant_id`(pbl_common.api.tenant_id()),缺失即 fail-closed 报错。
|
||||
- 新增契约需同步三处:api.py 定义 + __init__.py 导出 + init.py env 注册 + scripts/load_path.py 路径。
|
||||
53
sql/pbl_compiler.sql
Normal file
53
sql/pbl_compiler.sql
Normal file
@ -0,0 +1,53 @@
|
||||
-- pbl_compiler 表 DDL(自动生成,与 apps/pbls/scripts/ddl/pbls_tables.sql 同源)
|
||||
CREATE TABLE IF NOT EXISTS `pbl_compiler_version` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`code` VARCHAR(64) NOT NULL COMMENT "版本编码",
|
||||
`semver` VARCHAR(64) NOT NULL COMMENT "语义化版本",
|
||||
`rules_hash` VARCHAR(32) NOT NULL COMMENT "规则集SHA256前缀",
|
||||
`entrypoint` VARCHAR(64) NOT NULL COMMENT "入口函数",
|
||||
`enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "启用",
|
||||
`notes` TEXT NULL COMMENT "说明",
|
||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_cv_code` (`tenant_id`, `code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="编译器版本(29.6 确定性)";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_game_definition` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "来源蓝图",
|
||||
`blueprint_version_no` INT NOT NULL DEFAULT 0 COMMENT "蓝图版本",
|
||||
`compiler_version_id` BIGINT UNSIGNED NOT NULL COMMENT "编译版本",
|
||||
`content_fingerprint` VARCHAR(32) NOT NULL COMMENT "SHA-256 指纹",
|
||||
`definition_json` LONGTEXT NULL COMMENT "Game Definition 正文",
|
||||
`world_id` BIGINT UNSIGNED NOT NULL COMMENT "落库 world",
|
||||
`scene_id` BIGINT UNSIGNED NOT NULL COMMENT "落库 scene",
|
||||
`entity_count` INT NOT NULL DEFAULT 0 COMMENT "实体数",
|
||||
`event_count` INT NOT NULL DEFAULT 0 COMMENT "事件数",
|
||||
`quality_state` VARCHAR(64) NOT NULL COMMENT "编译时质量状态",
|
||||
`duration_ms` INT NOT NULL DEFAULT 0 COMMENT "耗时",
|
||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_gd_fp` (`tenant_id`, `content_fingerprint`),
|
||||
KEY `idx_gd_bp` (`tenant_id`, `blueprint_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Game Definition(第9章 schema + 指纹)";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_capability_registry` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`capability_key` VARCHAR(64) NOT NULL COMMENT "能力键",
|
||||
`category` VARCHAR(64) NOT NULL COMMENT "分类",
|
||||
`args_schema_json` LONGTEXT NULL COMMENT "参数JSON Schema",
|
||||
`permission_required` VARCHAR(64) NOT NULL COMMENT "所需权限",
|
||||
`is_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "启用",
|
||||
`version_no` INT NOT NULL DEFAULT 0 COMMENT "版本",
|
||||
`description` TEXT NULL COMMENT "说明",
|
||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_cap_key` (`tenant_id`, `capability_key`, `version_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Capability Registry(第11章缺口补齐)";
|
||||
|
||||
4
wwwroot/api/pbl_capability_list.dspy
Normal file
4
wwwroot/api/pbl_capability_list.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_capability_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_capability_list.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_capability_list(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_capability_register.dspy
Normal file
4
wwwroot/api/pbl_capability_register.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_capability_register.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_capability_register.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_capability_register(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_compiler_compare.dspy
Normal file
4
wwwroot/api/pbl_compiler_compare.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_compiler_compare.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_compiler_compare.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_compiler_compare(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_compiler_compile.dspy
Normal file
4
wwwroot/api/pbl_compiler_compile.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_compiler_compile.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_compiler_compile.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_compiler_compile(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_compiler_preview.dspy
Normal file
4
wwwroot/api/pbl_compiler_preview.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_compiler_preview.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_compiler_preview.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_compiler_preview(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_compiler_version_list.dspy
Normal file
4
wwwroot/api/pbl_compiler_version_list.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_compiler_version_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_compiler_version_list.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_compiler_version_list(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_compiler_version_save.dspy
Normal file
4
wwwroot/api/pbl_compiler_version_save.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_compiler/api/pbl_compiler_version_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_compiler/api/pbl_compiler_version_save.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_compiler_version_save(**params_kw)
|
||||
return data
|
||||
231
wwwroot/index.ui
Normal file
231
wwwroot/index.ui
Normal file
@ -0,0 +1,231 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "PBL Compiler v1(确定性编译 + Game Definition,M3a/M3b)",
|
||||
"fontSize": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"gap": "16px",
|
||||
"minWidth": "250px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_compiler_compile.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_compiler_compile"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_compiler_preview.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_compiler_preview"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_compiler_compare.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_compiler_compare"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_compiler_version_list.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_compiler_version_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_compiler_version_save.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_compiler_version_save"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_capability_list.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_capability_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_compiler_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_capability_register.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_capability_register"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "pbl_compiler_content",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"flex": "1",
|
||||
"marginTop": "20px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user