Files
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

135 lines
5.7 KiB
Python

"""Case study: Forum / community.
Architectural pattern demonstrated: **public-read + owner-edit + moderator override**.
Posts are publicly readable but only the author can edit them; moderators can
delete or lock any post regardless of authorship. Comments are owned by their
author. This pattern shows multiple-rule permission composition --- a single
type carries an owner rule, a public rule, and a moderator-override rule.
Object types: forum_post, comment, moderation_log
Key invariants:
- posts are publicly readable
- only the post author can edit content
- moderators can lock or delete any post
- locked posts cannot be edited (even by author)
- comments inherit the lock from their parent post
"""
from __future__ import annotations
from pedo.core.models import (
AccessContext, DataObject, ObjectType, Operation,
PermissionRule, PrivilegeType, ReactionDeclaration,
Relationship, RelationshipAction,
)
from pedo.core.store import ObjectStore
def validate_post_not_locked(proposed, existing, accessor, store):
"""Locked posts cannot be edited (even by the author).
Moderators can still lock/unlock via the 'locked' field itself."""
if existing is None:
return True
if existing.content.get("locked"):
# Allow only the moderator unlock action: the only legal change is
# 'locked' going from True to False or remaining True.
for field, new_val in proposed.content.items():
if field == "locked":
continue
if existing.content.get(field) != new_val:
if accessor.role != "moderator":
return f"post is locked; field {field!r} cannot be edited"
return True
def validate_comment_parent_not_locked(proposed, existing, accessor, store):
"""Comments cannot be added to or edited on a locked post."""
post_id = proposed.content.get("post_id")
if not post_id:
return True
post = store.raw_read(post_id)
if post is None:
return f"parent post {post_id} not found"
if post.content.get("locked") and accessor.role != "moderator":
return "parent post is locked; comments cannot be added"
return True
def log_moderation(event, store):
"""Reaction: log every moderator-driven change (lock, delete) to a
moderation_log so the community can audit moderator actions."""
sys_ctx = AccessContext(user_id="system", role="system", org_id=event["object_org"])
log = DataObject(
type_name="moderation_log",
content={
"action": event["event"],
"object_id": event["object_id"],
"object_type": event["object_type"],
"fields": event.get("changed_fields", []),
"timestamp": event["timestamp"],
},
owner_id="system",
org_id=event["object_org"],
)
store.create(log, sys_ctx, _reaction_depth=event["depth"])
def register_forum_types(store: ObjectStore) -> None:
store.register_reaction_handler("log_moderation", log_moderation)
store.register_type(ObjectType(
name="forum_post",
fields={"title": "str", "body": "str", "locked": "bool"},
permission_rules=[
# Public read.
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {}),
PermissionRule(Operation.ACCEPT, PrivilegeType.SELECT, {}),
# Any authenticated user can post.
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {}),
# Author can edit.
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"is_owner": True}),
# Moderators have override on edit + delete.
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"role": "moderator"}),
PermissionRule(Operation.ACCEPT, PrivilegeType.DELETE, {"role": "moderator"}),
# Author cannot delete (community-history preservation).
],
validators=[validate_post_not_locked],
reactions=[
# Note: moderation log fires on every post update; in practice,
# one would filter on changed_fields for moderator-only events.
ReactionDeclaration(event="after_update:locked", handler="log_moderation"),
ReactionDeclaration(event="after_delete", handler="log_moderation"),
],
default_policy=Operation.DENY,
))
store.register_type(ObjectType(
name="comment",
fields={"post_id": "str", "body": "str"},
permission_rules=[
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {}),
PermissionRule(Operation.ACCEPT, PrivilegeType.SELECT, {}),
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {}),
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"is_owner": True}),
PermissionRule(Operation.ACCEPT, PrivilegeType.DELETE, {"is_owner": True}),
PermissionRule(Operation.ACCEPT, PrivilegeType.DELETE, {"role": "moderator"}),
],
validators=[validate_comment_parent_not_locked],
relationships=[
Relationship(name="post", target_type="forum_post",
on_delete=RelationshipAction.CASCADE),
],
default_policy=Operation.DENY,
))
store.register_type(ObjectType(
name="moderation_log",
fields={"action": "str", "object_id": "str", "object_type": "str",
"fields": "list", "timestamp": "float"},
permission_rules=[
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {"role": "system"}),
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {}), # public audit
PermissionRule(Operation.ACCEPT, PrivilegeType.SELECT, {}),
],
default_policy=Operation.DENY,
))