执行清单,不是事实来源。所有实现细节以
docs/为准;本文件与docs/冲突时以docs/为准。docs/只读,需求变更请先改文档,再勾选这里的条目。
当前阶段:第 2 章(鉴权核心)已完成。第 1 章仅剩 migration 执行脚本(依赖第 7 章的 deploy/)。下一步进入第 3 章(审计)。
图例:[ ] 未做 · [x] 已完成 · [~] 部分完成
backend/cmd/haven/main.go 重写为读环境变量的入口(见 architecture.md 第四节、5.2/5.3):删除硬编码 :8080,改用 HAVEN_LISTEN_ADDR,接 log/slogfrontend/src/components/HelloWorld.vue 与模板样式(style.css 里的 demo 变量、src/assets/{vue.svg,vite.svg,hero.png})frontend/README.md(Vite 模板说明)与 frontend/public/icons.svg 等模板残留,保留 favicon.svg 或替换frontend/index.html:lang="zh-CN",标题改为 Havenvue-router(History 模式)与 pinia 到 main.ts[x] 根目录 .gitignore(当前只有子目录里的,backend/.gitignore 里 /backend/... 这类路径是按仓库根写的,实际匹配不到)
[x] 根目录 AGENTS.md:写文档驱动规则、Git 规范、TODO 边界、技术栈锁定(已含“AI 只读不写 docs”“禁止自动提交”“按 TODO 顺序执行”三条)
[x] 根目录 opencode.json:instructions 只加载 docs/conventions.md 和 docs/overview.md,其余文档按需手动喂,不全量加载
internal/config:读取并校验全部 HAVEN_* 变量,缺必填项拒绝启动;常量(cookie 名、有效期、bcrypt cost)写死不走配置internal/db:database/sql + pgx 连接池,启动时 pingmigrations/0001_init.up.sql / .down.sql:按 data-model.md 依赖顺序建表(users → sessions/pending_logins/trusted_devices/tokens/audit_events → notes/tags/portfolio_items → note_tags/portfolio_repos),含全部索引与 CHECKCREATE EXTENSION IF NOT EXISTS pg_trgm;;确认 gen_random_uuid() 可用backend/cmd/haven-init-user)deploy/ 或文档待定处补执行方式(已确认启动不迁移;执行方式待 deploy/ 建立后补,见第 7 章)internal/auth,按 auth.md)Authorization: Bearer 优先于 cookie;sessions / tokens 两张表;pending_logins 不参与anonymous_ok / login_required / write / totp_verified 四档,并把可见性条件注入 contexthaven_session / haven_trusted_device,HttpOnly、SameSite=Lax、Domain=HAVEN_COOKIE_DOMAIN、Secure 跟随 HAVEN_COOKIE_SECUREpending_logins + temp_token(5 分钟)sessions.last_used_at,不延长 expires_attotp_enabled;disable 同时删除该用户全部授信设备sessions + trusted_devices,不删 tokens,当前会话也失效补充实现(在章节内一并完成):会话/授信设备/Token 的
internal/auth业务与 handler、internal/httpx统一错误响应、cmd/haven路由装配。 会话「是否通过过 TOTP」用 token 后缀:totp承载(auth.md6.2 需要、sessions表无该列),与pending_logins的trust_device后缀手法一致。
internal/audit,按 audit.md)audit_events 写入器:只追加;actor_user_id 可为 NULL,actor_username 存快照api.md 逐条实现)/api/me(11.1):匿名/会话/Token 三种响应/api/auth(二):login、login/totp、logout、totp/setup|verify|disable、password/api/sessions(三):列表带 is_current;禁止撤销当前会话(422);Token 访问 403/api/trusted-devices(四):只读 + 删除,不提供 POST/api/tokens(五):创建仅返回一次明文;撤销写 revoked_at 不删记录;需 TOTP/api/notes(六):列表 updated_at DESC + content_preview(rune 截 200 + …);创建不收 visibility;tag_ids 全量替换published_at 首次写入、撤回保留;重复操作 409/api/public/notes(七):published_at DESC,不可见统一 404/api/tags(八):lower(name) 唯一 → 409;无重命名接口;级联删除关联/api/search 笔记查 notes.search_text ILIKE '%q%'、标签查 tags.name ILIKE '%q%',返回 {notes, tags} 两块;/api/public/search 只查公开笔记的 search_text,不返回标签列表/api/public/portfolio + /api/portfolio/*(十):url 由后端拼接(Gogs 用 HAVEN_GOGS_BASE_URL,GitHub 固定);主链接唯一internal/httpx:统一错误响应(error/message/可选 fields)+ 统一分页(limit 默认 20 上限 100)snake_case;列表空值返回 {"items": [], "total": 0}r.Static 托管 /assets(长缓存),NoRoute 兜底 index.html;HAVEN_FRONTEND_DIR/api/* 不被 SPA 兜底吞掉frontend.md)src/api/ 按领域拆文件(auth/notes/tags/search/portfolio/sessions/devices/tokens/me)+ 统一 ApiError;路径写相对 /api/...auth store:启动调 GET /api/me;401 统一清空并跳 /login;403 只提示不跳转window.location.hostname 选择公开域/私有域路由表(同一份构建)/login 外全部需登录,支持 ?redirect=/、/notes/:id、/search?q=、/portfolio;无登录入口、无后台链接/login(含 TOTP 第二步,temp_token 只存内存)、/、/notes/:id、/notes/new、/search、/tags、/portfolio、/settings、/settings/security、/settings/sessions、/settings/devices、/settings/tokens/settings/tokens 遇 403 时引导去 /settings/security 完成 TOTP,不登出NoteCard、TagBadge、SearchBar、LoginForm、TotpForm、SettingsNav/api 到后端(vite.config.ts 目前没有 server proxy)go vet ./...、go test ./... 全绿;至少覆盖:可见性矩阵、Token scope、跨用户 404、publish 幂等性pnpm build(含 vue-tsc -b)通过deploy/(README 与 conventions.md 提到但尚不存在):构建脚本 + systemd unit + migration 执行architecture.md 第八节):端口、HTTPS/反代、pg_dump 备份定时任务README.md 的「快速开始」占位internal/llm 只保留接口)