Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ FASTWRITE_URL=http://127.0.0.1:3003
FASTWRITE_PUBLIC_URL=http://127.0.0.1:3003
FASTWRITE_API_KEY=
FASTTASK_URL=http://127.0.0.1:10000
# Independent server-to-server identity for today (all three or none).
# Register audience fasttask-api and scope integrations:fastresearch in FastCAS.
FASTTASK_SERVICE_ISSUER=
FASTTASK_SERVICE_CLIENT_ID=
FASTTASK_SERVICE_CLIENT_SECRET=
FASTTASK_SERVICE_ALLOW_LOOPBACK_HTTP=false
# Legacy today path when no service settings exist; Task must enable its legacy switch.
FASTTASK_API_KEY=replace-with-fasttask-ingress-key
# Optional; set only when FastTask provides account lifecycle endpoints.
FASTTASK_PROVISION_KEY=
Expand Down
4 changes: 4 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,9 @@ VITE_FASTPPT_URL=/fastppt/
# Public FastTask origin used to validate external SSO callbacks.
FASTTASK_PUBLIC_URL=https://fasttask.example.com
FASTTASK_URL=http://fasttask:10000
# Separate from FASTRESEARCH_FASTCAS_* browser-login credentials. Set all three.
FASTTASK_SERVICE_ISSUER=
FASTTASK_SERVICE_CLIENT_ID=
FASTTASK_SERVICE_CLIENT_SECRET=
# Must match FASTTASK_FASTRESEARCH_SSO_SECRET in FastTask and contain at least 32 characters.
FASTRESEARCH_FASTTASK_SSO_SECRET=replace-with-a-random-secret-of-at-least-32-characters
5 changes: 5 additions & 0 deletions .github/scripts/ci-node.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
npm test
npm run build
test -f dist/index.html
6 changes: 5 additions & 1 deletion .github/workflows/tag-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ permissions:
contents: read

jobs:
node-gate:
uses: FastR-D/.github/.github/workflows/node-npm-check.yml@98eac6706014d070c5563eb75e3899611507f176

validate:
needs: node-gate
runs-on: ubuntu-24.04
timeout-minutes: 20
concurrency:
Expand Down Expand Up @@ -68,7 +72,7 @@ jobs:

deploy:
needs: validate
if: startsWith(github.ref, 'refs/tags/v')
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
# FastDeploy v2: pin the reviewed implementation, including its server scripts.
uses: FastR-D/FastDeploy/.github/workflows/deploy.yml@9a2c51dcd63f47a0e6c8f6bb07bd22ea589abbd2
with:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ FASTRESEARCH_CORS_ORIGINS=

FastTask 可使用 `FASTTASK_INGEST_KEY` 配置专用 `X-FastTask-Key` 发布凭证;该凭证只用于工作流发布,不影响 FastInsight 的 `FASTINSIGHT_INGEST_KEY`。

FastResearch 回传工序完成证据时使用 `FASTTASK_URL` + `FASTTASK_API_KEY`,请求 FastTask 的 `/api/v1/integrations/fastresearch/progress`,并通过 `X-API-Key` 认证。FastTask 对应配置为 `FASTTASK_FASTRESEARCH_INGEST_KEY`;同一工序事件使用稳定 `event_id` 幂等,服务重启后会继续重试待同步事件。
Research 已预留工序完成证据的 outbox 和 `/api/v1/integrations/fastresearch/progress` 客户端,使用稳定 `event_id` 重试;当前 Task 接收端仅有 `/today`,尚未实现 progress/tasks/accounts。部署时不能把这些预留调用视为已可用的跨产品能力,today 的独立服务身份也不授权这些写操作。

- `POST /api/content/reading/publish` - FastRead 推送阅读记录
- `POST /api/insight/publish` - FastInsight 推送研究信号
Expand Down Expand Up @@ -209,7 +209,7 @@ FastTask 使用 `fasttask-dispatch-v1` 协议版本发布任务:
| `FASTTASK_FASTRESEARCH_PUBLIC_URL` | FastTask | 浏览器访问 FastResearch 的公开地址 |
| `FASTTASK_FASTRESEARCH_SSO_SECRET` | FastTask | 与 FastResearch 侧一致 |

