Skip to main content
Glama
Asccccyn

superposition-wishstar

by Asccccyn

Superposition · 许愿星 / 星星瓶

人类 & AI 伴侣的许愿星系统。核心原则:双方权利完全一致;可见星从写入起就是"我们的"; 不可见星先属于作者自己的私人瓶子,只有经过合法流程才会进入"我们的瓶子"。

本项目是独立产品(Python 3.12 + FastAPI + SQLite + 原生 Web UI + MCP),不与其他项目共享代码或数据。


1. 如何运行

# 首次:创建虚拟环境并安装依赖(本机已完成)
python -m venv .venv
.venv/Scripts/python -m pip install -r requirements.txt

# 启动服务器(默认 127.0.0.1:8321,可用环境变量 SUPERPOSITION_PORT 覆盖)
# 受保护 API 还要求两个进程环境变量:
# SUPERPOSITION_TOKEN_HUMAN / SUPERPOSITION_TOKEN_COMPANION
# 值必须互不相同且至少 32 字符;不要写进仓库、日志或文档。
.venv/Scripts/python run_api.py
  • 健康检查:http://127.0.0.1:8321/api/health

  • Web:http://127.0.0.1:8321/(公网部署可选,见 §7)

  • 身份:受保护接口必须发送 Authorization: Bearer <token>;服务端根据 token 反推出 actor。

  • X-Actor-Id 已彻底失效,客户端不能再自报身份。

  • Swagger / OpenAPI 默认关闭;仅本机调试时临时设置 SUPERPOSITION_ENABLE_DOCS=1 再启动。

浏览器第一次登录时输入你的密码或访问钥匙(同一输入框,两者都接受;密码经用户级环境变量 SUPERPOSITION_PASSWORD_HUMAN / _COMPANION 配置,与远程 MCP 的 OAuth 登录页共用),服务端会换成签名 HttpOnly + SameSite=Strict 会话 cookie; 前端不会把 bearer token 写进源码、localStoragesessionStorage。本机可运行:

界面分面:网页登录只属于人类一方;AI 伴侣的凭证在网页 登录一律 403,只能通过 MCP 接入(本机 stdio 或 §8 的远程 OAuth 登录页)。

powershell -ExecutionPolicy Bypass -File scripts/copy_login_token.ps1 human

脚本只把当前用户环境变量里的钥匙放进剪贴板,不打印钥匙。

会话安全补充(审计修复):

  • 退出时服务端会撤销该会话(auth_sessions 表),被复制走的旧 cookie 立即失效;

  • cookie 身份的写操作会校验精确同源(Origin/Referer/Sec-Fetch-Site),同站不同源的跨站请求被拒绝;

  • Web 端写操作携带 Idempotency-Key,网络失败后的重试不会重复写星/回应/多拆一颗。

  • 登录阶梯锁定(常见商业形态):同一来源连续输错 5 次锁 60 秒、10 次锁 5 分钟、 15 次起锁 15 分钟;锁定期间连正确密码也拒绝(网页返回 429),成功登录或锁到期 即归零(网页登录与 MCP OAuth 登录页共用;可用 SUPERPOSITION_LOGIN_LOCKOUT 覆盖配置)。

Related MCP server: agents_mcp_chat

2. 分步验证脚本(一项一项运行)

