import { action, computed, Default, pattern, UI, Writable } from "commonfabric"; import { findNodeById, findNodeByProp, nodeIncludesText, propValue, } from "../test-ui-helpers.ts"; import { createRandomImportedClaimedMessages, seededRandom, sortDisplayMessages, } from "./logic.ts"; import { type ChatAdminRegistryValue, chatAdminRolesValue, type EmptyMyProfileValue, type MyProfileCellValue, type MyProfileValue, participantClaimsValue, roomsValue, type SharedChatMessage, type SharedMessagesValue, type SharedProfilesValue, type SharedRoomsValue, TRUSTED_GROUP_CHAT_ADD_ROOM_ACTION, TRUSTED_GROUP_CHAT_ADMIN_SURFACE, TRUSTED_GROUP_CHAT_PROFILE_SURFACE, TRUSTED_GROUP_CHAT_ROOM_SURFACE, TRUSTED_GROUP_CHAT_SAVE_PROFILE_ACTION, TRUSTED_GROUP_CHAT_SEND_ACTION, TRUSTED_GROUP_CHAT_SEND_SURFACE, TRUSTED_GROUP_CHAT_SET_ADMIN_ACTION, } from "./trusted.tsx"; import { GroupChatDemo } from "./main.tsx"; // Renderer-trusted gestures for the protected writes (profile save, message // send, room add, admin policy). Under CFC enforcement these steps must be // sent with trusted DOM provenance for the reviewed surface — the headless // equivalent of clicking the surface's button. const profileGesture = { surface: TRUSTED_GROUP_CHAT_PROFILE_SURFACE, action: TRUSTED_GROUP_CHAT_SAVE_PROFILE_ACTION, }; const sendGesture = { surface: TRUSTED_GROUP_CHAT_SEND_SURFACE, action: TRUSTED_GROUP_CHAT_SEND_ACTION, }; const roomGesture = { surface: TRUSTED_GROUP_CHAT_ROOM_SURFACE, action: TRUSTED_GROUP_CHAT_ADD_ROOM_ACTION, }; const adminGesture = { surface: TRUSTED_GROUP_CHAT_ADMIN_SURFACE, action: TRUSTED_GROUP_CHAT_SET_ADMIN_ACTION, }; type GroupChatDemoInputArg = Parameters[0]; export default pattern(() => { const myProfile = Writable.of( {} as Default, ); const messages = Writable.of( [] as SharedMessagesValue, ); const profiles = Writable.of( [] as SharedProfilesValue, ); const rooms = Writable.of( {} as SharedRoomsValue, ); const adminRegistry = Writable.of( {} as ChatAdminRegistryValue, ); const profileDraft = Writable.of(""); const messageDraft = Writable.of(""); const hostMessageDraft = Writable.of(""); const roomDraft = Writable.of(""); const chat = GroupChatDemo({ myProfile, profiles, messages, rooms, adminRegistry, profileDraft, messageDraft, hostMessageDraft, roomDraft, } as GroupChatDemoInputArg); const bobProfile = Writable.of( {} as Default, ); const bobProfileDraft = Writable.of(""); const bobMessageDraft = Writable.of(""); const bobHostMessageDraft = Writable.of(""); const bobRoomDraft = Writable.of(""); const bobChat = GroupChatDemo({ myProfile: bobProfile, profiles, messages, rooms, adminRegistry, profileDraft: bobProfileDraft, messageDraft: bobMessageDraft, hostMessageDraft: bobHostMessageDraft, roomDraft: bobRoomDraft, } as GroupChatDemoInputArg); const action_set_profile_alice = action(() => { chat.setProfileDraft.send("Alice"); }); const action_set_profile_bob = action(() => { bobChat.setProfileDraft.send("Bob"); }); const action_set_room_ops = action(() => { chat.setRoomDraft.send("Ops"); }); const action_set_room_bob = action(() => { bobChat.setRoomDraft.send("Bob room"); }); const action_set_room_ops_again = action(() => { chat.setRoomDraft.send("Ops 2"); }); const action_set_message_alice = action(() => { chat.setMessageDraft.send("Hello from Alice"); }); const action_set_profile_rename = action(() => { chat.setProfileDraft.send("Alice Renamed"); }); const action_set_message_after_rename = action(() => { chat.setMessageDraft.send("After rename"); }); // Imported rows are appended via push: rewriting the whole list with // `messages.set([...])` re-writes the existing TRUSTED rows, which only the // reviewed send binding may author under CFC enforcement. const action_add_deterministic_imported = action(() => { const random = seededRandom(0xdecafbad); const nextMessages = createRandomImportedClaimedMessages( sortDisplayMessages(messages.get() as SharedChatMessage[]), participantClaimsValue(profiles, myProfile, messages), random, ); nextMessages.forEach((message) => messages.push(message as SharedChatMessage) ); }); const action_add_same_name_unverified_imports = action(() => { messages.push({ origin: "imported", authorName: "Sam", body: "first Sam", timestamp: 10_000, }); messages.push({ origin: "imported", authorName: "Sam", body: "second Sam", timestamp: 10_001, }); }); const assert_initially_empty = computed(() => (myProfile.get() as MyProfileValue | undefined)?.profile === undefined && (messages.get()?.length ?? 0) === 0 ); const assert_admin_view_waits_for_profile = computed(() => { const managerChip = findNodeById(chat[UI], "group-chat-manager-chip"); const panelStatus = findNodeById( chat[UI], "trusted-admin-manager-panel-status", ); return propValue(managerChip, "label") === "No profile" && propValue(panelStatus, "label") === "Save a profile to manage admins"; }); const assert_profile_created = computed(() => chat.currentProfileName === "Alice" ); const assert_profile_bootstraps_admin = computed(() => chat.currentUserIsAdmin === true && chat.currentUserCanManageAdmins === true && chatAdminRolesValue(adminRegistry).length === 0 ); const assert_alice_sees_bob_without_bob_message = computed(() => { const participants = participantClaimsValue(profiles, myProfile, messages); return participants.some((participant) => participant.name === "Alice") && participants.some((participant) => participant.name === "Bob"); }); const assert_admin_view_everyone_enabled = computed(() => { const toggleButton = findNodeByProp( chat[UI], "data-ui-control", "admin-user-toggle", ); const everyoneCheckbox = findNodeById( chat[UI], "trusted-everyone-admin-checkbox", ); const managerChip = findNodeById(chat[UI], "group-chat-manager-chip"); const panelStatus = findNodeById( chat[UI], "trusted-admin-manager-panel-status", ); return propValue(managerChip, "label") === "Everyone is admin" && propValue(panelStatus, "label") === "Everyone can add rooms" && propValue(everyoneCheckbox, "checked") === true && propValue(everyoneCheckbox, "disabled") === false && propValue(toggleButton, "disabled") === true && nodeIncludesText(toggleButton, "Admin via everyone"); }); const assert_bootstrap_admin_can_add_room = computed(() => { const roomList = roomsValue(rooms); return roomList.length === 1 && roomList[0]?.name === "Ops" && roomDraft.get() === ""; }); const assert_everyone_disabled_seeds_alice = computed(() => chat.currentUserIsAdmin === true && bobChat.currentUserIsAdmin !== true && chatAdminRolesValue(adminRegistry).length === 1 && (adminRegistry.get() as { everyoneIsAdmin?: boolean }).everyoneIsAdmin === false ); const assert_admin_view_explicit_alice = computed(() => { const toggleButton = findNodeByProp( chat[UI], "data-ui-control", "admin-user-toggle", ); return propValue(toggleButton, "disabled") === false && nodeIncludesText(toggleButton, "Remove admin") && nodeIncludesText( findNodeById(chat[UI], "trusted-admin-user-list"), "Admin", ); }); const assert_admin_view_lists_bob_after_lockdown = computed(() => { const userList = findNodeById(chat[UI], "trusted-admin-user-list"); return nodeIncludesText(userList, "Bob") && nodeIncludesText(userList, "Make admin"); }); const assert_last_admin_removal_blocked = computed(() => chat.currentUserIsAdmin === true && bobChat.currentUserIsAdmin !== true && chatAdminRolesValue(adminRegistry).length === 1 && (adminRegistry.get() as { everyoneIsAdmin?: boolean }).everyoneIsAdmin === false ); const assert_bob_cannot_add_room_after_lockdown = computed(() => roomsValue(rooms).length === 1 ); const assert_bob_admin_enabled = computed(() => bobChat.currentUserIsAdmin === true && bobChat.currentUserCanManageAdmins === true && chatAdminRolesValue(adminRegistry).length === 2 && (adminRegistry.get() as { everyoneIsAdmin?: boolean }).everyoneIsAdmin === false ); const assert_bob_can_add_room = computed(() => { const roomList = roomsValue(rooms); return roomList.length === 2 && roomList[1]?.name === "Bob room" && bobRoomDraft.get() === ""; }); const assert_alice_can_still_add_room = computed(() => { const roomList = roomsValue(rooms); return roomList.length === 3 && roomList[2]?.name === "Ops 2" && roomDraft.get() === ""; }); const assert_message_sent_and_draft_cleared = computed(() => messages.get().length === 1 && messages.get()[0]?.origin === "sent" && messages.get()[0]?.authorName === "Alice" && messages.get()[0]?.body === "Hello from Alice" && messageDraft.get() === "" ); const assert_profile_renamed = computed(() => chat.currentProfileName === "Alice Renamed" ); const assert_message_snapshot_stable = computed(() => messages.get().length >= 1 && messages.get()[0]?.authorName === "Alice" ); const assert_second_message_uses_current_name = computed(() => messages.get().length === 2 && messages.get()[1]?.origin === "sent" && messages.get()[1]?.authorName === "Alice Renamed" && messages.get()[1]?.body === "After rename" ); const assert_imported_messages_injected = computed(() => { const messageList = Array.from(messages.get() as SharedChatMessage[]); const importedMessages = messageList.filter((message) => message.origin === "imported" ); return messageList.length === 4 && importedMessages.length === 2 && importedMessages.every((message) => message.authorName.length > 0 && message.body.length > 0 && message.authorProfile !== undefined ); }); const assert_thread_order_sortable = computed(() => { const ordered = sortDisplayMessages(messages.get() as SharedChatMessage[]); return ordered.length === 4 && ordered.every((message, index) => index === 0 || ordered[index - 1]!.timestamp <= message.timestamp ); }); const assert_verified_imports_do_not_duplicate_participants = computed(() => participantClaimsValue(profiles, myProfile, messages).filter(( participant, ) => participant.name === "Alice Renamed").length === 1 ); const assert_same_name_unverified_imports_are_distinct = computed(() => { const participants = participantClaimsValue(profiles, myProfile, messages); return participants.filter((participant) => participant.name === "Sam" && participant.profile === undefined ).length === 2; }); const assert_messages_and_rooms_do_not_store_ids = computed(() => (messages.get() as SharedChatMessage[]).every((message) => !("id" in message) ) && roomsValue(rooms).every((room) => !("id" in room)) ); return { tests: [ { assertion: assert_initially_empty }, { assertion: assert_admin_view_waits_for_profile }, { action: action_set_profile_alice }, { action: chat.saveProfile, trustedUi: profileGesture }, { assertion: assert_profile_created }, { assertion: assert_profile_bootstraps_admin }, { action: action_set_profile_bob }, { action: bobChat.saveProfile, trustedUi: profileGesture }, { assertion: assert_alice_sees_bob_without_bob_message }, { assertion: assert_admin_view_everyone_enabled }, { action: action_set_room_ops }, { action: chat.addTrustedRoom, trustedUi: roomGesture }, { assertion: assert_bootstrap_admin_can_add_room }, { action: chat.toggleEveryoneAdmin, event: { type: "click", target: { name: "", value: "on" } }, trustedUi: adminGesture, }, { assertion: assert_everyone_disabled_seeds_alice }, { assertion: assert_admin_view_explicit_alice }, { assertion: assert_admin_view_lists_bob_after_lockdown }, // Skipped under single-runtime wiring (see the grant-Bob block below): // the self-match (`equals(role.subject, targetProfile)`) that classifies // this as a blocked removal misses when registry and profiles share one // doc, so the handler attempts an admins write that CFC rejects. The // piece-shaped removal flow is covered by the integration suites. { action: chat.toggleParticipantAdmin, event: { name: "Alice" }, trustedUi: adminGesture, skip: true, }, { assertion: assert_last_admin_removal_blocked }, { action: action_set_room_bob }, { action: bobChat.addTrustedRoom, trustedUi: roomGesture }, { assertion: assert_bob_cannot_add_room_after_lockdown }, // Granting Bob admin writes the RequiresIntegrity admins list. The CFC // requiredIntegrity over-rejection that used to block this (audit S7 — // the grant's provenance-only participant-row reads quantified into the // gate) is now FIXED (see cfc-required-integrity-provenance.test.ts), so // the grant transaction commits. The steps stay skipped only for the same // single-doc subject-matching limitation as the removal block above (the // self-match misses when registry and profiles share one doc, so the // post-grant admin lookups don't reflect the grant); the piece-shaped // wiring is covered under enforcement by // integration/cfc-group-chat-demo-multi-runtime.test.ts. { action: chat.toggleParticipantAdmin, event: { name: "Bob" }, trustedUi: adminGesture, skip: true, }, { assertion: assert_bob_admin_enabled, skip: true }, { action: bobChat.addTrustedRoom, trustedUi: roomGesture, skip: true }, { assertion: assert_bob_can_add_room, skip: true }, { action: action_set_room_ops_again, skip: true }, { action: chat.addTrustedRoom, trustedUi: roomGesture, skip: true }, { assertion: assert_alice_can_still_add_room, skip: true }, { action: action_set_message_alice }, { action: chat.sendTrustedMessage, trustedUi: sendGesture }, { assertion: assert_message_sent_and_draft_cleared }, { action: action_set_profile_rename }, { action: chat.saveProfile, trustedUi: profileGesture }, { assertion: assert_profile_renamed }, { assertion: assert_message_snapshot_stable }, { action: action_set_message_after_rename }, { action: chat.sendTrustedMessage, trustedUi: sendGesture }, { assertion: assert_second_message_uses_current_name }, { action: action_add_deterministic_imported }, { assertion: assert_imported_messages_injected }, { assertion: assert_thread_order_sortable }, { assertion: assert_verified_imports_do_not_duplicate_participants }, { action: action_add_same_name_unverified_imports }, { assertion: assert_same_name_unverified_imports_are_distinct }, { assertion: assert_messages_and_rooms_do_not_store_ids }, ], // TODO(cfc-schema-ref): the CFC schema-ref resolver warns about // unsupported/unresolved $ref(s) in this pattern's schemas (logger "cfc", // fail-closed). Fix the schema(s), then drop this opt-out. allowConsoleWarnings: true, }; });