FastResearch 工作台通过 `GET /api/fasttask/today` 展示当前成员在 FastTask 的当天任务,`GET /api/workflow/team` 同时返回每位成员当天的 FastTask 任务进度。FastResearch 服务端使用 `FASTTASK_URL` + `FASTTASK_API_KEY` 调用 FastTask 的 `/api/v1/integrations/fastresearch/today`;FastTask 侧对应配置为 `FASTTASK_FASTRESEARCH_INGEST_KEY`,两边的 Key 必须一致。没有收到任务时,工作台显示「未接受到任务」。
FastResearch 工作台通过 `GET /api/fasttask/today` 展示当前成员在 FastTask 的当天任务,`GET /api/workflow/team` 同时返回每位成员当天的 FastTask 任务进度。服务端配置 `FASTTASK_URL` 及 `FASTTASK_SERVICE_ISSUER`、`FASTTASK_SERVICE_CLIENT_ID`、`FASTTASK_SERVICE_CLIENT_SECRET`,通过已发行 FastCAS SDK 获取 scope 为 `integrations:fastresearch` 的服务 token。该身份独立于浏览器登录,FastCAS 注册须签发 Task audience(默认 `fasttask-api`);Task 侧还须允许此 client 和对应 accountId 的 receiver refs。仅本地 HTTP issuer 需要 `FASTTASK_SERVICE_ALLOW_LOOPBACK_HTTP=true`。现有 `FASTTASK_API_KEY`(或 `FASTTASK_PROVISION_KEY`)部署仍可查询,但要求 Task 显式启用 `FASTTASK_FASTRESEARCH_LEGACY_INGEST_KEY_ENABLED` 且共享 Key 一致;服务身份配置不全或获取 token 失败不会降级到 Key。没有收到任务时,工作台显示「未接受到任务」。接收合同见 `contracts/fastresearch-today/v1/`;Task 当前未实现 tasks/progress/accounts 接收端。

本机以 `FASTTASK_ENV=production` 运行且公开地址为回环 HTTP 时,FastTask 还需设置 `FASTTASK_FASTRESEARCH_LOOPBACK_HTTP=true`。完整接口与安全规则见 [docs/key-login.md](docs/key-login.md)。

Expand Down
21 changes: 21 additions & 0 deletions contracts/fastresearch-today/v1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Vendored Task-owned today fixture

Source: FastR-D/FastTask `contracts/fastresearch-today/v1/cases.json`.
This local copy supports a clean single-repository test run. Upgrade it together
with the receiver-owned contract; it is not a separately authored protocol.
The shared fixture describes the real today endpoint only. Research's historical
tasks/progress/account methods remain reserved against Task main 8e91d76.

`npm test` exercises the production today factory and installed registry SDK.
`node scripts/fasttask-today-contract.mjs` requires an explicitly configured local
test receiver and issuer, and asserts both service and legacy paths. The Task
wire test can launch that probe with a disposable signed issuer and real Task
HTTP server. The probe also starts Research's actual server with only service
credentials, logs in a local Key member, and calls its workspace today route
twice. It asserts received=true, the full nonempty three-item projection,
completion/progress counts, plan metadata and workspace links against
`research_today`. The direct client also checks the known and unknown receivers'
entire wire responses. Task seeds actual account mappings and domain rows;
`$today` is the current UTC date, not a product configuration option.
Task checks that the production client reuses its token. It must never
target production.
45 changes: 45 additions & 0 deletions contracts/fastresearch-today/v1/cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"request": {"receiver_refs": [" account-a ", "account-a", "", "missing-account"]},
"normalized_refs": ["account-a", "missing-account"],
"response": {"members": [
{
"receiver_ref": "account-a", "found": true,
"user_id": "task-user-a", "display_name": "Research member",
"local_date": "$today", "timezone": "UTC", "plan_id": "today-plan-a",
"plan_status": "active", "plan_revision": 3, "updated_at": "2026-11-10T00:00:00Z",
"items": [
{"id": "item-read", "task_id": "task-read", "kind": "core", "title": "fastread: Read evidence", "commitment": "Extract two claims", "minimum_action": "Read the abstract", "status": "in_progress", "target_minutes": 30, "position": 1, "workspace": {"system": "fastread", "label": "FastRead", "url": "https://fastrd.io/read/"}},
{"id": "item-write", "task_id": "task-write", "kind": "core", "title": "fastwrite: Draft findings", "commitment": "Save a findings draft", "minimum_action": "Write one paragraph", "status": "satisfied", "completion_type": "result", "completion_summary": "Draft saved", "target_minutes": 45, "position": 2, "workspace": {"system": "fastwrite", "label": "FastWrite", "url": "https://fastrd.io/write/"}},
{"id": "item-review", "task_id": "task-review", "kind": "core", "title": "Review evidence", "commitment": "Check the claim list", "minimum_action": "Open the checklist", "status": "planned", "target_minutes": 45, "position": 3}
],
"progress": {"total": 3, "completed": 1, "in_progress": 1, "planned": 1, "skipped": 0}
},
{"receiver_ref": "missing-account", "found": false, "items": [], "progress": {"total": 0, "completed": 0, "in_progress": 0, "planned": 0, "skipped": 0}}
]},
"research_today": {
"received": true, "date": "$today", "timezone": "UTC", "planStatus": "active", "planRevision": 3, "updatedAt": "2026-11-10T00:00:00Z",
"items": [
{"id": "item-read", "taskId": "task-read", "kind": "core", "title": "fastread: Read evidence", "commitment": "Extract two claims", "minimumAction": "Read the abstract", "status": "in_progress", "completionType": "", "completionSummary": "", "targetMinutes": 30, "position": 1, "workspace": {"system": "fastread", "label": "FastRead", "url": "https://fastrd.io/read/"}},
{"id": "item-write", "taskId": "task-write", "kind": "core", "title": "fastwrite: Draft findings", "commitment": "Save a findings draft", "minimumAction": "Write one paragraph", "status": "satisfied", "completionType": "result", "completionSummary": "Draft saved", "targetMinutes": 45, "position": 2, "workspace": {"system": "fastwrite", "label": "FastWrite", "url": "https://fastrd.io/write/"}},
{"id": "item-review", "taskId": "task-review", "kind": "core", "title": "Review evidence", "commitment": "Check the claim list", "minimumAction": "Open the checklist", "status": "planned", "completionType": "", "completionSummary": "", "targetMinutes": 45, "position": 3, "workspace": null}
],
"progress": {"total": 3, "completed": 1, "inProgress": 1, "planned": 1, "skipped": 0}, "error": ""
},
"auth_cases": [
{"name": "service", "status": 200},
{"name": "legacy", "status": 200},
{"name": "mixed", "status": 400},
{"name": "missing", "status": 401},
{"name": "wrong-scope", "status": 401},
{"name": "wrong-audience", "status": 401},
{"name": "wrong-issuer", "status": 401},
{"name": "wrong-client", "status": 401},
{"name": "non-service", "status": 401},
{"name": "wrong-subject", "status": 401},
{"name": "expired", "status": 401},
{"name": "wrong-signature", "status": 401},
{"name": "wrong-token-use", "status": 401},
{"name": "denied-receiver", "status": 403},
{"name": "service-disabled", "status": 503}
]
}
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ services:
fastresearch:
build:
context: .
additional_contexts:
fastcas-sdk: ../FastCAS/sdk/typescript
args:
VITE_API_URL: ${VITE_API_URL:-}
VITE_READ_URL: ${VITE_READ_URL:-}
Expand Down
6 changes: 4 additions & 2 deletions docs/FASTCAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