.venv/Scripts/python tests/step1_domain_test.py        # 领域层:枚举/异常/状态机(§29)
.venv/Scripts/python tests/step2_db_test.py            # 数据层:建库/种子/条件状态更新(§48)
.venv/Scripts/python tests/step3_core_service_test.py  # 写星/三瓶/注释/编辑(含公共池)/不提供删除
.venv/Scripts/python tests/step4_request_flow_test.py  # 请求→审批→服务端随机→打开(§8.1/§47)
.venv/Scripts/python tests/step5_offer_flow_test.py    # 主动递星→接住(§8.2/§50)
.venv/Scripts/python tests/step6_response_audit_test.py# 回应/通知/审计(§20/§38/§53)
.venv/Scripts/python tests/step7_session_flow_test.py  # 纪念日 0 点快照互看 / 自定义特殊日(第二轮修订)
# step8 自建隔离服务(随机端口 + 唯一临时库),无需预先启动服务器,也绝不连生产:
.venv/Scripts/python tests/step8_api_smoke_test.py
.venv/Scripts/python tests/step9_security_regression_test.py # 认证/metadata/shared_at 回归
.venv/Scripts/python tests/step10_web_session_test.py   # Web 静态页 / HttpOnly 会话 / 退出撤销 / 同源校验
.venv/Scripts/python tests/step11_mcp_adapter_test.py   # actor-bound MCP adapter / 请求随机+指定
.venv/Scripts/python tests/step12_migration_restart_test.py # 迁移版本表 / 重启不改 session/quota
.venv/Scripts/python tests/step13_view_tracking_test.py # 首次查看 / 累计足迹 / 揭晓不双计 / 私人详情
.venv/Scripts/python tests/step14_web_frontend_regress_test.py # 前端 Cancel/私人编辑/揭晓渲染回归
.venv/Scripts/python tests/step15_remote_mcp_oauth_test.py     # 远程 MCP OAuth 2.1 全流程/密码登录

所有脚本都使用独立的唯一临时数据库(step8/step10 还会自建隔离 HTTP 服务与随机端口), 互不影响,也不污染 data/superposition.db。step8 若要通过 SUPERPOSITION_TEST_BASE_URL 指向外部服务,该服务必须以 SUPERPOSITION_TEST_INSTANCE=1 启动,否则脚本会拒绝运行。

3. 架构分层(低耦合,架构文档 §45)

Browser Web UI ── HTTP + HttpOnly session ─┐
curl / API ────── HTTP + Bearer token ─────┤
AI 伴侣 MCP ───── stdio / actor-bound ──────┤
                                           ▼
                                StarService
                                           ▼
                                  repositories
                                           ▼
                               SQLite / domain model
  • Web 与 MCP 工具层 共用同一个 StarService(§43~§46),没有第二套权限或状态机。

  • mcp_server.py 的身份固定绑定为 companion,工具参数里没有 actor_id,模型不能自报或切换身份。

  • 领域层可独立单测(tests/step1_domain_test.py 不触碰数据库)。

  • 数据库为 SQLite 单文件(WAL 模式),写事务 BEGIN IMMEDIATE + 条件 UPDATE 保证同一颗星不会被同时随机分配 / 递出 / 锁定(§48)。

