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
9 changes: 9 additions & 0 deletions .github/scripts/ci-node.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
npm run build
test -f dist/index.html
npx --no-install playwright install --with-deps chromium
CHROME_BIN="$(node --input-type=module -e "import { chromium } from '@playwright/test'; process.stdout.write(chromium.executablePath())")"
export CHROME_BIN
"$CHROME_BIN" --version
npm test
20 changes: 17 additions & 3 deletions .github/workflows/tag-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,39 @@ permissions:
contents: read

jobs:
web:
uses: FastR-D/.github/.github/workflows/node-npm-check.yml@98eac6706014d070c5563eb75e3899611507f176
with:
working-directory: web

validate:
needs: web
runs-on: ubuntu-24.04
timeout-minutes: 45
concurrency:
group: validate-fasttask-${{ github.ref }}
cancel-in-progress: true
env:
GOWORK: 'off'
CGO_ENABLED: '1'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26.8'
cache: true
- run: go mod download
- run: go mod verify
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: web/package-lock.json
- run: go test ./...
- run: CGO_ENABLED=1 go build -trimpath -o bin/fasttask ./cmd/fasttask
- run: npm ci && npm test && npm run build && npm run build:sidecar && npm prune --omit=dev --ignore-scripts
- run: go build -trimpath -o bin/fasttask ./cmd/fasttask
# The reusable web gate tests this revision before the release job rebuilds
# its locked web/sidecar runtime and prunes development-only dependencies.
- run: npm ci && npm run build && npm run build:sidecar && npm prune --omit=dev --ignore-scripts
working-directory: web
- name: Checkout pinned FastDeploy packager
uses: actions/checkout@v4
Expand All @@ -50,7 +64,7 @@ jobs:

deploy:
needs: validate
if: startsWith(github.ref, 'refs/tags/v')
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: FastR-D/FastDeploy/.github/workflows/deploy.yml@9a2c51dcd63f47a0e6c8f6bb07bd22ea589abbd2
with:
app: fasttask
Expand Down
54 changes: 54 additions & 0 deletions contracts/fastresearch-today/v1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# FastResearch today v1

Owner: FastTask. `POST /api/v1/integrations/fastresearch/today`, JSON, `Cache-Control: no-store`.
The path version is the contract release identifier, not a required body field.

Primary identity: FastCAS client_credentials, scope `integrations:fastresearch`.
Issuer registration must issue the Task audience (default `fasttask-api`);
the SDK grant does not accept an arbitrary audience override. Task verifies the
signature, issuer, audience, expiry, access token_use, service=true, sub=client_id,
client allowlist and each normalized receiver's exact allowlist entry.

Legacy identity remains an actual supported path: `X-API-Key` (or existing
`X-FastResearch-Provision-Key`) only when `FASTTASK_FASTRESEARCH_LEGACY_INGEST_KEY_ENABLED=true`
and the configured ingest key matches. Never send it with Authorization; never
retry a rejected Bearer using the key. Credentials are server-side only.

`request.schema.json` describes raw wire input; normalization takes the first 200
unique nonempty trimmed refs, preserving order. Empty normalized lists return
400. `cases.json` supplies synthetic fixtures for both HTTP receiver and Research
client tests. Unknown receivers have found=false, items=[], zero progress.
Known receivers include user/date/plan metadata and items. A per-member `error`
may accompany lookup/refresh failures; HTTP 200 alone is not a complete-member
success. RefreshSchedule may derive today's plan; this API does not create or
complete underlying tasks. Counts reflect satisfied/in_progress/skipped/planned.

The positive fixture maps `account-a` to an active user, a frozen current UTC
plan at revision 3, and three real task/item rows: in_progress, satisfied and
planned. The receiver test seeds domain rows in SQLite; it does not inject HTTP
responses or replace RefreshSchedule/PlanItemViews. It compares the entire wire
response, including Task IDs, commitments, completion metadata, counts and the
FastRead/FastWrite workspace projections. `missing-account` stays unknown.
`$today` is replaced with the current UTC calendar date by both runners; stable
IDs and the fixed updated_at remain exact assertions. `research_today` records
the expected Research member projection, including snake_case to camelCase
fields and null workspace for an unclassified task.

The mapped `outside` account is deliberately absent from the service allowlist:
service queries return 403, while the explicitly enabled legacy shared key can
still read it. Legacy has its existing integration-wide authority, not the
service client's receiver restriction.

Run `GOWORK=off go test ./internal/httpapi -run TestFastResearchTodayWireContract -count=1`.
For a local cross-product run, also set `FASTRESEARCH_CONTRACT_PROBE` to the
absolute Research `scripts/fasttask-today-contract.mjs` path. This makes the same
receiver test run the installed npm SDK through Research's production factory,
against live loopback Task and a disposable signed OIDC issuer, then starts the
actual Research server and queries its member today route with service-only
configuration. The issuer counts grants to verify token reuse. Ordinary product
CI needs only its own checkout and registry dependencies.

Research vendors this fixture under `contracts/fastresearch-today/v1`; updates
must be copied from this receiver-owned directory and reviewed on both sides.
No tasks/progress/accounts endpoint is promised by this contract. These two ends
are not evidence of two independent sending products or a deployed FastCAS run.
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}
]
}
8 changes: 8 additions & 0 deletions contracts/fastresearch-today/v1/request.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "FastResearch today wire request",
"type": "object",
"required": ["receiver_refs"],
"properties": {"receiver_refs": {"type": "array", "items": {"type": "string"}}},
"description": "Receiver trims whitespace, drops empty/duplicate values and takes the first 200 unique refs. At least one normalized ref is required. Unknown properties are ignored."
}
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ services:
fasttask:
build:
context: .
# Pinned in the Dockerfile; override only to track a different FastCAS SDK commit.
args:
FASTCAS_REF: ${FASTCAS_REF:-676d49b18f700c009f1f491280a93de7da294836}
# The default module proxy is not reachable from this network; the host's own Go
# toolchain is configured the same way (go env GOPROXY).
GOPROXY: ${GOPROXY:-https://goproxy.cn,direct}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/coreos/go-oidc/v3 v3.21.0
github.com/coreos/go-oidc/v3 v3.21.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4
Expand Down
Loading
Loading