四项配置必须一起提供:`FASTRESEARCH_FASTCAS_ISSUER`、`FASTRESEARCH_FASTCAS_CLIENT_ID`、`FASTRESEARCH_FASTCAS_CLIENT_SECRET`、`FASTRESEARCH_FASTCAS_REDIRECT_URI`。回调为本实例 `/api/auth/fastcas/callback`;scope 为 `openid profile email`;事件端点为 `/api/auth/fastcas/events`。开发环回 HTTP 可设置 `FASTRESEARCH_FASTCAS_ALLOW_LOOPBACK_HTTP=true`。未配置时不导入 SDK,不访问 FastCAS。

当前 SDK 固定正式 npm 版本 `@fastrd/fastcas@0.1.2`,直接运行 `npm ci` 安装本项目依赖。锁文件已更新;网络恢复后 `npm ci --no-audit --no-fund` 完成,`npm run build`(TypeScript + Vite)通过。
SDK 使用正式发行包 `@fastrd/fastcas@0.1.2`,由根 package.json 与 package-lock.json 精确锁定。从本仓根目录执行 `npm ci --no-audit --no-fund`,随后 `npm run build`(TypeScript + Vite);安装与生产构建均不需要相邻 FastCAS checkout 或预构建 SDK。

FastCAS 认证操作要求页面 Origin 与回调 Origin 完全一致;部署采用前端/API 同源。回调仅接收发起浏览器的 HttpOnly 绑定 Cookie 和原项目会话 Cookie,使用 SDK 的 state/nonce/PKCE 验证。登录结果通过 HttpOnly 项目会话返回,前端经既有 `/api/content/me` 恢复项目会话,不接收上游 token。

绑定关系和一次性事务持久化。撤销事件只接受最大 64 KiB `application/jwt`,先 SDK 验签,再原子更新映射、会话和去重记录。CAS 会话每五分钟复核绑定;无法确认时拒绝该来源,本地 Key 入口仍可用。解除认证只撤销该绑定的 CAS 来源会话。本地 Key 撤销不会直接删除账号或 CAS 身份。

API 前缀 `/api/auth/fastcas`:GET `available/login/callback/status`;POST `link/reconcile/revoke/events`。link/revoke 的 JSON body 为 `{key}`。

