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.
Files changed (38) hide show
  1. package/README.md +2 -2
  2. package/bin/draftgo.js +8 -8
  3. package/package.json +72 -72
  4. package/resources/custom-service-sdk/auth_test.go +56 -0
  5. package/resources/custom-service-sdk/manifest.json +14 -9
  6. package/resources/custom-service-sdk/platform.go +19 -27
  7. package/resources/custom-service-sdk/resources.go +1 -0
  8. package/resources/custom-service-sdk/resources_scope_test.go +10 -5
  9. package/resources/custom-service-sdk/sdk.go +6 -5
  10. package/resources/skill/SKILL.md +1 -1
  11. package/resources/skill/manifest.json +1 -1
  12. package/resources/skill/references/aihub.md +74 -74
  13. package/resources/skill/references/app-api.md +78 -78
  14. package/resources/skill/references/architecture.md +40 -40
  15. package/resources/skill/references/checkout.md +105 -105
  16. package/resources/skill/references/custom-services.md +6 -6
  17. package/resources/skill/references/data.md +168 -168
  18. package/resources/skill/references/methods.md +3 -0
  19. package/resources/skill/references/modules.md +48 -48
  20. package/resources/skill/references/runtime.md +95 -96
  21. package/resources/skill/story/SKILL.md +264 -264
  22. package/src/commands/help.js +72 -72
  23. package/src/commands/listTargets.js +12 -12
  24. package/src/commands/status.js +2 -2
  25. package/src/commands/uninstall.js +45 -45
  26. package/src/commands/update.js +20 -20
  27. package/src/customServices.js +5 -4
  28. package/src/detect.js +14 -14
  29. package/src/fsx.js +67 -67
  30. package/src/index.js +25 -25
  31. package/src/localRuntime/detect.js +76 -76
  32. package/src/localRuntime/mysqlClient.js +138 -138
  33. package/src/logger.js +37 -37
  34. package/src/mcp/client.js +586 -595
  35. package/src/mcp/hosts.js +520 -520
  36. package/src/mcp/protocol.js +184 -164
  37. package/src/prompt.js +94 -94
  38. 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` 上下文;可信服务可对单次操作显式使用 `ctx.Admin.*`,该提升会审计且不会泄漏到后续调用。余额扣款、权益、支付和订阅使用 `ctx.Billing` / `ctx.Admin.Billing`,精确签名查看 checkout 后只读的 `.draftgo-sdk/billing.go`。完整字段与规则见[自定义服务方法指南](resources/skill/references/custom-services.md)。
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
- 无人值守自动化使用平台提供的独立服务凭据通道及服务主体 AccessGrant,不复用个人 API Key。
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.22",
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": "c750610f8d4b1652da259ea436d47d9856104261c9af1b0e3753c073d36d4693",
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": "ec0d8621a8bb677578001d029fe191af58df9d275204eced5e8061b339ad085b",
34
- "bytes": 12373
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": "e633aa1431beddd25b2c999370a770f77c3de7a2d7123687eca75c9ed0d3838f",
49
- "bytes": 9708
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": "bba3243f348e9418888f37ee2d5b487fc26a31e4a646e75bef46d5d807938cee",
64
- "bytes": 3592
68
+ "sha256": "f9b19b592c0b2ddc0564715828fd840c974216f8e99e9690af43f2cf4a0d7fb5",
69
+ "bytes": 3870
65
70
  },
66
71
  {
67
72
  "path": "sdk.go",
68
- "sha256": "95ffcc335416ac842e06ea51b75e6739a04ddd5573a946e4175d6c5f8c110a67",
69
- "bytes": 8167
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{ user map[string]any }
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
- if a.user == nil {
328
- return errors.New("require_login failed: user is not authenticated")
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
- if err := a.RequireLogin(); err != nil {
334
- return err
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
- if err := a.RequireLogin(); err != nil {
348
- return err
339
+ role = strings.TrimSpace(role)
340
+ if role == "" {
341
+ return errors.New("require_role failed: role is required")
349
342
  }
350
- if a.user["role_code"] == role {
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
- for _, raw := range asSlice(a.user["roles"]) {
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": "service", "subject_id": "service:42", "username": "publisher", "is_admin": true}, nil
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 != "service" || principal.SubjectID != "service:42" || principal.Username != "publisher" || !principal.IsAdmin {
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: "service", SubjectID: "custom-service:7", Username: "Service-Script"})
79
- if ctx.Platform || ctx.WorkspaceID != 11 || ctx.SpaceID != 13 || ctx.Principal.SubjectID != "custom-service:7" {
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: "service", SubjectID: "custom-service:8"})
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 service principal supplied by DraftGo. Custom services
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 preserves the service subject, scopes elevation to one
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
@@ -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 调用继承调用者、服务归属和 AccessGrant;`ctx.Admin.*` 是可信服务显式选择的单次管理提升,允许跨用户/空间操作且必须进入审计,不会泄漏到后续调用。高风险 MCP operation 仅在用户意图明确且影响范围已核对时设置 `confirm=true`。
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
  ## 完成条件
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1.0",
3
3
  "id": "draftgo",
4
4
  "name": "DraftGo 开发助手",
5
- "version": "4.0.22",
5
+ "version": "4.0.24",
6
6
  "entry": "SKILL.md",
7
7
  "description": "以 Skill/reference 任务路由、MCP 实时发现、长正文 checkout/commit、统一验证和完成日志为边界的 DraftGo 工作流。",
8
8
  "license": "MIT",