Merge authoritative main into the task branch

Why: include the current integration baseline before verification.

Evidence: fetched origin/main=63e70d8b91ccb1691ce9b78a373e4e8bc9da24a2.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync

# Conflicts:
#	modules/backend
This commit is contained in:
2026-10-15 03:56:33 +00:00
4 changed files with 512 additions and 20 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -40,16 +40,16 @@ import (
const (
aliceOwner = "mi_alice"
alicePass0 = "mi-alice-pass-0"
alicePass = "mi-alice-pass-1"
httpWait = 15 * time.Second
)
type instance struct {
name string
base string
cmd *exec.Cmd
log *lockedBuffer
name string
base string
dataDir string // serve 的 cwd:本地实例身份(manifest)所在,账户 CLI 须以此身份运行
cmd *exec.Cmd
log *lockedBuffer
}
type lockedBuffer struct {
@@ -312,6 +312,20 @@ func (e *env) startPair() {
e.t.Helper()
e.a = e.startInstance(e.t, "miA", "127.0.0.1:"+freePort(e.t))
e.b = e.startInstance(e.t, "miB", "127.0.0.1:"+freePort(e.t))
// 库内服务身份是显式引导(svc init 同款),且为部署级单行:双进程共享
// 同一身份,只对 A ensure 一次。账户 CLI 的审计与核验都依赖它。
e.identityEnsure(e.t, e.a)
}
// identityEnsure 建立/对齐库内服务身份行(agent_system_identity)。
func (e *env) identityEnsure(t *testing.T, inst *instance) {
t.Helper()
cmd := exec.Command(e.bin, "identity", "ensure", "--instance-id", inst.name, "--data", inst.dataDir, "--dbref", "pg")
cmd.Dir = inst.dataDir
cmd.Env = append(os.Environ(), "AGENT_SYSTEM_DSN="+e.dbDSN)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("identity ensure %s: %v\n%s", inst.name, err, out)
}
}
func (e *env) startInstance(t *testing.T, name, addr string) *instance {
@@ -332,11 +346,12 @@ func (e *env) startInstance(t *testing.T, name, addr string) *instance {
// 否则测不到带类型空 *S3 回落本地目录的路径。
cmd.Env = withoutBlobstoreS3(os.Environ())
// 独立 cwd:serve 会向 ./data 写 skills/SQLite 等,不能落在测试目录。
cmd.Dir = t.TempDir()
dataDir := t.TempDir()
cmd.Dir = dataDir
if err := cmd.Start(); err != nil {
t.Fatalf("start %s: %v", name, err)
}
inst := &instance{name: name, base: "http://" + addr, cmd: cmd, log: log}
inst := &instance{name: name, base: "http://" + addr, dataDir: dataDir, cmd: cmd, log: log}
t.Cleanup(func() { e.stopInstance(inst) })
e.waitHealthy(t, inst)
return inst
@@ -435,29 +450,35 @@ func (e *env) killNine(t *testing.T, inst *instance) {
// loginAlice 引导 alice 账号并发放 API key(CLI 直连测试库;key 存共享 PG,
// 对两实例同时有效——程序化验收的首选凭证,SSE 亦可用 ?key= 兜底)。
// 建号后 must_change=1:API Key 在改密完成前不能走业务接口(login_keys 门禁),
// 故先经会话完成 alicePass0→alicePass,再发 Key。
// U3 后本地无密码创建:user create 只签发待激活账号的一次性激活 Key(凭据
// 独占落 JSON),redeem 会话进入 grant-setup 改密设 alicePass,完成后再发 Key。
func (e *env) loginAlice(t *testing.T) *userClient {
t.Helper()
e.cli(t, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--password", alicePass0)
// 空库先引导首个管理员(本地 pending 创建要求库内已有用户),再建 alice。
// 两步都签发一次性凭据,独占落临时 JSON 后即弃。
rootCred := filepath.Join(t.TempDir(), "root-cred.json")
e.cliIn(t, e.a.dataDir, "user", "bootstrap-admin", "--dsn", e.dbDSN, "--owner", "mi-root", "--out-file", rootCred)
activation := filepath.Join(t.TempDir(), "activation.json")
e.cliIn(t, e.a.dataDir, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--out", activation)
activateKey := activationSecret(t, activation)
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
sess := &http.Client{Timeout: httpWait, Jar: jar}
// e.uc 尚未赋值 → baseFor 走 A;会话 cookie 落 jar 后改密清 must_change。
e.do(t, sess, "POST", "/api/auth/login", map[string]any{
"owner": aliceOwner, "password": alicePass0,
// e.uc 尚未赋值 → baseFor 走 A;redeem 会话(grant-setup)直接设 alicePass。
e.do(t, sess, "POST", "/api/auth/login-key/redeem", map[string]any{
"key": activateKey,
}, nil)
e.do(t, sess, "POST", "/api/auth/password", map[string]any{
"current": alicePass0, "next": alicePass,
"next": alicePass,
}, nil)
key := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
apiKey := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
uc := &userClient{
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
streamB: &http.Client{Transport: bearerRT{key: key}}, // SSE 无整体超时;chan 背压控制
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
streamB: &http.Client{Transport: bearerRT{key: apiKey}}, // SSE 无整体超时;chan 背压控制
}
// runtime.mock 自系统受管设置体制(backend #188/#190)起由 --mock 播种进
// 系统层,经 Effective 链对所有租户生效(selectConversationRunner 按
@@ -474,6 +495,22 @@ func (b bearerRT) RoundTrip(r *http.Request) (*http.Response, error) {
return http.DefaultTransport.RoundTrip(r2)
}
// activationSecret 读 CLI 凭据独占文件里的激活 Key(JSON {"kind","owner","secret"})。
func activationSecret(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var v struct {
Secret string `json:"secret"`
}
if err := json.Unmarshal(b, &v); err != nil || v.Secret == "" {
t.Fatalf("activation credential unreadable: %v", err)
}
return v.Secret
}
// cliOut 运行 CLI 并取 stdout 首行(apikey create 的明文仅此一次输出)。
func (e *env) cliOut(t *testing.T, args ...string) string {
t.Helper()
@@ -492,9 +529,16 @@ func (e *env) cliOut(t *testing.T, args ...string) string {
}
func (e *env) cli(t *testing.T, args ...string) {
t.Helper()
e.cliIn(t, t.TempDir(), args...)
}
// cliIn 以指定 cwd 运行 CLI:账户命令经 assertLocalAccountOwner 核验本地实例
// 身份,必须从持有该身份的 dataDir 运行。
func (e *env) cliIn(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command(e.bin, args...)
cmd.Dir = t.TempDir()
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cli %v…: %v\n%s", args[:2], err, out)