Docmost 短期演示实例运行边界

运行入口：scripts/demo/gateway.mjs。Node 26.12.0，pnpm 11.25.0。
Docker 镜像使用 Dockerfile.demo，必须以带 source-export.json 的完整对应源码导出目录构建。
不能把普通工作树直接 docker build：.dockerignore 会去掉 .git，而严格构建要求提交或导出证明。
镜像保留构建源码、许可证、依赖、服务端和客户端产物及对应源码 receipt。
运行时 /app 只读；每代运行目录复制两套 dist，避免多个实例共写前端 index.html。

结构
网关和最多两个 Docmost 子 Node 进程共处一个容器，不挂 Docker socket。
子进程仅监听容器内 127.0.0.1:3101/3102，公开请求全部经过网关。
每槽有独立 PostgreSQL 和 Redis 容器；数据库和 Redis 不发布宿主端口。
数据库和Redis只加入各自internal网络；网关另加入edge网络以发布回环端口。
网关容器有外网连接能力，外发限制依赖API白名单及不提供SMTP等外发配置，不能宣称网络完全断网。
外部 TLS 代理只连接宿主 127.0.0.1:3009。
每槽新建工作区、成员、空间、文档，不复制原开发数据库或文件。

单槽默认资源上限
gateway（含子进程）：768 MiB，1.5 CPU，128 PIDs。
PostgreSQL：192 MiB，0.5 CPU，最多25连接，32 MiB shared_buffers。
Redis：48 MiB，0.25 CPU，24 MiB maxmemory，禁止淘汰；不持久化演示缓存。
这些是容器上限，不是运行内存的测量结果。第二槽需先测量宿主余量。
第二槽通过 Compose profile two-slots 开启，同时配置两个 slots，并按实测调整
DOCMOST_DEMO_APP_MEMORY；只开启profile不会自动扩展配置中的会话容量。

私有配置
DOCMOST_DEMO_CONFIG_FILE 指向网关JSON配置，由Compose作为 /run/secrets/demo-config.json 挂载。
配置目录0700、文件0600，不放进源码目录、Git或源码归档；容器node用户必须能读挂载文件。
配置字段：
  releaseRoot：容器内 /app，或本机已构建对应源码目录的绝对路径。
  stateDir：容器内 /var/lib/docmost-demo，或源码目录之外的专用绝对路径。
  publicOrigin：实际HTTPS来源；仅本机验证允许 http://localhost:3009 等回环来源。
  slots：一或两个对象，id仅slot1/slot2、port仅独立高位回环端口，databaseUrl、redisUrl。
容器数据库形如 postgresql://docmost_demo_slot1:<随机密码>@db1:5432/docmost_demo_slot1；
Redis形如 redis://redis1:6379/0。第二槽替换为slot2、db2、redis2。
本机验证时只能使用另建的回环高位端口。脚本拒绝原开发数据库名和25418/26418端口。
禁止用Redis不同DB编号替代独立Redis实例，协作Pub/Sub通道不按DB编号隔离。

Compose变量还需：
  DOCMOST_DEMO_IMAGE：本次源码构建的明确镜像标签。
  DOCMOST_DEMO_SLOT1_BOOTSTRAP_PASSWORD_FILE：独立PG初始化管理员密码文件。
  DOCMOST_DEMO_SLOT1_PASSWORD_FILE：至少64位随机hex的专用应用账号密码文件。
  第二槽使用对应 DOCMOST_DEMO_SLOT2_BOOTSTRAP_PASSWORD_FILE、DOCMOST_DEMO_SLOT2_PASSWORD_FILE。
初始化脚本用stdin传密码创建非superuser、不可建库/建角色的数据库owner。
运行容器只有应用账号凭据，没有bootstrap密码挂载。
所有命令显式指定 -p 本次项目名和 -f scripts/demo/compose.yaml；不运行全局prune。

生命周期
首次操作验证数据库名和非superuser账号，取得该专库的session advisory lock；
锁不等待、不在destroy时释放，一直持有至runtime.close，阻止另一网关在初始化空窗清库。
连接意外断开会将runtime关闭并终止该槽应用，避免失去所有权后继续写。
prepare在持有锁的前提下清理自己的public schema及Redis。
复制运行树后，临时以演示边界关闭的私网进程初始化：setup生成不可对外使用的owner，
三位访客都是workspace member；作者和评审者是space writer，只读成员是space reader。
使用现有交付文档API创建合成需求说明。owner登录session随后删除。
停止初始化进程，再使用本代独立APP_SECRET和DOCMOST_DEMO_SECRET开启演示边界。
prepare返回三位有限角色的内部身份和认证cookie、页面地址及签名secret，仅供网关使用。
不把密码、cookie或secret打印、持久化到公共材料或返回浏览器。

destroy先TERM整个已跟踪子进程组并等待，超时再KILL；确认结束后清自己的数据与目录。
无跟踪进程的启动恢复先检查槽位端口。若被占用，拒绝清库，不根据保存PID猜测或杀进程；
需停止原runner或重启该项目容器后再恢复。目录只回收 slotN-UUID 形状，遇symlink拒绝。
清理失败时槽位不可重新分配。活动会话不被定时全库重置影响。
应用stdout/stderr仅写stateDir下0600诊断文件，供管理员排错；其中可能有内部数据，不公开。
每槽只保留 slotN.current.log 与 slotN.previous.log，每份最多1 MiB。
新prepare轮转一次；初始化和演示两次子进程启动共同使用current文件的同一配额。
超限后继续读取并丢弃输出，防止子进程因管道堵塞而卡住，也不积累待写内存。
轮转拒绝符号链接、硬链接和特殊文件，仅清理同槽旧UUID日志，不删除其他槽或无关文件。
不提供真实邮件配置；不传MAIL_DRIVER时上游使用本机log驱动。

验证
node --test scripts/demo/runtime.test.mjs
实际专用PG/Redis已启动后，可指定私有配置执行数据库互斥测试：
DOCMOST_RUNTIME_TEST_CONFIG=/absolute/private/config.json node --test scripts/demo/runtime.test.mjs
该集成测试只清理配置中的docmost_demo_slot1专库；不能给它原业务配置。
完整验收还需实际网关双会话、HTTP/WebSocket越权、版本评审、过期回收、重启及对应源码下载。
Compose语法通过或容器healthy不能代替这些业务验收。