4. API 一览(前缀 /api

方法

路径

说明

POST

/stars

写星(visibility=visible 直接进我们的瓶子;hidden 进私人瓶子;均可带 mood 与 note 注释)

PATCH

/stars/{id}

作者本人编辑自己的星(封存中 / 公共池均可;正在流程中短暂冻结)。产品不提供删除

GET

/bottles/counts

三个瓶子计数(对方私人瓶只有数量,§7)

GET

/stars/hidden/mine

我的私人瓶子(仅作者本人可见;列表刷新计查看足迹)

GET

/stars/hidden/mine/{id}

私人星详情:显式打开计 1 次足迹并初始化首次查看;不改状态、不通知对方;非作者一律 404

GET

/stars/shared/stars/shared/{id}

我们的瓶子列表 / 详情(含回应;列表支持作者、写入日、拆开日、来源、session 筛选)。详情每次显式打开计 1 次足迹,首次查看时间只记一次

POST

/requests

向对方请求一颗隐藏星(不传 star_id,§10/§31)

POST

/requests/{id}/respond

审批:give(可选 star_id 指定给哪一颗,缺省服务端随机)/ not_now

POST

/requests/{id}/open

请求人打开分配的星(不可重抽;打开后必须留话,才可再要下一颗)。返回值即完整详情,前端直接渲染不再补发请求

POST

/offers

作者主动递出指定的一颗(§14),可带 message 捎一句话

POST

/offers/{id}/accept

接住:正文展开,进入我们的瓶子(§15)。返回值即完整详情

POST

/stars/{id}/responses

文字 / 语音回应(语音字段已预留,§21)

GET / POST

/special-dates

特殊日期(种子不预置日期;可新增 anniversary 或 custom 类型)

POST

/sessions

发起"一起看星星吗?":必须绑定具体 special_date_id;session 类型由服务端按该日期的数据派生(anniversary→anniversary,custom→special_day),显式传 type 不一致会被拒绝。anniversary 只能在纪念日当天(业务时区)发起,双方额度按当天 00:00 冻结快照随 session 持久化

GET

/sessions

历史批次列表(含绑定的特殊日名称,供按批次回看)

POST

/sessions/{id}/confirm

对方确认 → 激活。纪念日额度已按当天 0 点冻结,确认不重算(跨午夜/重启也不变);自定义特殊日按确认那一刻瓶里数量(既有口径)

POST

/sessions/{id}/take-next

看一颗对方本轮可交换的星(随机)。纪念日:0 点后才写的星既不占额度也不进候选;我的额度耗尽提示"不可以哦,你的瓶子里面没有星星可以交换了,下次多写点吧。",对方本轮候选为空提示"对方瓶子里没有本轮可以交换的星星了。"。返回值即完整详情(该揭晓恰好计 1 次足迹)

POST

/sessions/{id}/finish

结束本轮;没看过的星自然留在各自瓶子里

POST

/sessions/{id}/cancel

发起方在确认前撤回邀请

POST

/sessions/{id}/decline

被邀请方在确认前拒绝邀请(不必先同意)

GET

/notifications、POST /notifications/{id}/read

轻提示(新星 / 请求 / 递星 / 邀约)

audit_log 仅供本地维护与故障审计,不提供普通 actor HTTP 读取接口。

5. 权限如何被后端强制(§58 第十三)

  • 服务层不提供"读取他人隐藏星列表 / 正文"的方法;猜中他人的 hidden star ID 也统一表现为"不存在"。

  • request_hidden_star() 只创建请求,绝不返回正文;随机分配结果在真正打开前不会出现在 API DTO 中。

  • 普通请求保留两种批准方式:缺省 star_id 时由服务端在审批事务内随机分配 (ORDER BY RANDOM()),请求人无法指定随机结果;主人也可传 star_id 指定给 自己某一颗可用的 hidden SEALED 星。两种方式结果落库即锁定,不可重抽。

  • incoming offer 在接住前不返回内部 star_id;隐藏星数量通知不返回 hidden star_id

  • 打开类操作逐一校验:请求人本人 / 被递星人本人 / session 参与者。

  • 纪念日 session:必须绑定具体纪念日(anniversary 类特殊日), 只能在纪念日当天发起;双方额度按当天 00:00(业务时区)冻结快照在发起时持久化 (actor_a_count / actor_b_count / quota_cutoff_at),确认、跨午夜、重启都不重算; 0 点后新写的星可以存在,但不占额度、也不进本轮候选。看的时候才真正打开那颗星, 没被看的自然留在瓶子里。"我的额度耗尽"与"对方本轮候选为空"是两种提示。 自定义特殊日(custom)保留既有口径:确认那一刻瓶里数量,走 special_day。

  • 用户规则:任何从私人瓶进入公共瓶的星(请求打开 / 接住 / 互看),看了必须留一句话 ——上一颗没留话之前不能打开/接住/看下一颗;可见星(一开始就公开的)不强制。

  • 用户规则:星星只有作者本人能编辑(封存中和公共池都可以,正在流程中短暂冻结); 编辑只能改内容/心情/注释,绝不会改变 state / visibility / shared_at / 首次查看; 星星不提供删除——写错可以改,抹掉不行。

  • 查看足迹(第二轮修订语义):"查看"只指明确打开正文详情——列表刷新、数量查询、 轮询、内部装配都不计。每一次明确查看只按人累计次数star_view_counts 聚合表, 不保存逐次查看时间);首次查看时间与人 (first_view_at / first_view_by)只在第一次写入,永不覆盖。请求打开 / 接住 / 拆星等揭晓接口的返回值就是完整详情,该揭晓恰好计 1 次足迹——前端与 MCP 直接用 返回值渲染,不再补发详情请求,同一次操作不会记两次。私人星同样支持显式查看留痕 (GET /stars/hidden/mine/{id}),但绝不改变状态、绝不向对方公开。

  • 请求周期一次一颗:没有 pending/approved 未打开的旧请求时才能发起新请求; give 时主人可指定给哪一颗(必须是主人自己的 hidden SEALED 星),缺省服务端随机。

  • 只有非空文字回应才解除"回应后再请求"的门槛;空白/占位音频 URL 会被直接拒绝。

  • 隐藏星数量通知在数据库中每个接收人只保留一行(重新置未读,不落新的逐颗时间), 输出层合并为一条置顶提示,历史多行旧数据同样收敛、不暴露 star_id 与时间。

