draftgo-cli 4.0.22 → 4.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/draftgo.js +8 -8
- package/package.json +72 -72
- package/resources/custom-service-sdk/auth_test.go +56 -0
- package/resources/custom-service-sdk/manifest.json +14 -9
- package/resources/custom-service-sdk/platform.go +19 -27
- package/resources/custom-service-sdk/resources.go +1 -0
- package/resources/custom-service-sdk/resources_scope_test.go +10 -5
- package/resources/custom-service-sdk/sdk.go +6 -5
- package/resources/skill/SKILL.md +1 -1
- package/resources/skill/manifest.json +1 -1
- package/resources/skill/references/aihub.md +74 -74
- package/resources/skill/references/app-api.md +78 -78
- package/resources/skill/references/architecture.md +40 -40
- package/resources/skill/references/checkout.md +105 -105
- package/resources/skill/references/custom-services.md +6 -6
- package/resources/skill/references/data.md +168 -168
- package/resources/skill/references/methods.md +3 -0
- package/resources/skill/references/modules.md +48 -48
- package/resources/skill/references/runtime.md +95 -96
- package/resources/skill/story/SKILL.md +264 -264
- package/src/commands/help.js +72 -72
- package/src/commands/listTargets.js +12 -12
- package/src/commands/status.js +2 -2
- package/src/commands/uninstall.js +45 -45
- package/src/commands/update.js +20 -20
- package/src/customServices.js +5 -4
- package/src/detect.js +14 -14
- package/src/fsx.js +67 -67
- package/src/index.js +25 -25
- package/src/localRuntime/detect.js +76 -76
- package/src/localRuntime/mysqlClient.js +138 -138
- package/src/logger.js +37 -37
- package/src/mcp/client.js +586 -595
- package/src/mcp/hosts.js +520 -520
- package/src/mcp/protocol.js +184 -164
- package/src/prompt.js +94 -94
- package/src/updateCheck.js +16 -16
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ draftgo test custom-services <id> --source draft --handler route:POST:/path --in
|
|
|
134
134
|
|
|
135
135
|
`commit custom-services` 会自动完成提交、验证和发布,并输出明确的 published 提示;`validate`、`test`、`publish` 仍可作为单独的兼容入口。`test` 只运行 cloud draft,不调用线上版本;发布后如需再试运行,先修改并提交形成新草稿。默认拒绝外部副作用;外部调用用 `--side-effect-policy mock` 模拟,只有明确需要真实副作用时才组合 `--side-effect-policy live --test-write`。数据库等写操作也必须显式加 `--test-write`。
|
|
136
136
|
|
|
137
|
-
`--source` 支持 `draft`(默认)、`auto`、`published`。Selector 支持 `route:METHOD:/path`、`event:name`、`scheduled:name` 或 handler 名;`--input`、`--headers`、`--user` 都读取 UTF-8 JSON object。普通 SDK 调用继承已验证的 `platform` 或 `space`
|
|
137
|
+
`--source` 支持 `draft`(默认)、`auto`、`published`。Selector 支持 `route:METHOD:/path`、`event:name`、`scheduled:name` 或 handler 名;`--input`、`--headers`、`--user` 都读取 UTF-8 JSON object。普通 SDK 调用继承已验证的 `platform` 或 `space` 上下文;自定义服务可对单次操作显式使用 `ctx.Admin.*`,该次调用按系统内置 `*:*:all` 全权限执行、进入审计且不会泄漏到后续调用。余额扣款、权益、支付和订阅使用 `ctx.Billing` / `ctx.Admin.Billing`,精确签名查看 checkout 后只读的 `.draftgo-sdk/billing.go`。完整字段与规则见[自定义服务方法指南](resources/skill/references/custom-services.md)。
|
|
138
138
|
|
|
139
139
|
### MCP/API
|
|
140
140
|
|
|
@@ -172,7 +172,7 @@ draftgo data-range list
|
|
|
172
172
|
`role` 管理无作用域的 Role 模板;授权使用 `grant` 创建带 `platform` 或 `space` 范围的 AccessGrant。工作区成员关系不直接授予权限。资源归属由服务端持久化 ResourceOwnership 决定,DB 的 DataRange
|
|
173
173
|
只在该范围内应用 `none`、`owner` 或 `all` 记录策略。交互式 CLI、MCP 和普通 HTTP API 均使用当前用户的
|
|
174
174
|
API Key,并始终按该用户的 AccessGrant 授权;API Key 不能创建或伪造服务身份。CI、定时任务和共享服务等
|
|
175
|
-
|
|
175
|
+
无人值守自动化由运行时使用 system 身份;定时和事件处理器没有调用者,必须在代码中显式使用 `ctx.Admin.*`,不创建服务主体 AccessGrant,也不复用个人 API Key。
|
|
176
176
|
|
|
177
177
|
### 交付与记录
|
|
178
178
|
|
package/bin/draftgo.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
Promise.resolve(require('../src/index.js').run(process.argv.slice(2)))
|
|
5
|
-
.then((code) => { process.exitCode = Number(code) || 0; })
|
|
6
|
-
.catch((err) => {
|
|
7
|
-
console.error(err && err.stack ? err.stack : String(err));
|
|
8
|
-
process.exitCode = 1;
|
|
9
|
-
});
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Promise.resolve(require('../src/index.js').run(process.argv.slice(2)))
|
|
5
|
+
.then((code) => { process.exitCode = Number(code) || 0; })
|
|
6
|
+
.catch((err) => {
|
|
7
|
+
console.error(err && err.stack ? err.stack : String(err));
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
});
|
package/package.json
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "draftgo-cli",
|
|
3
|
-
"version": "4.0.
|
|
4
|
-
"description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro, Pi).",
|
|
5
|
-
"bin": {
|
|
6
|
-
"draftgo": "bin/draftgo.js"
|
|
7
|
-
},
|
|
8
|
-
"main": "src/index.js",
|
|
9
|
-
"engines": {
|
|
10
|
-
"node": ">=20.19"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"bin/",
|
|
14
|
-
"src/",
|
|
15
|
-
"resources/",
|
|
16
|
-
"!resources/**/__pycache__/**",
|
|
17
|
-
"!resources/**/*.py[cod]",
|
|
18
|
-
"LICENSE",
|
|
19
|
-
"README.md"
|
|
20
|
-
],
|
|
21
|
-
"keywords": [
|
|
22
|
-
"draftgo",
|
|
23
|
-
"skill",
|
|
24
|
-
"cli",
|
|
25
|
-
"claude-code",
|
|
26
|
-
"codex",
|
|
27
|
-
"cursor",
|
|
28
|
-
"windsurf",
|
|
29
|
-
"antigravity",
|
|
30
|
-
"copilot",
|
|
31
|
-
"gemini",
|
|
32
|
-
"kiro",
|
|
33
|
-
"pi"
|
|
34
|
-
],
|
|
35
|
-
"author": "draftgo <cabinai@163.com> (https://github.com/draftgo)",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"homepage": "https://github.com/draftgo/draftgo-cli#readme",
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/draftgo/draftgo-cli/issues"
|
|
40
|
-
},
|
|
41
|
-
"repository": {
|
|
42
|
-
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/draftgo/draftgo-cli.git"
|
|
44
|
-
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"lint": "node scripts/check-syntax.js",
|
|
47
|
-
"validate:skill": "node scripts/validate-skill.js",
|
|
48
|
-
"sync:custom-service-sdk": "node scripts/sync-custom-service-sdk.js",
|
|
49
|
-
"build:release": "node scripts/build-release.js",
|
|
50
|
-
"verify:package": "node scripts/verify-package.js",
|
|
51
|
-
"test": "npm run lint && npm run validate:skill && npm run verify:package && npm run test:unit && npm run test:components && npm run test:capabilities && npm run test:workflow && npm run test:worklog && npm run test:mcp && npm run test:worktree && npm run test:integration && npm run test:local && npm run test:e2e",
|
|
52
|
-
"test:unit": "node tests/unit.js",
|
|
53
|
-
"test:components": "node --test tests/components.test.js",
|
|
54
|
-
"test:capabilities": "node --test tests/capabilities.test.js",
|
|
55
|
-
"test:workflow": "node tests/workflow.test.js",
|
|
56
|
-
"test:worklog": "node tests/worklog.test.js",
|
|
57
|
-
"test:mcp": "node tests/mcp.test.js",
|
|
58
|
-
"test:worktree": "node tests/worktree.test.js",
|
|
59
|
-
"test:integration": "node tests/integration.test.js",
|
|
60
|
-
"test:local": "node tests/local-runtime.js",
|
|
61
|
-
"test:e2e": "node tests/e2e.js"
|
|
62
|
-
},
|
|
63
|
-
"dependencies": {
|
|
64
|
-
"@grpc/grpc-js": "1.14.1",
|
|
65
|
-
"adm-zip": "0.5.16",
|
|
66
|
-
"parse5": "6.0.1",
|
|
67
|
-
"playwright-core": "1.61.1"
|
|
68
|
-
},
|
|
69
|
-
"directories": {
|
|
70
|
-
"test": "tests"
|
|
71
|
-
}
|
|
72
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "draftgo-cli",
|
|
3
|
+
"version": "4.0.24",
|
|
4
|
+
"description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro, Pi).",
|
|
5
|
+
"bin": {
|
|
6
|
+
"draftgo": "bin/draftgo.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.19"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"resources/",
|
|
16
|
+
"!resources/**/__pycache__/**",
|
|
17
|
+
"!resources/**/*.py[cod]",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"draftgo",
|
|
23
|
+
"skill",
|
|
24
|
+
"cli",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"codex",
|
|
27
|
+
"cursor",
|
|
28
|
+
"windsurf",
|
|
29
|
+
"antigravity",
|
|
30
|
+
"copilot",
|
|
31
|
+
"gemini",
|
|
32
|
+
"kiro",
|
|
33
|
+
"pi"
|
|
34
|
+
],
|
|
35
|
+
"author": "draftgo <cabinai@163.com> (https://github.com/draftgo)",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"homepage": "https://github.com/draftgo/draftgo-cli#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/draftgo/draftgo-cli/issues"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/draftgo/draftgo-cli.git"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"lint": "node scripts/check-syntax.js",
|
|
47
|
+
"validate:skill": "node scripts/validate-skill.js",
|
|
48
|
+
"sync:custom-service-sdk": "node scripts/sync-custom-service-sdk.js",
|
|
49
|
+
"build:release": "node scripts/build-release.js",
|
|
50
|
+
"verify:package": "node scripts/verify-package.js",
|
|
51
|
+
"test": "npm run lint && npm run validate:skill && npm run verify:package && npm run test:unit && npm run test:components && npm run test:capabilities && npm run test:workflow && npm run test:worklog && npm run test:mcp && npm run test:worktree && npm run test:integration && npm run test:local && npm run test:e2e",
|
|
52
|
+
"test:unit": "node tests/unit.js",
|
|
53
|
+
"test:components": "node --test tests/components.test.js",
|
|
54
|
+
"test:capabilities": "node --test tests/capabilities.test.js",
|
|
55
|
+
"test:workflow": "node tests/workflow.test.js",
|
|
56
|
+
"test:worklog": "node tests/worklog.test.js",
|
|
57
|
+
"test:mcp": "node tests/mcp.test.js",
|
|
58
|
+
"test:worktree": "node tests/worktree.test.js",
|
|
59
|
+
"test:integration": "node tests/integration.test.js",
|
|
60
|
+
"test:local": "node tests/local-runtime.js",
|
|
61
|
+
"test:e2e": "node tests/e2e.js"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@grpc/grpc-js": "1.14.1",
|
|
65
|
+
"adm-zip": "0.5.16",
|
|
66
|
+
"parse5": "6.0.1",
|
|
67
|
+
"playwright-core": "1.61.1"
|
|
68
|
+
},
|
|
69
|
+
"directories": {
|
|
70
|
+
"test": "tests"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package sdk
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type authRecordingClient struct {
|
|
10
|
+
operation string
|
|
11
|
+
args map[string]any
|
|
12
|
+
err error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func (client *authRecordingClient) Call(_ context.Context, operation string, args any) (any, error) {
|
|
16
|
+
client.operation = operation
|
|
17
|
+
client.args, _ = args.(map[string]any)
|
|
18
|
+
return true, client.err
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func TestAuthAssertionsUseHostAuthorization(t *testing.T) {
|
|
22
|
+
client := &authRecordingClient{}
|
|
23
|
+
draftgo := NewContext(context.Background(), nil, map[string]any{
|
|
24
|
+
"id": float64(7), "is_admin": false, "role_code": "member",
|
|
25
|
+
}, nil, client)
|
|
26
|
+
|
|
27
|
+
if err := draftgo.Auth.RequireAdmin(); err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
if client.operation != "auth.require_admin" {
|
|
31
|
+
t.Fatalf("operation = %q", client.operation)
|
|
32
|
+
}
|
|
33
|
+
if err := draftgo.Auth.RequireRole(" workspace_owner "); err != nil {
|
|
34
|
+
t.Fatal(err)
|
|
35
|
+
}
|
|
36
|
+
if client.operation != "auth.require_role" || client.args["role"] != "workspace_owner" {
|
|
37
|
+
t.Fatalf("operation=%q args=%#v", client.operation, client.args)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
client.err = errors.New("denied by host")
|
|
41
|
+
if err := draftgo.Auth.RequireAdmin(); err == nil || err.Error() != "require_admin failed: denied by host" {
|
|
42
|
+
t.Fatalf("host denial was not returned: %v", err)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func TestAdminAuthCallCarriesScopedElevation(t *testing.T) {
|
|
47
|
+
client := &authRecordingClient{}
|
|
48
|
+
draftgo := NewContext(context.Background(), nil, nil, nil, client)
|
|
49
|
+
|
|
50
|
+
if err := draftgo.Admin.Auth.RequireAdmin(); err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
if client.operation != "auth.require_admin" || client.args["__draftgo_admin"] != true {
|
|
54
|
+
t.Fatalf("operation=%q args=%#v", client.operation, client.args)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"module": "draftgo/sdk",
|
|
4
|
-
"fingerprint": "
|
|
4
|
+
"fingerprint": "4d0fc5d9b9dd8e0f3332fc1f61c544579d1d0bd1c96b39bc1a25006bb172bcc3",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "ai.go",
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
"sha256": "d5a906ea3d9d0451f116d2acac54bfb83669e5e471d1d5ada7c0e1fdabef13b6",
|
|
14
14
|
"bytes": 6531
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
"path": "auth_test.go",
|
|
18
|
+
"sha256": "2905126322a678df82289ebf13c15f6595fb8def2f98cbd5bb53c406bf5ac31c",
|
|
19
|
+
"bytes": 1626
|
|
20
|
+
},
|
|
16
21
|
{
|
|
17
22
|
"path": "billing.go",
|
|
18
23
|
"sha256": "9ce87c80885aa8b8996710622ec0f14cc7a74d3ca78e5ed4168b6c135994d583",
|
|
@@ -30,8 +35,8 @@
|
|
|
30
35
|
},
|
|
31
36
|
{
|
|
32
37
|
"path": "platform.go",
|
|
33
|
-
"sha256": "
|
|
34
|
-
"bytes":
|
|
38
|
+
"sha256": "cc696f69c3a8efe093968ea4f6bfea1f257944ebcd83591b5ffd75ae85a8b583",
|
|
39
|
+
"bytes": 12182
|
|
35
40
|
},
|
|
36
41
|
{
|
|
37
42
|
"path": "platform_logger_test.go",
|
|
@@ -45,8 +50,8 @@
|
|
|
45
50
|
},
|
|
46
51
|
{
|
|
47
52
|
"path": "resources.go",
|
|
48
|
-
"sha256": "
|
|
49
|
-
"bytes":
|
|
53
|
+
"sha256": "78f465a598ace6dc2858fea4a448488d195a5d1a8dd3452465d73ce9727b6a72",
|
|
54
|
+
"bytes": 9761
|
|
50
55
|
},
|
|
51
56
|
{
|
|
52
57
|
"path": "resources_billing_test.go",
|
|
@@ -60,13 +65,13 @@
|
|
|
60
65
|
},
|
|
61
66
|
{
|
|
62
67
|
"path": "resources_scope_test.go",
|
|
63
|
-
"sha256": "
|
|
64
|
-
"bytes":
|
|
68
|
+
"sha256": "f9b19b592c0b2ddc0564715828fd840c974216f8e99e9690af43f2cf4a0d7fb5",
|
|
69
|
+
"bytes": 3870
|
|
65
70
|
},
|
|
66
71
|
{
|
|
67
72
|
"path": "sdk.go",
|
|
68
|
-
"sha256": "
|
|
69
|
-
"bytes":
|
|
73
|
+
"sha256": "18872d1f8535cdba53ddb3f487792cf74f10035f22a3023ec5cff3c3e597a8c0",
|
|
74
|
+
"bytes": 8342
|
|
70
75
|
}
|
|
71
76
|
]
|
|
72
77
|
}
|
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"errors"
|
|
7
7
|
"fmt"
|
|
8
8
|
"net/http"
|
|
9
|
+
"strings"
|
|
9
10
|
"sync"
|
|
10
11
|
"time"
|
|
11
12
|
)
|
|
@@ -320,41 +321,32 @@ func asInt(value any) int {
|
|
|
320
321
|
}
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
type authClient struct{
|
|
324
|
+
type authClient struct {
|
|
325
|
+
platformClient
|
|
326
|
+
user map[string]any
|
|
327
|
+
}
|
|
324
328
|
|
|
325
329
|
func (a authClient) CurrentUser() map[string]any { return a.user }
|
|
326
330
|
func (a authClient) RequireLogin() error {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
return nil
|
|
331
|
+
_, err := a.call(a.context, "auth.require_login", map[string]any{})
|
|
332
|
+
return authAssertionError("require_login", err)
|
|
331
333
|
}
|
|
332
334
|
func (a authClient) RequireAdmin() error {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
if a.user["is_admin"] == true || a.user["role_code"] == "admin" {
|
|
337
|
-
return nil
|
|
338
|
-
}
|
|
339
|
-
for _, role := range asSlice(a.user["roles"]) {
|
|
340
|
-
if item, ok := role.(map[string]any); ok && item["code"] == "admin" {
|
|
341
|
-
return nil
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return errors.New("require_admin failed: user is not an administrator")
|
|
335
|
+
_, err := a.call(a.context, "auth.require_admin", map[string]any{})
|
|
336
|
+
return authAssertionError("require_admin", err)
|
|
345
337
|
}
|
|
346
338
|
func (a authClient) RequireRole(role string) error {
|
|
347
|
-
|
|
348
|
-
|
|
339
|
+
role = strings.TrimSpace(role)
|
|
340
|
+
if role == "" {
|
|
341
|
+
return errors.New("require_role failed: role is required")
|
|
349
342
|
}
|
|
350
|
-
|
|
343
|
+
_, err := a.call(a.context, "auth.require_role", map[string]any{"role": role})
|
|
344
|
+
return authAssertionError("require_role", err)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func authAssertionError(assertion string, err error) error {
|
|
348
|
+
if err == nil {
|
|
351
349
|
return nil
|
|
352
350
|
}
|
|
353
|
-
|
|
354
|
-
if item, ok := raw.(map[string]any); ok && item["code"] == role {
|
|
355
|
-
return nil
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return fmt.Errorf("require_role failed: missing role %q", role)
|
|
351
|
+
return fmt.Errorf("%s failed: %w", assertion, err)
|
|
359
352
|
}
|
|
360
|
-
func asSlice(value any) []any { items, _ := value.([]any); return items }
|
|
@@ -94,6 +94,7 @@ type ResourceContext struct {
|
|
|
94
94
|
ScopeType string `json:"scope_type"`
|
|
95
95
|
WorkspaceID int64 `json:"workspace_id,omitempty"`
|
|
96
96
|
SpaceID int64 `json:"space_id,omitempty"`
|
|
97
|
+
OwnerUserID int64 `json:"owner_user_id,omitempty"`
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// ResourceOwnership identifies the persisted authorization boundary of a
|
|
@@ -16,7 +16,7 @@ func (client *scopeRecordingClient) Call(_ context.Context, operation string, ar
|
|
|
16
16
|
case "resource.context.current":
|
|
17
17
|
return map[string]any{"scope_type": ScopeSpace, "workspace_id": float64(2), "space_id": float64(8)}, nil
|
|
18
18
|
case "scope.principal.current":
|
|
19
|
-
return map[string]any{"subject_type": "
|
|
19
|
+
return map[string]any{"subject_type": "system", "subject_id": "0", "username": "System", "is_admin": true}, nil
|
|
20
20
|
default:
|
|
21
21
|
return map[string]any{"ok": true}, nil
|
|
22
22
|
}
|
|
@@ -38,7 +38,7 @@ func TestScopeClientUsesResourceAndPermissionOperations(t *testing.T) {
|
|
|
38
38
|
if err != nil || recorder.operation != "scope.principal.current" {
|
|
39
39
|
t.Fatalf("principal operation=%q err=%v", recorder.operation, err)
|
|
40
40
|
}
|
|
41
|
-
if principal.SubjectType != "
|
|
41
|
+
if principal.SubjectType != "system" || principal.SubjectID != "0" || principal.Username != "System" || !principal.IsAdmin {
|
|
42
42
|
t.Fatalf("principal=%#v", principal)
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -75,13 +75,18 @@ func TestContextCarriesPersistedResourceAndPrincipal(t *testing.T) {
|
|
|
75
75
|
recorder := &scopeRecordingClient{}
|
|
76
76
|
ctx := NewContextWithResource(context.Background(), nil, nil, nil, recorder,
|
|
77
77
|
ResourceContext{ScopeType: ScopeSpace, WorkspaceID: 11, SpaceID: 13},
|
|
78
|
-
ScopePrincipal{SubjectType: "
|
|
79
|
-
if ctx.Platform || ctx.WorkspaceID != 11 || ctx.SpaceID != 13 || ctx.Principal.SubjectID != "
|
|
78
|
+
ScopePrincipal{SubjectType: "system", SubjectID: "0", Username: "System"})
|
|
79
|
+
if ctx.Platform || ctx.WorkspaceID != 11 || ctx.SpaceID != 13 || ctx.Principal.SubjectID != "0" {
|
|
80
80
|
t.Fatalf("runtime context = %+v", ctx)
|
|
81
81
|
}
|
|
82
82
|
platform := NewContextWithResource(context.Background(), nil, nil, nil, recorder,
|
|
83
|
-
ResourceContext{ScopeType: ScopePlatform}, ScopePrincipal{SubjectType: "
|
|
83
|
+
ResourceContext{ScopeType: ScopePlatform}, ScopePrincipal{SubjectType: "system", SubjectID: "0"})
|
|
84
84
|
if !platform.Platform || platform.WorkspaceID != 0 || platform.SpaceID != 0 {
|
|
85
85
|
t.Fatalf("platform runtime context = %+v", platform)
|
|
86
86
|
}
|
|
87
|
+
personal := NewContextWithResource(context.Background(), nil, nil, nil, recorder,
|
|
88
|
+
ResourceContext{ScopeType: "user", OwnerUserID: 17}, ScopePrincipal{SubjectType: "system", SubjectID: "0"})
|
|
89
|
+
if personal.Platform || personal.OwnerUserID != 17 || personal.WorkspaceID != 0 || personal.SpaceID != 0 {
|
|
90
|
+
t.Fatalf("personal runtime context = %+v", personal)
|
|
91
|
+
}
|
|
87
92
|
}
|
|
@@ -106,6 +106,7 @@ type Context struct {
|
|
|
106
106
|
Platform bool `json:"platform"`
|
|
107
107
|
WorkspaceID int64 `json:"workspace_id,omitempty"`
|
|
108
108
|
SpaceID int64 `json:"space_id,omitempty"`
|
|
109
|
+
OwnerUserID int64 `json:"owner_user_id,omitempty"`
|
|
109
110
|
Principal ScopePrincipal `json:"principal"`
|
|
110
111
|
|
|
111
112
|
DB Database `json:"-"`
|
|
@@ -137,7 +138,7 @@ func NewContext(draftgo context.Context, input, user map[string]any, headers htt
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
// NewContextWithResource constructs a handler context with persisted resource
|
|
140
|
-
// ownership and the
|
|
141
|
+
// ownership and the verified caller/system principal supplied by DraftGo. Custom services
|
|
141
142
|
// cannot override these values through Input or request headers.
|
|
142
143
|
func NewContextWithResource(draftgo context.Context, input, user map[string]any, headers http.Header, client Client, resource ResourceContext, principal ScopePrincipal) *Context {
|
|
143
144
|
if draftgo == nil {
|
|
@@ -150,12 +151,12 @@ func NewContextWithResource(draftgo context.Context, input, user map[string]any,
|
|
|
150
151
|
adminPlatform := platformClient{client: client, context: draftgo, admin: true}
|
|
151
152
|
return &Context{
|
|
152
153
|
Input: input, User: user, Headers: headers, context: draftgo,
|
|
153
|
-
Platform: resource.ScopeType == ScopePlatform, WorkspaceID: resource.WorkspaceID, SpaceID: resource.SpaceID, Principal: principal,
|
|
154
|
-
DB: dbClient{platform}, Users: usersClient{platform}, Auth: authClient{user: user}, Notify: notifierClient{platform},
|
|
154
|
+
Platform: resource.ScopeType == ScopePlatform, WorkspaceID: resource.WorkspaceID, SpaceID: resource.SpaceID, OwnerUserID: resource.OwnerUserID, Principal: principal,
|
|
155
|
+
DB: dbClient{platform}, Users: usersClient{platform}, Auth: authClient{platformClient: platform, user: user}, Notify: notifierClient{platform},
|
|
155
156
|
HTTP: httpClient{platform}, Cache: cacheClient{platform}, Config: configClient{platform}, AIHub: aiHubClient{platform},
|
|
156
157
|
Knowledge: knowledgeClient{platform}, Memory: memoryClient{platform}, Files: fileStoreClient{platform}, Scope: scopeClient{platform}, Billing: billingClient{platform},
|
|
157
158
|
Admin: Admin{
|
|
158
|
-
DB: dbClient{adminPlatform}, Users: usersClient{adminPlatform}, Auth: authClient{user: user}, Notify: notifierClient{adminPlatform},
|
|
159
|
+
DB: dbClient{adminPlatform}, Users: usersClient{adminPlatform}, Auth: authClient{platformClient: adminPlatform, user: user}, Notify: notifierClient{adminPlatform},
|
|
159
160
|
HTTP: httpClient{adminPlatform}, Cache: cacheClient{adminPlatform}, Config: configClient{adminPlatform}, AIHub: aiHubClient{adminPlatform},
|
|
160
161
|
Knowledge: knowledgeClient{adminPlatform}, Memory: memoryClient{adminPlatform},
|
|
161
162
|
Files: fileStoreClient{adminPlatform}, Scope: scopeClient{adminPlatform}, Billing: billingClient{adminPlatform},
|
|
@@ -165,7 +166,7 @@ func NewContextWithResource(draftgo context.Context, input, user map[string]any,
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
// Admin exposes explicitly elevated platform capabilities to trusted custom
|
|
168
|
-
// services. The runtime
|
|
169
|
+
// services. The runtime uses a system principal (actor_id=0), scopes elevation to one
|
|
169
170
|
// audited RPC call, and never exposes platform credentials.
|
|
170
171
|
type Admin struct {
|
|
171
172
|
DB Database
|
package/resources/skill/SKILL.md
CHANGED
|
@@ -47,7 +47,7 @@ description: Use this skill to inspect, develop, debug, or deliver a DraftGo app
|
|
|
47
47
|
- 同一文件或 DraftGo 资源全程只能由一个 Agent 修改;不同资源、operation 和 owner 不冲突的单元可以并发,不设置客户端并发上限;同一资源的依赖步骤保持串行。主 Agent 负责 owner 分配、汇总、验证、交付和 worklog 状态。
|
|
48
48
|
- 用户 API Key 只保存在 `.draftgo/config.json`,不得进入宿主配置、命令参数、Skill、日志、manifest 或错误消息。宿主 MCP 配置只运行 `draftgo mcp serve`。
|
|
49
49
|
- 页面优先使用可信本地资源;禁止境外 CDN。净化不可信 HTML,关键状态不能只靠颜色或动效表达。页面通过 `window.parent.App` 使用认证、权限、反馈和主题;详细规则见前端与运行时 Reference。
|
|
50
|
-
- 普通自定义服务 SDK
|
|
50
|
+
- Route 普通自定义服务 SDK 调用继承真实调用者和服务归属;Event/Scheduled 没有调用者,普通 `ctx.xxx` 返回 403,必须显式使用 `ctx.Admin.*`。Admin RPC 使用 system actor_id=0,按 `*:*:all` 单次执行并进入审计,不创建服务主体 AccessGrant,也不会泄漏到后续调用。
|
|
51
51
|
- `.draftgo/tmp/` 可清理;先运行 `draftgo clean --dry-run`,再在需要时使用 `--yes`。用户可查验的证据放入已注册的 `.draftgo/artifacts/`。
|
|
52
52
|
|
|
53
53
|
## 完成条件
|