feat(cloud): cloud-managed skill store + per-host entitlement + sync versioning
Tests / Test passed: 819

Adds cloud/skills.py (domain + service), SQLAlchemy models and Alembic
migration 0010_skill_management (cloud_skills, cloud_skill_entitlements,
cloud_skill_sync_state, a per-host changelog, and host_skill_inventory),
and repository methods with a monotonic per-host entitlement_version that
drives correct incremental fetch_host_delta. cloud-api suite green (41
passed); HEAD_REVISION bumped to 0010.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 07:45:22 +08:00
co-authored by Claude Opus 4.6
parent 8baf3a6a8b
commit 52e442790a
7 changed files with 931 additions and 5 deletions
@@ -360,3 +360,83 @@ class LlmProviderSettingsRow(Base):
Integer, nullable=False, default=1, server_default=text("1")
)
updated_at: Mapped[str] = mapped_column(String, nullable=False)
class CloudSkillRow(Base):
__tablename__ = "cloud_skills"
__table_args__ = (
UniqueConstraint(
"name_normalized",
name="uq_cloud_skills_name_normalized",
),
Index("ix_cloud_skills_kind", "kind"),
)
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
name_normalized: Mapped[str] = mapped_column(String, nullable=False)
kind: Mapped[str] = mapped_column(String, nullable=False)
description: Mapped[str] = mapped_column(Text, nullable=False)
tags_json: Mapped[str] = mapped_column(Text, nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
steps_json: Mapped[str] = mapped_column(Text, nullable=False)
parameters_json: Mapped[str] = mapped_column(Text, nullable=False)
revision: Mapped[int] = mapped_column(
Integer, nullable=False, default=1, server_default=text("1")
)
created_at: Mapped[str] = mapped_column(String, nullable=False)
updated_at: Mapped[str] = mapped_column(String, nullable=False)
class CloudSkillEntitlementRow(Base):
__tablename__ = "cloud_skill_entitlements"
__table_args__ = (
UniqueConstraint(
"skill_id",
"host_id",
name="uq_cloud_skill_entitlements_skill_host",
),
Index("ix_cloud_skill_entitlements_host_id", "host_id"),
Index("ix_cloud_skill_entitlements_skill_id", "skill_id"),
)
skill_id: Mapped[str] = mapped_column(
String,
ForeignKey("cloud_skills.id"),
primary_key=True,
)
host_id: Mapped[str] = mapped_column(String, primary_key=True)
granted_at: Mapped[str] = mapped_column(String, nullable=False)
class CloudSkillSyncStateRow(Base):
__tablename__ = "cloud_skill_sync_state"
host_id: Mapped[str] = mapped_column(String, primary_key=True)
last_version: Mapped[int] = mapped_column(
Integer, nullable=False, default=0, server_default=text("0")
)
updated_at: Mapped[str] = mapped_column(String, nullable=False)
class CloudSkillHostChangeRow(Base):
"""Per-host changelog enabling incremental sync (design D3)."""
__tablename__ = "cloud_skill_host_changes"
__table_args__ = (
Index("ix_cloud_skill_host_changes_host_version", "host_id", "version"),
)
host_id: Mapped[str] = mapped_column(String, primary_key=True)
version: Mapped[int] = mapped_column(Integer, primary_key=True)
skill_id: Mapped[str] = mapped_column(String, nullable=False)
change_type: Mapped[str] = mapped_column(String, nullable=False)
recorded_at: Mapped[str] = mapped_column(String, nullable=False)
class HostSkillInventoryRow(Base):
__tablename__ = "host_skill_inventory"
host_id: Mapped[str] = mapped_column(String, primary_key=True)
payload_json: Mapped[str] = mapped_column(Text, nullable=False)
reported_at: Mapped[str] = mapped_column(String, nullable=False)