6. 与架构文档的实现对照说明

  • REQUEST_PENDING / REQUEST_APPROVED 等请求侧状态记录在 star_requests.status 上; 星在批准前保持 SEALED(因为随机在批准时才发生),因此批准前仍可被作者编辑 / 递出 / 进入纪念日——这正是文档 §50 想要的行为。

  • 主动递星的 OFFERED → DELIVERED → OPENED → SHARED 在"接住"这一个事务里依次经过, 不会停在悬空状态;offer 最终状态记为 completed

  • 旧版"快照锁定/结束退回"的 session 逻辑已按用户规则改为配额互看。第二轮修订引入 schema_migrations 版本表:每个迁移按 name 只执行一次;旧的破坏性清理 (解锁 SESSION_LOCKED 星 / 收尾旧 session)已退休,正常重启不会改动任何业务 状态——waiting/active session 跨重启保持原样(tests/step12 覆盖)。

  • 业务时区固定为 SUPERPOSITION_BUSINESS_TIMEZONE(默认 Asia/Shanghai): "纪念日当天 00:00" 的额度冻结时刻以它计算,不随系统时区 / VPN / 服务器迁移漂移; 库内时间串继续是无 offset 的业务时区本地时间,与历史数据口径一致。

  • 纪念日额度不需要常驻定时器:0 点状态由 written_at < cutoff AND (shared_at IS NULL OR shared_at >= cutoff) 从历史恢复,候选另加当前 SEALED 限制;cutoff 与额度随 session 持久化,重启后直接读库。

  • 打开方式记录在 open_mode / shared_origin / cycle_id,我们的瓶子可以区分 "一开始就公开 / 请求随机 / 主动递出 / 纪念日批次"(§17 / §37)。

  • 所有隐藏星合法打开路径都会写 shared_at = opened_at,共同瓶子按真正进入 shared 的时间排序。

7. 公网访问(可选,Cloudflare Tunnel 或任意反向代理)

默认只监听 127.0.0.1:8321,不主动暴露公网。想在外面访问时,用 Cloudflare Tunnel 或任意反向代理把你的域名指向 127.0.0.1:8321 即可。

  • 若走 HTTPS 公网域名,设置环境变量 SUPERPOSITION_PUBLIC_ORIGIN=https://你的域名 (远程 MCP 的 DNS 重绑定放行名单使用它,见 mcp_server.py)。

  • 注意:Cloudflare 开了浏览器完整性检查时,程序化客户端必须带浏览器式 User-Agent, 否则会被 403(error code: 1010)。例如 User-Agent: Mozilla/5.0 Chrome/128。 也可以在 CF 控制台为该主机名关闭 Browser Integrity Check / Bot Fight Mode。

8. AI 伴侣 MCP(本机 stdio + 手机远程两种接法)

