Merge origin/main into feature/fast-slow-display-design
Why: Clear SYNC_REQUIRED so the design branch tracks current main while keeping Documents fast-slow r1.1 + U1 plan commits. Evidence: make task-sync; Documents merge to 5b71525. Impact: Submodule pins follow origin/main except Documents feature tip. Refs: policy/delivery.md task-sync Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
# feature/u7-b5-c2-load 任务卡
|
||||
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/439;状态:进行中;owner:weichen
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/439;状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:B5 seed(tiny/contract)+ C2 `--instances 2` 入口落地;检查单 B5/C2 标 ⏳;tiny 单/双实例 smoke 绿(SELF)
|
||||
- 验证:PASS——`run_load.sh --seed tiny --skip-k6`(SSE p95≈3.6ms、dup_seq=0);`run_load.sh --seed tiny --instances 2 --skip-k6`(A/B 探针均 PASS、dup_seq=0)。SKIP/NOT_RUN:k6 段(本机未装,跳过并写指引)、contract 满量窗口、ladder/60min/2h、双主机 C3
|
||||
@@ -395,3 +395,9 @@
|
||||
"receipt_sha256": "c0078d226bb651f13844084be7dc11df581a150075ea19c4c4fc7d863090535e"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge dec25692c247c8923b0f9396c8f674aefec47521(PR #439)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Add B5 load seed and C2 dual-instance load entry' (#439) from feature/u7-b5-c2-load into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
@@ -348,6 +348,23 @@ Releases 仓跑 `scripts/build-release.sh <v>`。
|
||||
`-dsn` 为空时回退读该环境变量(无 `config explain` 子命令时,svc 以 ELF 内静态
|
||||
引用作能力证据)。
|
||||
|
||||
#### 5.4.1 常用入口 cutover 到 RDM 组合(审计能力)
|
||||
|
||||
增量 2 后,基座 React 前端仅在 `GET /api/commands` 齐备七个 `rdm.inspect.*` 且
|
||||
`enabled` 时展示「追溯审计」。要把常用入口(如 `dev`)切到组合二进制:
|
||||
|
||||
1. **钉 pins**:Agent-System-RDM `pins/` 对齐 Dev 上含能力门控的 Frontend(及兼容
|
||||
Backend/Core/UI),合入 RDM main 后记下 `rdm@<sha>`(`scripts/svc.sh commits rdm`)。
|
||||
2. **备份**:`scripts/svc.sh <name> backup`(SQLite 一致性快照在 `runtime-backups/`)。
|
||||
3. **候选(禁止双写)**:先 `scripts/svc.sh <name> stop`,再
|
||||
`scripts/svc.sh <cand> deploy --from rdm@<sha> --data runtime/<name>/data --port <空闲> --auth on`;
|
||||
验登录 / `/api/projects` / `rdmAuditReady` 后停候选。
|
||||
4. **Cutover**:`scripts/svc.sh <name> deploy --from rdm@<sha>`(同名 redeploy 保留
|
||||
data/auth)。失败回滚:`scripts/svc.sh <name> deploy --from dev@<旧源>`。
|
||||
5. **基座 FE 验收**:`PLAYWRIGHT_API_TARGET=http://127.0.0.1:<port> npm run preview`
|
||||
(或 Vite dev 代理);`svc` static **无** `/api` 反代。RDM `web/` 工作台是另一套 UI
|
||||
(`htdocs` + 伴生 static),与 React SPA 门控并行可用。
|
||||
|
||||
### 5.5 辅助
|
||||
|
||||
```bash
|
||||
|
||||
+1
-1
Submodule modules/backend updated: dcc180dcad...5e8ada727c
+1
-1
Submodule modules/documents updated: f9d96dfc59...5b71525818
+1
-1
Submodule modules/frontend updated: d01a3ebd81...cc233fec90
@@ -78,6 +78,7 @@ cleanup() {
|
||||
[ -n "${UI_PID:-}" ] && kill "$UI_PID" 2>/dev/null || true
|
||||
pkill -f "vite preview --port $UI_PORT" 2>/dev/null || true
|
||||
pkill -f "agent-system-rdm -data.*e2e" 2>/dev/null || true
|
||||
pkill -f "agent-system serve .*$API_PORT" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
(cd "$RDM_WT" && go build -o "$OUT/agent-system-rdm" .) || die "rdm build failed"
|
||||
@@ -220,4 +221,26 @@ set -e
|
||||
cat "$OUT/playwright.log"
|
||||
[ "$PW_EXIT" = 0 ] && log "PASS" || die "playwright exit=$PW_EXIT (log: $OUT/playwright.log, trace: $OUT/frontend/test-results/)"
|
||||
|
||||
# ── 4. 普通基座缺能力场景(增量 2 验收)──────────────────
|
||||
# 同一 $DATA 换基座 CLI 服务(与 RDM 二进制同源同 pin,命令面无 rdm.inspect.*),
|
||||
# preview 不动只换 /api 指向,跑 plain-base spec:审计入口隐藏、代理常驻、
|
||||
# 全程无 rdm.* 命令请求——消除「入口可达、进入即 400」的原缺陷路径。
|
||||
log "plain-base phase: serve base CLI (no RDM commands)"
|
||||
stop_serve
|
||||
"$OUT/agent-system" serve --data "$DATA" --addr "127.0.0.1:$API_PORT" --auth on \
|
||||
>"$OUT/serve-plain.log" 2>&1 &
|
||||
API_PID=$!
|
||||
for i in $(seq 1 40); do
|
||||
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$API_BASE/api/me" || echo 000)
|
||||
[ "$CODE" != "000" ] && break
|
||||
sleep 0.25
|
||||
[ "$i" = 40 ] && { tail -5 "$OUT/serve-plain.log"; die "plain api not ready"; }
|
||||
done
|
||||
set +e
|
||||
E2E_PLAIN_BASE=1 npx playwright test e2e/plain-base.playwright.ts e2e/changepw-form.playwright.ts --reporter=list >"$OUT/playwright-plain.log" 2>&1
|
||||
PW_PLAIN_EXIT=$?
|
||||
set -e
|
||||
cat "$OUT/playwright-plain.log"
|
||||
[ "$PW_PLAIN_EXIT" = 0 ] && log "PASS (plain-base + changepw-form)" || die "plain-base playwright exit=$PW_PLAIN_EXIT (log: $OUT/playwright-plain.log)"
|
||||
|
||||
log "done; artifacts in $OUT"
|
||||
|
||||
@@ -81,30 +81,63 @@ function otherBase(mine) {
|
||||
return BASE_URLS[0] === mine ? BASE_URLS[1] : BASE_URLS[0];
|
||||
}
|
||||
|
||||
const jsonHeaders = { headers: { 'Content-Type': 'application/json' } };
|
||||
|
||||
function parseJSON(res) {
|
||||
try {
|
||||
return res.json();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 与现网一致:管理员建号 → 首登 must_change → POST /api/auth/password → 再登录。
|
||||
// 压测口令保持不变(next=同一密码),只走完设密门,不另造后门清标志。
|
||||
function loginReady(base, owner, password) {
|
||||
const login = http.post(`${base}/api/auth/login`, JSON.stringify({ owner, password }), jsonHeaders);
|
||||
if (login.status !== 200) return login;
|
||||
const body = parseJSON(login);
|
||||
if (body && body.mustChangePassword) {
|
||||
const ch = http.post(`${base}/api/auth/password`, JSON.stringify({ next: password }), jsonHeaders);
|
||||
if (ch.status !== 200) return ch;
|
||||
return http.post(`${base}/api/auth/login`, JSON.stringify({ owner, password }), jsonHeaders);
|
||||
}
|
||||
return login;
|
||||
}
|
||||
|
||||
// ---- setup:管理员建压测用户;双实例时建共享热点会话 ----
|
||||
|
||||
export function setup() {
|
||||
const adminBase = BASE_URLS[0];
|
||||
const login = http.post(`${adminBase}/api/auth/login`, JSON.stringify({ owner: ADMIN_OWNER, password: ADMIN_PASS }), { headers: { 'Content-Type': 'application/json' } });
|
||||
if (login.status !== 200) throw new Error(`admin login: ${login.status} ${login.body}`);
|
||||
const adminLogin = loginReady(adminBase, ADMIN_OWNER, ADMIN_PASS);
|
||||
if (adminLogin.status !== 200) throw new Error(`admin login: ${adminLogin.status} ${adminLogin.body}`);
|
||||
|
||||
const users = [];
|
||||
for (let i = 0; i < USERS; i++) {
|
||||
const owner = `load_u${i}`;
|
||||
const password = `load-user-pass-${i}-Aa9`;
|
||||
const created = http.post(`${adminBase}/api/admin/users`, JSON.stringify({ owner, name: `压测用户${i}`, password, isAdmin: false }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const created = http.post(`${adminBase}/api/admin/users`, JSON.stringify({ owner, name: `压测用户${i}`, password, isAdmin: false }), jsonHeaders);
|
||||
if (created.status !== 200 && created.status !== 409) throw new Error(`create user ${owner}: ${created.status} ${created.body}`);
|
||||
users.push({ owner, password });
|
||||
}
|
||||
// 建号必须保持管理员会话;设密会换 cookie,只能在全员建完后逐个走门。
|
||||
for (const u of users) {
|
||||
const gated = loginReady(adminBase, u.owner, u.password);
|
||||
if (gated.status !== 200) throw new Error(`activate ${u.owner}: ${gated.status} ${gated.body}`);
|
||||
}
|
||||
|
||||
// 热点会话:多 VU 跨实例争抢同一会话(C2「互相不拖累」的争用源)。
|
||||
// 建用户时 cookie 落在最后一位 load_u* 上,热点资源须用管理员会话。
|
||||
const adminAgain = loginReady(adminBase, ADMIN_OWNER, ADMIN_PASS);
|
||||
if (adminAgain.status !== 200) throw new Error(`admin re-login: ${adminAgain.status} ${adminAgain.body}`);
|
||||
let hotspot = null;
|
||||
if (BASE_URLS.length >= 2) {
|
||||
const proj = http.post(`${adminBase}/api/projects`, JSON.stringify({ name: 'hotspot-shared' }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const proj = http.post(`${adminBase}/api/projects`, JSON.stringify({ name: 'hotspot-shared' }), jsonHeaders);
|
||||
if (proj.status !== 200) throw new Error(`hotspot project: ${proj.status} ${proj.body}`);
|
||||
const conv = http.post(`${adminBase}/api/projects/${proj.json().id}/conversations`, JSON.stringify({ title: '热点会话' }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const projBody = parseJSON(proj);
|
||||
const conv = http.post(`${adminBase}/api/projects/${projBody.id}/conversations`, JSON.stringify({ title: '热点会话' }), jsonHeaders);
|
||||
if (conv.status !== 200) throw new Error(`hotspot conv: ${conv.status} ${conv.body}`);
|
||||
hotspot = { projectID: proj.json().id, convID: conv.json().id, owner: ADMIN_OWNER, password: ADMIN_PASS };
|
||||
hotspot = { projectID: projBody.id, convID: parseJSON(conv).id, owner: ADMIN_OWNER, password: ADMIN_PASS };
|
||||
}
|
||||
|
||||
return { users, hotspot, bases: BASE_URLS };
|
||||
@@ -115,16 +148,26 @@ export function setup() {
|
||||
export default function (data) {
|
||||
const base = baseForVU();
|
||||
const u = data.users[exec.scenario.iterationInTest % data.users.length];
|
||||
const login = http.post(`${base}/api/auth/login`, JSON.stringify({ owner: u.owner, password: u.password }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const login = loginReady(base, u.owner, u.password);
|
||||
if (login.status !== 200) {
|
||||
check(null, { 'user login': () => false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!u.__ctx) {
|
||||
const proj = http.post(`${base}/api/projects`, JSON.stringify({ name: `压测-${u.owner}` }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const conv = http.post(`${base}/api/projects/${proj.json().id}/conversations`, JSON.stringify({ title: '压测会话' }), { headers: { 'Content-Type': 'application/json' } });
|
||||
u.__ctx = { projectID: proj.json().id, convID: conv.json().id };
|
||||
const proj = http.post(`${base}/api/projects`, JSON.stringify({ name: `压测-${u.owner}` }), jsonHeaders);
|
||||
const projBody = parseJSON(proj);
|
||||
if (proj.status !== 200 || !projBody || !projBody.id) {
|
||||
check(proj, { 'create project 2xx': () => false });
|
||||
return;
|
||||
}
|
||||
const conv = http.post(`${base}/api/projects/${projBody.id}/conversations`, JSON.stringify({ title: '压测会话' }), jsonHeaders);
|
||||
const convBody = parseJSON(conv);
|
||||
if (conv.status !== 200 || !convBody || !convBody.id) {
|
||||
check(conv, { 'create conversation 2xx': () => false });
|
||||
return;
|
||||
}
|
||||
u.__ctx = { projectID: projBody.id, convID: convBody.id };
|
||||
}
|
||||
const { projectID, convID } = u.__ctx;
|
||||
|
||||
@@ -143,7 +186,7 @@ export default function (data) {
|
||||
if (BASE_URLS.length >= 2 && exec.scenario.iterationInTest % 5 === 0) {
|
||||
const other = otherBase(base);
|
||||
// 另一实例需同用户登录(cookie jar 按 VU;换 host 需重新登录)。
|
||||
const loginB = http.post(`${other}/api/auth/login`, JSON.stringify({ owner: u.owner, password: u.password }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const loginB = loginReady(other, u.owner, u.password);
|
||||
if (loginB.status === 200) {
|
||||
const cross = http.get(`${other}/api/conversations/${convID}`);
|
||||
if (cross.status === 200) crossReadOk.add(1);
|
||||
@@ -158,7 +201,7 @@ export default function (data) {
|
||||
if (data.hotspot && exec.scenario.iterationInTest % 5 === 0) {
|
||||
targetConv = data.hotspot.convID;
|
||||
// 热点会话属 admin:先以 admin 登录再写。
|
||||
const hl = http.post(`${writeBase}/api/auth/login`, JSON.stringify({ owner: data.hotspot.owner, password: data.hotspot.password }), { headers: { 'Content-Type': 'application/json' } });
|
||||
const hl = loginReady(writeBase, data.hotspot.owner, data.hotspot.password);
|
||||
if (hl.status !== 200) {
|
||||
check(null, { 'hotspot admin login': () => false });
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user