Metadata-Version: 2.4
Name: tg-bot-plugin-contract-core
Version: 0.2.0
Summary: TG-BOT 插件的共享 manifest、bundle、digest 与 signer 校验合同层。
Author: Fire Dragons
License: MIT
Requires-Python: >=3.11
Requires-Dist: sigstore>=4.2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Description-Content-Type: text/markdown

# tg-bot-plugin-contract-core

`tg-bot-plugin-contract-core` 是 TG-BOT 插件 `manifest` 与 `.tgpkg` bundle 的共享正式合同层。

它提供：

- manifest 规范化与合同校验
- `artifact_digest` 计算
- 通过 `package_sha256` 计算 canonical `.tgpkg` 字节身份
- 可复现 bundle 写入
- bundle 结构检查
- Sigstore bundle 验签
- signer identity 提取、capability 合同校验与 trusted publisher 匹配

它有意不提供：

- TG-BOT runtime 加载或插件类实例化
- trusted signer 文件发现或配置加载
- 超出 bundle 与 signer 合同范围的平台策略决策

## 安装

```bash
pip install tg-bot-plugin-contract-core
```

## 发布与验证

- `pull_request` / `main` 分支会执行：
  - `pytest`
  - wheel-only 构建
  - wheel 安装 smoke test
- `tag push v*` 会额外执行：
  - `tag == project.version` 校验
  - PyPI 发布
  - GitHub Release 附件上传

## 公开 API

```python
from tg_bot_plugin_contract_core import (
    compute_artifact_digest,
    compute_package_sha256,
    inspect_bundle,
    match_trusted_publisher_identity,
    validate_bundle_same_profile,
    validate_bundle_target_profile,
    verify_bundle,
    write_reproducible_bundle,
)
```

## 使用示例

```python
from pathlib import Path

from tg_bot_plugin_contract_core import (
    compute_artifact_digest,
    inspect_bundle,
    match_trusted_publisher_identity,
    verify_bundle,
)

plugin_dir = Path("plugin-staging")
manifest_payload = {
    "plugin_id": "demo",
    "plugin_version": "1.0.0",
    "name": "demo",
    "description": "demo plugin",
    "category": "utility",
    "runtime_profile_id": "cpython-3.13-linux-x86_64-gnu",
    "artifact_digest": "",
    "entrypoint": {"module_path": "__init__", "symbol": "DemoPlugin"},
    "config_schema": {},
    "interaction_schema": {"capabilities": []},
    "declared_scopes": [],
    "declared_default_scope_matrix": {
        "private": {
            "enabled": True,
            "access": "free",
            "plugin_patch": {},
            "platform_patch": {},
        }
    },
    "feature_catalog": [
        {
            "feature_id": "overview",
            "title": "Overview",
            "default_access": "free",
            "supported_scopes": ["private"],
            "ui_surface_refs": ["user_web:overview"],
        }
    ],
    "user_web_entries": [
        {
            "entry_id": "overview",
            "title": "Overview",
            "supported_scopes": ["private"],
            "nav_group": "workspace",
            "required_features": ["overview"],
            "landing_eligible": True,
            "visibility": "visible",
            "page_render": {
                "sections": [
                    {
                        "section_id": "welcome",
                        "kind": "text",
                        "title": "Workspace Overview",
                        "body": "Render user-facing plugin content here instead of turning user-web into a control-plane page.",
                    },
                    {
                        "section_id": "status",
                        "kind": "info_list",
                        "title": "Current Status",
                        "items": [
                            {"label": "Access", "value_from": "entry.access"},
                            {"label": "Database", "value_from": "ctx.db.status"},
                        ],
                    },
                ]
            },
        }
    ],
    "platform_contract": {
        "resource_slots": {
            "primary_datasource": {
                "resource_kind": "postgresql",
                "binding_scope": "bot",
                "required_for_bot_activation": True,
                "exposed_as": "ctx.db",
            }
        }
    },
    "declared_capabilities": [],
}

artifact_digest = compute_artifact_digest(plugin_dir, manifest_payload)
inspection = inspect_bundle("dist/plugins/demo/demo-1.0.0-cpython-3.13-linux-x86_64-gnu.tgpkg")
verification = verify_bundle(inspection, allow_unsigned_dev=False)
match_result = match_trusted_publisher_identity(verification, trusted_publishers=[
    {
        "publisher_id": "github-release-main",
        "issuer": "https://token.actions.githubusercontent.com",
        "repository_owner": "Fire-Dragons",
        "reusable_workflow_repository": "Fire-Dragons/tg-bot-plugin-buildkit",
        "reusable_workflow_path": ".github/workflows/release-plugin.yml",
        "enabled": True,
    }
])
```

## 说明

- `verify_bundle()` 会先从最终 `.tgpkg` 中剥离 `META-INF/sigstore.bundle.json`，重建 unsigned canonical bundle，再校验 Sigstore 签名材料并提取 signer identity。
- `match_trusted_publisher_identity()` 是 canonical API，会基于 `issuer + repository_owner + reusable_workflow_repository + reusable_workflow_path` 执行发布者身份匹配，忽略 workflow 版本 ref。
- `match_signer_identity()` 仅保留为历史兼容入口，并只委托到 `match_trusted_publisher_identity()`。
- `manifest.declared_capabilities` 与 `manifest.interaction_schema.capabilities` 必须同时存在、结构合法且内容完全一致，否则会分别返回 `manifest_capability_missing`、`manifest_capability_invalid` 或 `manifest_capability_conflict`。
- `manifest.declared_default_scope_matrix` 是作者侧 scope 声明真源；平台公开读面仍会继续使用 `default_scope_matrix` 这条既有投影链，不应在作者仓再维护第二套字段。
- `manifest.feature_catalog`、`manifest.user_web_entries` 与 `manifest.platform_contract` 现在都是正式合同字段，不再属于主仓私有扩展；后续 page、feature、resource slot、lifecycle 与 dependency 摘要都应从这里投影。
- `allow_unsigned_dev=True` 仅用于受控的本地开发链路。