远程(手机 claude.ai / 任意 MCP 客户端):后端在 /mcp 暴露 OAuth 2.1 保护的 Streamable HTTP MCP(经 Cloudflare 隧道公网可达,CF 侧零配置)。在 claude.ai 添加自定义连接器,URL 填 https://你的域名/mcp, 会自动弹出许愿星登录页,输入家里人的密码即完成授权;之后 Claude 以AI 伴侣身份 使用全部 28 个工具。令牌 30 天有效、支持刷新;删除 data/mcp_oauth_secret.txt 并重启可撤销全部授权。SUPERPOSITION_MCP_HTTP=0 可整体关闭远程端点。

本机(stdio)

本地 stdio MCP 使用官方 Python SDK v2。宿主启动:

.venv/Scripts/python mcp_server.py

它直接复用 StarService 和同一个 SQLite 数据库,目前注册 28 个工具(含私人星详情 my_hidden_star),覆盖身份、三瓶计数、写星、 写星(含注释)、作者编辑(含公共池)、请求/审批/打开(可指定给哪颗)、主动递星(可捎话)/接住、 共同星轨与查看足迹、回应门槛、通知、特殊日期、纪念日配额互看(含取消/拒绝与历史批次)。 不要把 mcp_server.py 暴露成无认证公网 stdio/HTTP 服务;它的安全边界是本机宿主进程。

9. 版本范围

  • Product V1:三个瓶子、写星、可见/不可见、请求随机、主动递星、接住打开、回应、通知、 纪念日 session(含取消/拒绝)、快照、内部审计、Bearer / HttpOnly 身份认证(服务端可撤销)、 metadata 脱敏(隐藏星数量通知不含逐颗时间)、筛选(含拆星批次)、响应式 Web UI (完整私人星管理、特殊日期管理、手机退出入口、键盘可达)、AI 伴侣 actor-bound MCP/tool adapter 均已实现并通过回归测试。

  • V1.3.0(上线收尾):远程 MCP 连接器(OAuth 2.1 登录页 + PKCE + 动态注册,/mcp)、 网页密码登录(与访问钥匙同框)、隧道持久化启动、测试数据清零维护脚本。

  • V1.2.0(第二轮修订):schema_migrations 一次性迁移(重启不再结束 session); 业务时区与纪念日 0 点冻结快照额度(post-cutoff 新星不进本轮);session 强绑定 special_date_id 且类型由日期数据派生;first_view / 累计足迹语义(揭晓恰计 1 次、 列表刷新不计);私人星详情端点;前端 Cancel / 私人编辑分流 / 揭晓直渲染修复; 纪念日与自定义特殊日在 UI 与校验上彻底区分。

  • 宿主接线(待具体宿主)mcp_server.py 已可运行;Claude/其他宿主需要在其 MCP 配置中注册这条本地命令。

  • V1.5(未做):浏览器录音上传(字段已预留 audio_url / audio_duration)、折纸动画等视觉。

  • V2(未做):语音"耳朵"接入、共同回忆时间轴等。

本地开发重置数据:删除 data/ 目录下的三个数据库文件后重启即可 (生产数据严禁如此处理——真实 data/superposition.db 不得删除/重建/清空)。

许可协议

AGPL-3.0-only + 附加条款:禁止商用。个人学习、研究与非商业的自用和修改自由, 但衍生作品须以相同协议(AGPL-3.0 + 本附加条款)完整开源并保留署名; 任何商业用途需事先取得作者书面授权。详见 LICENSE

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides AI agents with persistent identities (Weid numbers) and a friend-based messaging system, enabling cross-platform AI-to-AI communication through 11 MCP tools.
    1
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to communicate directly via 1-to-1 chat over MCP, supporting registration, chat creation, and message exchange without human relay.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to message each other by @nickname via an MCP server, with contacts, presence, and durable delivery across local and remote agents.
    3
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables personal AI agents to discover compatible counterparts over MCP, exchange private asynchronous messages, and submit sealed recommendations that reveal mutual affinity only when both agree.
    3 npm
    AGPL 3.0