容器直接安装锁定的正式 SDK:`docker build -t fastresearch .`,不需要额外 SDK 构建上下文。Dockerfile 已更新,容器构建尚未验证。
容器仅使用本仓上下文:`docker build -t fastresearch .`。Dockerfile 的 `npm ci` 安装同一 registry 锁,运行层直接复制已安装的 node_modules;不再需要 SDK named context。Compose 同样只用 `context: .`:部署前准备 `.env.production` 后执行 `docker compose build fastresearch`。镜像实构结果与宿主 Docker daemon 阻碍见根实施记录;不能用宿主 npm 构建成功代替镜像验收。

验证现状:`node --test server/*.test.mjs` 7 项通过,涵盖原服务真实 HTTP 生命周期、关闭 CAS 的接口、迁移和服务层。完整前端类型检查及生产构建通过;真实 Go/PostgreSQL 提供方 → Node SDK → FastResearch HTTP 服务联调已通过。新增真实 Chrome 移动端契约:项目页面 Key 登录、显式绑定、原生 FastCAS 授权确认和回调、独立浏览器 FastCAS 登录均回到同一账号及研究笔记;浏览器解绑后 CAS 会话失效而 Key 会话与内容可用,同时修复登录后页头横向溢出。FastCAS 登录页的 CSP 现允许本次已登记的精确回调源,浏览器跨端口自动跳转在 race 模式连续三次通过;生产域名流程仍待验收。全局退出和身份停用的标准退出通知已通过真实跨进程投递。

Expand Down Expand Up @@ -42,3 +42,5 @@ FastNews 可选跨项目读取使用 `GET /api/connectors/news/profile`。该端
使用字面量 `false` 关闭。两个分项开关不会阻断签名事件、来源会话撤销或原本地登录,也不改变账号和业务数据;全局关闭时 CAS 来源会话在项目原有有效性检查边界失效。保留独立的本地恢复方式后再关闭中心登录。

发布依赖基线更新为 FastCAS SDK **0.1.2**:使用正式 npm/PyPI 包或 Go 模块标签,不再依赖同级 FastCAS 源码目录。开发者修改 SDK 时可自行使用本地 override;生产锁文件固定已验收版本。

构建后运行 `node scripts/fastcas-mode-browser.mjs /path/to/chromium /path/to/evidence`,可验证真实发布 SDK → 临时协议 issuer → Research HTTP/SQLite → 浏览器的登录、绑定、撤销和独立模式。该场景校验 Basic 客户端凭证、PKCE、签名 ID token、userinfo 与 account-link endpoint,覆盖 pending 关系在 link 关闭时拒绝激活,以及 Key 会话和内容保留;结果清单写入 evidence 目录。临时 issuer 是外部协议 fixture,本次运行不等同于真实 Go/PostgreSQL 中心验收。合并验证的实际命令与环境限制见工作区根 `INTEGRATION-fast-research.md`。
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ Authorization: Bearer <成员会话>

### 3.2.2 FastTask 当日任务

`GET /api/fasttask/today` 返回当前成员在 FastTask 的当天计划。FastResearch 服务端使用 `FASTTASK_URL` + `FASTTASK_API_KEY` 调用 FastTask 的 `POST /api/v1/integrations/fastresearch/today`;FastTask 侧校验 `FASTTASK_FASTRESEARCH_INGEST_KEY`,并按 `receiver_ref`(即 `accountId`)找到对应 FastTask 用户。
`GET /api/fasttask/today` 返回当前成员在 FastTask 的当天计划。FastResearch 服务端使用 `FASTTASK_URL` 和独立的 `FASTTASK_SERVICE_ISSUER/CLIENT_ID/CLIENT_SECRET` 配置,通过 FastCAS SDK 的 client_credentials 获取 `integrations:fastresearch` scope 服务 token,调用 Task 的 `POST /api/v1/integrations/fastresearch/today`。Task 校验 audience(默认 `fasttask-api`)、client allowlist 及 `receiver_ref`(即 `accountId`)授权。未配置服务身份的既有 Key 部署需要 Task 显式打开 legacy ingest 开关;失败的服务认证不会降级。wire 合同见 `contracts/fastresearch-today/v1/`。

```json
{
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
"test:fasttask": "node --test server/fasttask-integration.test.mjs",
"test:integration": "node --test server/integration.test.mjs",
"test:visual": "node scripts/visual-check.mjs",
"test:fastui": "node scripts/fastui-browser.mjs",
"start": "node --env-file=.env.production server/index.mjs"
},
"dependencies": {
"@fastrd/ui-foundation": "0.1.2",
"@fastrd/ui-react": "0.1.2",
"@fastrd/fastcas": "0.1.2",
"lucide-react": "^0.468.0",
"react": "^19.0.0",
Expand Down
Loading
Loading