ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
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
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
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""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,
|
||||
))
|
||||
Reference in New Issue
Block a user