Media row reads room ids from @rooms.file_zones #153

Merged
void merged 1 commit from void/stewardships-rooms-key into dev 2026-11-13 23:25:21 +00:00
3 changed files with 36 additions and 9 deletions

View file

@ -97,9 +97,11 @@ Probes run by Imagination on 2026-10-31, session `self-2026-10-29-eureka`.
events from the Minds' stewardship documents; each project room's alias is
`#<repo lowercased>` (runbook 146-150). This is the only place the client
can learn a not-yet-joined room's steward today: hierarchy `canonical_alias`
joined to the root `org.gamecult.stewardships`. Follow-up
`ratatoskr:follow_up:stewardships-carry-room-ids` asks the projection to
name room ids so the client stops leaning on the alias convention.
joined to the root `org.gamecult.stewardships`. The
projection now also names room ids under `@rooms.projects` and
`@rooms.file_zones` (gamecult-ops #111); the client reads `file_zones` for
the Media row (`mediaRoom.ts`) and still joins projects by alias, since
nothing yet consumes `@rooms.projects`.
5. **Sync and storage** (`src/client/initMatrix.ts`). The client uses
matrix-js-sdk 41.7.0 with `IndexedDBStore` (`web-sync-store`, 15-19) and the
Rust crypto store, and `startClient({ lazyLoadMembers: true })` (43-47): v2

View file

@ -45,7 +45,7 @@ describe('the Media row of a Mind', () => {
throw new Error('must not be asked');
});
const carried = mediaRoomIdFromStewardships(
{ void: ['GameCult/x'], media: { void: '!c:gamecult.org' } },
{ void: ['GameCult/x'], '@rooms': { file_zones: { void: '!c:gamecult.org' } } },
'void'
);
expect(carried).toBe('!c:gamecult.org');
@ -58,7 +58,23 @@ describe('the Media row of a Mind', () => {
it('ignores a stewardships state that carries no id for the Mind, or something that is not a room id', () => {
expect(mediaRoomIdFromStewardships({ void: ['GameCult/x'] }, 'void')).toBeUndefined();
expect(mediaRoomIdFromStewardships({ media: { void: 'nope' } }, 'void')).toBeUndefined();
expect(
mediaRoomIdFromStewardships({ '@rooms': { file_zones: { void: 'nope' } } }, 'void')
).toBeUndefined();
// the project ids beside it, another Mind's id, and the old provisional key carry nothing for this Mind
expect(
mediaRoomIdFromStewardships(
{
'@rooms': { projects: { x: '!p:s' }, file_zones: { kiko: '!k:s' } },
media: { void: '!m:s' },
},
'void'
)
).toBeUndefined();
// a Mind named "constructor" must not read an inherited property as an id
expect(
mediaRoomIdFromStewardships({ '@rooms': { file_zones: {} } }, 'constructor')
).toBeUndefined();
expect(mediaRoomIdFromStewardships(undefined, 'void')).toBeUndefined();
});

View file

@ -2,20 +2,29 @@
* A Mind's media room: the file-zone room Eureka provisions under the alias #<mind>-files:<server>.
* The navigator shows a Media row for every Mind; this module decides what that row says and where it points.
* A room id carried by the root Space's org.gamecult.stewardships state wins over the alias; the alias is the
* convention until the provisioning projection carries ids (ratatoskr:follow_up:stewardships-carry-room-ids).
* fallback for a Mind whose zone the provisioning run has not recorded yet.
*/
import { EstateRoom } from './buildEstate';
export const STEWARDSHIPS_KEY = 'org.gamecult.stewardships';
/** Where provisioning records room ids in the stewardships state (gamecult-ops synapse-accounts-hand-managed.py ROOMS_KEY). "@" is no legal Matrix localpart, so no Mind can own the key. */
export const ROOMS_KEY = '@rooms';
export const mediaAlias = (mind: string, server: string): string => `#${mind}-files:${server}`;
/**
* The room id the stewardships state names for a Mind's media room, if it does. The state's mind-to-repos
* lists are not read here; the id lives under `media[<mind>]` (provisional shape, awaiting the follow-up above).
* The room id the stewardships state names for a Mind's media room, under `@rooms.file_zones[<mind>]`.
* The state's mind-to-repos lists are not read here.
*/
export const mediaRoomIdFromStewardships = (content: unknown, mind: string): string | undefined => {
const id = (content as { media?: Record<string, unknown> } | null | undefined)?.media?.[mind];
const rooms = (
content as
| Record<string, { file_zones?: Record<string, unknown> } | undefined>
| null
| undefined
)?.[ROOMS_KEY];
const id = rooms?.file_zones?.[mind];
return typeof id === 'string' && id.startsWith('!') ? id : undefined;
};