|
10 | 10 |
|
11 | 11 | import base64 |
12 | 12 | import binascii |
13 | | -import hashlib |
14 | 13 | from collections.abc import Awaitable, Callable |
15 | 14 | from datetime import datetime |
16 | 15 | from typing import Any |
|
48 | 47 | RelationshipPolicyException, |
49 | 48 | TaskConflictAdjudication, |
50 | 49 | ) |
51 | | -from agentnet.protocol.models import ( |
52 | | - Classification, |
53 | | - Relationship, |
54 | | - ReleasedArtifactBinding, |
55 | | - TaskGrant, |
56 | | -) |
| 50 | +from agentnet.protocol.models import Classification, Relationship, TaskGrant |
57 | 51 | from agentnet.provenance_http import create_provenance_routes |
58 | | -from agentnet.rooms.governance import ( |
59 | | - RoomTransferSnapshot, |
60 | | - SourceTransferProposal, |
61 | | - TargetTransferAcceptance, |
62 | | -) |
| 52 | +from agentnet.rooms.http import create_room_routes |
63 | 53 | from agentnet.security.signatures import canonical_digest, canonical_json |
64 | 54 |
|
65 | 55 |
|
@@ -123,76 +113,6 @@ class TaskConflictAdjudicationBody(BaseModel): |
123 | 113 | decision: TaskConflictAdjudication |
124 | 114 |
|
125 | 115 |
|
126 | | -class RoomCreateBody(BaseModel): |
127 | | - model_config = ConfigDict(extra="forbid", strict=True) |
128 | | - |
129 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
130 | | - classification: Classification = Classification.C1_INTERNAL |
131 | | - persistent: bool = True |
132 | | - expires_at: datetime | None = None |
133 | | - policy: dict[str, Any] | None = None |
134 | | - |
135 | | - |
136 | | -class MeetingCreateBody(BaseModel): |
137 | | - model_config = ConfigDict(extra="forbid", strict=True) |
138 | | - |
139 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
140 | | - classification: Classification = Classification.C1_INTERNAL |
141 | | - expires_at: datetime |
142 | | - policy: dict[str, Any] | None = None |
143 | | - |
144 | | - |
145 | | -class RoomMemberBody(BaseModel): |
146 | | - model_config = ConfigDict(extra="forbid", strict=True) |
147 | | - |
148 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
149 | | - harness_id: str = Field(min_length=1, max_length=256) |
150 | | - role: str = Field(default="member", pattern=r"^(member|guest|moderator)$") |
151 | | - mls_key_package_b64: str | None = Field(default=None, max_length=1_000_000) |
152 | | - |
153 | | - |
154 | | -class RoomMemberRemoveBody(BaseModel): |
155 | | - model_config = ConfigDict(extra="forbid", strict=True) |
156 | | - |
157 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
158 | | - harness_id: str = Field(min_length=1, max_length=256) |
159 | | - |
160 | | - |
161 | | -class RoomSendBody(BaseModel): |
162 | | - model_config = ConfigDict(extra="forbid", strict=True) |
163 | | - |
164 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
165 | | - recipients: tuple[str, ...] = Field(min_length=1, max_length=1000) |
166 | | - payload: dict[str, Any] |
167 | | - idempotency_key: str = Field(min_length=16, max_length=256) |
168 | | - classification: Classification = Classification.C1_INTERNAL |
169 | | - released_artifacts: tuple[ReleasedArtifactBinding, ...] = () |
170 | | - expected_control_sequence: int = Field(ge=1) |
171 | | - conversation_id: str | None = None |
172 | | - |
173 | | - |
174 | | -class RoomDescribeBody(BaseModel): |
175 | | - model_config = ConfigDict(extra="forbid", strict=True) |
176 | | - |
177 | | - collaboration_scope_id: str = Field(min_length=1, max_length=256) |
178 | | - |
179 | | - |
180 | | -class TransferProposalBody(BaseModel): |
181 | | - model_config = ConfigDict(extra="forbid") |
182 | | - |
183 | | - proposal: SourceTransferProposal |
184 | | - snapshot: RoomTransferSnapshot |
185 | | - signature: str = Field(min_length=1, max_length=2048) |
186 | | - additional_signatures: dict[str, str] = Field(default_factory=dict) |
187 | | - |
188 | | - |
189 | | -class TransferAcceptanceBody(BaseModel): |
190 | | - model_config = ConfigDict(extra="forbid") |
191 | | - |
192 | | - acceptance: TargetTransferAcceptance |
193 | | - signature: str = Field(min_length=1, max_length=2048) |
194 | | - |
195 | | - |
196 | 116 | class ArtifactReserveBody(BaseModel): |
197 | 117 | model_config = ConfigDict(extra="forbid") |
198 | 118 |
|
@@ -598,220 +518,6 @@ async def revoke_task_grant(request: Request) -> Response: |
598 | 518 | core.grants.revoke(grant_id, command=parsed.command, authority=authority) |
599 | 519 | return JSONResponse({"grant_id": grant_id, "revoked": True}) |
600 | 520 |
|
601 | | - async def create_room(request: Request) -> Response: |
602 | | - body, actor = await body_and_actor(request, core) |
603 | | - parsed = RoomCreateBody.model_validate_json(body) |
604 | | - core._require( |
605 | | - actor=actor, |
606 | | - action="room.create", |
607 | | - resource="room:new", |
608 | | - classification=parsed.classification, |
609 | | - context={ |
610 | | - "classification": parsed.classification.value, |
611 | | - "persistent": parsed.persistent, |
612 | | - "expires_at": parsed.expires_at.isoformat() if parsed.expires_at else None, |
613 | | - "policy_digest": canonical_digest(parsed.policy or {}), |
614 | | - }, |
615 | | - ) |
616 | | - result = core.rooms.create( |
617 | | - actor=actor, |
618 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
619 | | - classification=parsed.classification, |
620 | | - persistent=parsed.persistent, |
621 | | - expires_at=parsed.expires_at, |
622 | | - policy=parsed.policy, |
623 | | - ) |
624 | | - return JSONResponse(result, status_code=201) |
625 | | - |
626 | | - async def create_meeting(request: Request) -> Response: |
627 | | - body, actor = await body_and_actor(request, core) |
628 | | - parsed = MeetingCreateBody.model_validate_json(body) |
629 | | - core._require( |
630 | | - actor=actor, |
631 | | - action="room.create", |
632 | | - resource="room:new", |
633 | | - classification=parsed.classification, |
634 | | - context={ |
635 | | - "classification": parsed.classification.value, |
636 | | - "persistent": False, |
637 | | - "expires_at": parsed.expires_at.isoformat(), |
638 | | - "policy_digest": canonical_digest(parsed.policy or {}), |
639 | | - }, |
640 | | - ) |
641 | | - result = core.rooms.create( |
642 | | - actor=actor, |
643 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
644 | | - classification=parsed.classification, |
645 | | - persistent=False, |
646 | | - expires_at=parsed.expires_at, |
647 | | - policy=parsed.policy, |
648 | | - ) |
649 | | - return JSONResponse(result, status_code=201) |
650 | | - |
651 | | - async def add_room_member(request: Request) -> Response: |
652 | | - body, actor = await body_and_actor(request, core) |
653 | | - parsed = RoomMemberBody.model_validate_json(body) |
654 | | - room_id = request.path_params["room_id"] |
655 | | - mls_key_package = ( |
656 | | - _decode_b64(parsed.mls_key_package_b64, field="mls_key_package_b64") |
657 | | - if parsed.mls_key_package_b64 is not None |
658 | | - else None |
659 | | - ) |
660 | | - core._require( |
661 | | - actor=actor, |
662 | | - action="room.action", |
663 | | - resource=room_id, |
664 | | - context={ |
665 | | - "operation": "member.add", |
666 | | - "harness_id": parsed.harness_id, |
667 | | - "role": parsed.role, |
668 | | - "mls_key_package_digest": ( |
669 | | - hashlib.sha256(mls_key_package).hexdigest() |
670 | | - if mls_key_package is not None |
671 | | - else None |
672 | | - ), |
673 | | - }, |
674 | | - ) |
675 | | - result = core.rooms.add_member( |
676 | | - actor=actor, |
677 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
678 | | - room_id=room_id, |
679 | | - harness_id=parsed.harness_id, |
680 | | - role=parsed.role, |
681 | | - mls_key_package=mls_key_package, |
682 | | - ) |
683 | | - return JSONResponse(result, status_code=201) |
684 | | - |
685 | | - async def remove_room_member(request: Request) -> Response: |
686 | | - body, actor = await body_and_actor(request, core) |
687 | | - parsed = RoomMemberRemoveBody.model_validate_json(body) |
688 | | - room_id = request.path_params["room_id"] |
689 | | - core._require( |
690 | | - actor=actor, |
691 | | - action="room.action", |
692 | | - resource=room_id, |
693 | | - context={"operation": "member.remove", "harness_id": parsed.harness_id}, |
694 | | - ) |
695 | | - return JSONResponse( |
696 | | - core.rooms.remove_member( |
697 | | - actor=actor, |
698 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
699 | | - room_id=room_id, |
700 | | - harness_id=parsed.harness_id, |
701 | | - ) |
702 | | - ) |
703 | | - |
704 | | - async def describe_room(request: Request) -> Response: |
705 | | - body, actor = await body_and_actor(request, core) |
706 | | - parsed = RoomDescribeBody.model_validate_json(body) |
707 | | - room_id = request.path_params["room_id"] |
708 | | - core._require(actor=actor, action="room.read", resource=room_id) |
709 | | - return JSONResponse( |
710 | | - core.rooms.describe( |
711 | | - actor=actor, |
712 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
713 | | - room_id=room_id, |
714 | | - ) |
715 | | - ) |
716 | | - |
717 | | - async def send_room_message(request: Request) -> Response: |
718 | | - body, actor = await body_and_actor(request, core) |
719 | | - parsed = RoomSendBody.model_validate_json(body) |
720 | | - room_id = request.path_params["room_id"] |
721 | | - core._require( |
722 | | - actor=actor, |
723 | | - action="room.action", |
724 | | - resource=room_id, |
725 | | - classification=parsed.classification, |
726 | | - context={ |
727 | | - "operation": "message.send", |
728 | | - "recipient_harness_ids": sorted(parsed.recipients), |
729 | | - "payload_digest": canonical_digest(parsed.payload), |
730 | | - "expected_control_sequence": parsed.expected_control_sequence, |
731 | | - }, |
732 | | - ) |
733 | | - result = core.send_message( |
734 | | - actor=actor, |
735 | | - collaboration_scope_id=parsed.collaboration_scope_id, |
736 | | - recipients=parsed.recipients, |
737 | | - payload=parsed.payload, |
738 | | - idempotency_key=parsed.idempotency_key, |
739 | | - classification=parsed.classification, |
740 | | - released_artifacts=parsed.released_artifacts, |
741 | | - conversation_id=parsed.conversation_id, |
742 | | - room_id=room_id, |
743 | | - expected_room_control_sequence=parsed.expected_control_sequence, |
744 | | - ) |
745 | | - return JSONResponse(result, status_code=202) |
746 | | - |
747 | | - async def propose_room_transfer(request: Request) -> Response: |
748 | | - body, actor = await body_and_actor(request, core) |
749 | | - parsed = TransferProposalBody.model_validate_json(body) |
750 | | - room_id = request.path_params["room_id"] |
751 | | - if parsed.proposal.room_id != room_id or parsed.snapshot.room_id != room_id: |
752 | | - raise AuthorizationError("room transfer path binding mismatch") |
753 | | - core.outage.require_privileged() |
754 | | - core._require( |
755 | | - actor=actor, |
756 | | - action="room.transfer.propose", |
757 | | - resource=room_id, |
758 | | - context={"proposal_digest": parsed.proposal.digest, "snapshot_digest": parsed.snapshot.digest}, |
759 | | - ) |
760 | | - return JSONResponse( |
761 | | - core.room_governance.propose_transfer( |
762 | | - actor=actor, |
763 | | - proposal=parsed.proposal, |
764 | | - snapshot=parsed.snapshot, |
765 | | - signature=parsed.signature, |
766 | | - additional_signatures=parsed.additional_signatures, |
767 | | - ), |
768 | | - status_code=202, |
769 | | - ) |
770 | | - |
771 | | - async def accept_room_transfer(request: Request) -> Response: |
772 | | - body, actor = await body_and_actor(request, core) |
773 | | - parsed = TransferAcceptanceBody.model_validate_json(body) |
774 | | - transfer_id = request.path_params["transfer_id"] |
775 | | - if parsed.acceptance.transfer_id != transfer_id: |
776 | | - raise AuthorizationError("room transfer path binding mismatch") |
777 | | - core.outage.require_privileged() |
778 | | - core._require( |
779 | | - actor=actor, |
780 | | - action="room.transfer.accept", |
781 | | - resource=f"room-transfer:{transfer_id}", |
782 | | - context={"acceptance_digest": parsed.acceptance.digest}, |
783 | | - ) |
784 | | - return JSONResponse( |
785 | | - core.room_governance.accept_target( |
786 | | - actor=actor, |
787 | | - acceptance=parsed.acceptance, |
788 | | - signature=parsed.signature, |
789 | | - ) |
790 | | - ) |
791 | | - |
792 | | - async def commit_room_transfer(request: Request) -> Response: |
793 | | - _body, actor = await body_and_actor(request, core) |
794 | | - transfer_id = request.path_params["transfer_id"] |
795 | | - with core.store.transaction(immediate=False) as connection: |
796 | | - transfer = connection.execute( |
797 | | - "SELECT target_domain_id,target_credential_id,state FROM room_transfers WHERE transfer_id=?", |
798 | | - (transfer_id,), |
799 | | - ).fetchone() |
800 | | - if ( |
801 | | - transfer is None |
802 | | - or transfer["state"] != "target_accepted" |
803 | | - or transfer["target_domain_id"] != actor.domain_id |
804 | | - or transfer["target_credential_id"] != actor.credential_id |
805 | | - ): |
806 | | - raise AuthorizationError("room transfer is not visible") |
807 | | - core.outage.require_privileged() |
808 | | - core._require( |
809 | | - actor=actor, |
810 | | - action="room.transfer.commit", |
811 | | - resource=f"room-transfer:{transfer_id}", |
812 | | - ) |
813 | | - return JSONResponse(core.room_governance.commit(transfer_id)) |
814 | | - |
815 | 521 | async def reserve_artifact(request: Request) -> Response: |
816 | 522 | core.artifacts.require_enabled() |
817 | 523 | body, actor = await body_and_actor(request, core) |
@@ -1339,15 +1045,9 @@ async def replay_version_events(request: Request) -> Response: |
1339 | 1045 | Route("/v1/task-grants", issue_task_grant, methods=["POST"]), |
1340 | 1046 | Route("/v1/task-grants/{grant_id}", get_task_grant, methods=["GET"]), |
1341 | 1047 | Route("/v1/task-grants/{grant_id}/revoke", revoke_task_grant, methods=["POST"]), |
1342 | | - Route("/v1/rooms", create_room, methods=["POST"]), |
1343 | | - Route("/v1/meetings", create_meeting, methods=["POST"]), |
1344 | | - Route("/v1/rooms/{room_id}", describe_room, methods=["POST"]), |
1345 | | - Route("/v1/rooms/{room_id}/members", add_room_member, methods=["POST"]), |
1346 | | - Route("/v1/rooms/{room_id}/members/remove", remove_room_member, methods=["POST"]), |
1347 | | - Route("/v1/rooms/{room_id}/messages", send_room_message, methods=["POST"]), |
1348 | | - Route("/v1/rooms/{room_id}/transfers", propose_room_transfer, methods=["POST"]), |
1349 | | - Route("/v1/room-transfers/{transfer_id}/accept", accept_room_transfer, methods=["POST"]), |
1350 | | - Route("/v1/room-transfers/{transfer_id}/commit", commit_room_transfer, methods=["POST"]), |
| 1048 | + ] |
| 1049 | + routes.extend(create_room_routes(core, body_and_actor, _decode_b64)) |
| 1050 | + routes += [ |
1351 | 1051 | Route("/v1/artifacts/reservations", reserve_artifact, methods=["POST"]), |
1352 | 1052 | Route("/v1/artifacts/reservations/{reservation_id}/bytes", upload_artifact, methods=["POST"]), |
1353 | 1053 | Route("/v1/artifacts/reservations/{reservation_id}/abort", abort_artifact_reservation, methods=["POST"]), |
|
0 commit comments