@sema-agent/settings-schema 10.1.1 → 11.0.0

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 (235) hide show
  1. package/CHANGELOG.md +414 -0
  2. package/README.md +141 -65
  3. package/bin/settings-schema.mjs +13 -6
  4. package/conformance/VERSION +1 -1
  5. package/conformance/baseline.json +3 -1
  6. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +3 -1
  7. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +12 -5
  8. package/conformance/cases/c02-hosts-clamp-stacked/meta.json +1 -1
  9. package/conformance/cases/c03-literal-dotted-top-key/expected.json +22 -2
  10. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +3 -1
  11. package/conformance/cases/c06-legacy-governance-lift/expected.json +11 -101
  12. package/conformance/cases/c06-legacy-governance-lift/meta.json +1 -1
  13. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +3 -1
  14. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +203 -2
  15. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +204 -2
  16. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +3 -1
  17. package/conformance/cases/c11-unicode-unknown-keys/expected.json +20 -2
  18. package/conformance/cases/c12-prototype-name-top-keys/expected.json +3 -1
  19. package/conformance/cases/c13-models-field-null/expected.json +3 -1
  20. package/conformance/cases/c14-models-field-empty-array/expected.json +6 -1
  21. package/conformance/cases/c15-models-field-number/expected.json +3 -1
  22. package/conformance/cases/c16-deep-nesting-205/expected.json +14 -2
  23. package/conformance/cases/c17-empty-domain-file/expected.json +3 -1
  24. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +3 -1
  25. package/conformance/cases/c19-wire-envelope-no-version/expected.json +3 -1
  26. package/conformance/cases/c20-governance-absent-lifts/expected.json +11 -86
  27. package/conformance/cases/c20-governance-absent-lifts/meta.json +1 -1
  28. package/conformance/cases/c21-governance-empty-object-wins/expected.json +6 -5
  29. package/conformance/cases/c21-governance-empty-object-wins/input.json +1 -4
  30. package/conformance/cases/c21-governance-empty-object-wins/meta.json +1 -1
  31. package/conformance/cases/c23-mixed-validity-models/expected.json +3 -1
  32. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +22 -2
  33. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +6 -1
  34. package/conformance/cases/c26-hosts-array-1024/expected.json +3 -1
  35. package/conformance/cases/c27-hosts-array-1025/expected.json +6 -7175
  36. package/conformance/cases/c27-hosts-array-1025/meta.json +1 -1
  37. package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -2
  38. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +3 -1
  39. package/conformance/cases/c30-version-triple-a-first/expected.json +6 -1
  40. package/conformance/cases/c31-version-triple-b-changed/expected.json +6 -1
  41. package/conformance/cases/c32-version-triple-a-restored/expected.json +6 -1
  42. package/conformance/cases/c33-limits-nested-carried-key/expected.json +18 -2
  43. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +19 -3
  44. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -1
  45. package/conformance/cases/c36-skills-entry-without-content/expected.json +6 -2
  46. package/conformance/cases/c37-skills-field-null/expected.json +6 -2
  47. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +3 -1
  48. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +3 -1
  49. package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +11 -4
  50. package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +9 -3
  51. package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +39 -2
  52. package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
  53. package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +3 -1
  54. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +19 -10
  55. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +1 -1
  56. package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/expected.json +12 -5
  57. package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/meta.json +9 -0
  58. package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/expected.json +23 -3
  59. package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/input.json +6 -0
  60. package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/meta.json +10 -0
  61. package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -1
  62. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +3 -1
  63. package/conformance/cases/c50-s784-lane-host-legal/expected.json +3 -1
  64. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +3 -1
  65. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +3 -1
  66. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +3 -1
  67. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +3 -1
  68. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +3 -1
  69. package/conformance/cases/c56-s784-lane-device-legal/expected.json +3 -1
  70. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +3 -1
  71. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +3 -1
  72. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +3 -1
  73. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +3 -1
  74. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +3 -1
  75. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +3 -1
  76. package/conformance/cases/c63-s784-host-two-replicas/expected.json +3 -1
  77. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +3 -1
  78. package/conformance/cases/c65-s784-pg-without-db/expected.json +3 -1
  79. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +3 -1
  80. package/conformance/cases/c67-s784-retired-field-loud/expected.json +3 -1
  81. package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
  82. package/conformance/cases/c68-s784-device-local-store/expected.json +3 -1
  83. package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +3 -1
  84. package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +3 -1
  85. package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +119 -0
  86. package/conformance/cases/c71-one-scanner-write-reads-display-forms/input.json +24 -0
  87. package/conformance/cases/c71-one-scanner-write-reads-display-forms/meta.json +10 -0
  88. package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +111 -0
  89. package/conformance/cases/c72-dangling-reference-write-seat-refuses/input.json +26 -0
  90. package/conformance/cases/c72-dangling-reference-write-seat-refuses/meta.json +6 -0
  91. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +3 -1
  92. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +3 -1
  93. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +6 -1
  94. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +11 -1
  95. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +11 -1
  96. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +16 -2
  97. package/conformance/cases/cli04-env-value-with-newline/expected.json +3 -1
  98. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -1
  99. package/conformance/cases/t01-absent-domain-key-not-null/expected.json +3 -1
  100. package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -2
  101. package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +3 -1
  102. package/conformance/cases/z01-every-domain-empty-object/expected.json +8 -4
  103. package/conformance/cases/z01-every-domain-empty-object/input.json +0 -1
  104. package/conformance/cases/z01-every-domain-empty-object/meta.json +1 -1
  105. package/conformance/cases/z02-every-domain-unknown-key/expected.json +163 -23
  106. package/conformance/cases/z02-every-domain-unknown-key/input.json +0 -3
  107. package/conformance/cases/z02-every-domain-unknown-key/meta.json +1 -1
  108. package/dist/api/auth-bridge.d.ts +51 -181
  109. package/dist/api/auth-bridge.js +58 -33
  110. package/dist/api/auth.d.ts +37 -146
  111. package/dist/api/auth.js +16 -26
  112. package/dist/api/feedback.d.ts +87 -0
  113. package/dist/api/feedback.js +66 -0
  114. package/dist/api/pull-token.d.ts +32 -0
  115. package/dist/api/pull-token.js +88 -0
  116. package/dist/api/scopes.d.ts +82 -174
  117. package/dist/api/scopes.js +23 -18
  118. package/dist/bundle.d.ts +15 -7
  119. package/dist/bundle.js +28 -27
  120. package/dist/cli.d.ts +3 -1
  121. package/dist/cli.js +132 -81
  122. package/dist/config-d-file.d.ts +14 -0
  123. package/dist/config-d-file.js +67 -0
  124. package/dist/config-fns.d.ts +87 -186
  125. package/dist/config-fns.js +207 -262
  126. package/dist/conformance/contract-adapter.d.ts +9 -2
  127. package/dist/conformance/contract-adapter.js +15 -7
  128. package/dist/conformance/corpus.d.ts +3 -0
  129. package/dist/conformance/corpus.js +5 -0
  130. package/dist/conformance/derive.d.ts +3 -0
  131. package/dist/conformance/index.d.ts +2 -2
  132. package/dist/conformance/index.js +2 -2
  133. package/dist/conformance/run.js +67 -22
  134. package/dist/conformance/types.d.ts +31 -15
  135. package/dist/conformance/types.js +5 -0
  136. package/dist/conformance/warnings.d.ts +1 -1
  137. package/dist/conformance/warnings.js +1 -4
  138. package/dist/cross-domain.d.ts +66 -22
  139. package/dist/cross-domain.js +164 -93
  140. package/dist/cross-session.d.ts +29 -11
  141. package/dist/default-model.d.ts +43 -0
  142. package/dist/default-model.js +34 -0
  143. package/dist/execution-lane-index.d.ts +4 -8
  144. package/dist/execution-lane-index.js +4 -8
  145. package/dist/execution-lanes.d.ts +31 -8
  146. package/dist/execution-lanes.js +23 -5
  147. package/dist/file-edit.js +5 -2
  148. package/dist/file-store.d.ts +7 -6
  149. package/dist/file-store.js +37 -50
  150. package/dist/fleet.d.ts +364 -762
  151. package/dist/fleet.js +277 -224
  152. package/dist/hooks.d.ts +1151 -4737
  153. package/dist/hooks.js +59 -15
  154. package/dist/index.d.ts +10 -4
  155. package/dist/index.js +13 -7
  156. package/dist/integer.d.ts +11 -0
  157. package/dist/integer.js +11 -0
  158. package/dist/issue-path.d.ts +10 -0
  159. package/dist/issue-path.js +10 -0
  160. package/dist/loopback.d.ts +26 -0
  161. package/dist/loopback.js +54 -0
  162. package/dist/migration-receipt.d.ts +42 -0
  163. package/dist/migration-receipt.js +12 -0
  164. package/dist/one-time-migrations.d.ts +56 -0
  165. package/dist/one-time-migrations.js +819 -0
  166. package/dist/prompts-digest.d.ts +1 -1
  167. package/dist/prompts-digest.js +3 -2
  168. package/dist/reader.d.ts +2 -2
  169. package/dist/remote-exec.d.ts +45 -187
  170. package/dist/remote-exec.js +13 -7
  171. package/dist/resolve-roster.d.ts +18 -3
  172. package/dist/resolve-roster.js +28 -25
  173. package/dist/retired-envs.d.ts +24 -0
  174. package/dist/retired-envs.js +114 -0
  175. package/dist/retired-form.d.ts +31 -0
  176. package/dist/retired-form.js +28 -0
  177. package/dist/safety-merge-spec.d.ts +131 -191
  178. package/dist/safety-merge-spec.js +18 -14
  179. package/dist/scheduler-lifetime.d.ts +5 -0
  180. package/dist/scheduler-lifetime.js +7 -0
  181. package/dist/scheduler-store-migration.d.ts +14 -0
  182. package/dist/scheduler-store-migration.js +14 -0
  183. package/dist/scheduler-store-node.d.ts +24 -9
  184. package/dist/scheduler-store-node.js +25 -15
  185. package/dist/scheduler-store.d.ts +47 -14
  186. package/dist/scheduler-store.js +32 -61
  187. package/dist/secret-refs.d.ts +3 -3
  188. package/dist/secret-refs.js +1 -1
  189. package/dist/secret-scan.d.ts +14 -24
  190. package/dist/secret-scan.js +45 -23
  191. package/dist/sha256-core.d.ts +16 -0
  192. package/dist/sha256-core.js +138 -0
  193. package/dist/sha256.d.ts +6 -1
  194. package/dist/sha256.js +8 -98
  195. package/dist/skills-manifest.js +1 -1
  196. package/dist/types.d.ts +1501 -13536
  197. package/dist/types.js +494 -438
  198. package/dist/url-shape.d.ts +34 -0
  199. package/dist/url-shape.js +61 -0
  200. package/dist/web-admin-credential.d.ts +96 -0
  201. package/dist/{center-credential.js → web-admin-credential.js} +46 -43
  202. package/dist/web-admin-env.d.ts +33 -0
  203. package/dist/web-admin-env.js +33 -0
  204. package/dist/worker-row-verdict.d.ts +16 -0
  205. package/dist/worker-row-verdict.js +31 -0
  206. package/dist/workers-rows.d.ts +34 -0
  207. package/dist/workers-rows.js +42 -0
  208. package/dist/zod-unwrap.d.ts +11 -0
  209. package/dist/zod-unwrap.js +25 -0
  210. package/package.json +17 -9
  211. package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +0 -6
  212. package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +0 -6
  213. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +0 -49
  214. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +0 -26
  215. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +0 -4
  216. package/conformance/migrate-workers/m02-g5-idempotent/expected.json +0 -38
  217. package/conformance/migrate-workers/m02-g5-idempotent/input.json +0 -33
  218. package/conformance/migrate-workers/m02-g5-idempotent/meta.json +0 -5
  219. package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +0 -71
  220. package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +0 -42
  221. package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +0 -4
  222. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +0 -52
  223. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +0 -30
  224. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +0 -4
  225. package/conformance/migrate-workers/m05-row-verdict-reserved-env/expected.json +0 -82
  226. package/conformance/migrate-workers/m05-row-verdict-reserved-env/input.json +0 -68
  227. package/conformance/migrate-workers/m05-row-verdict-reserved-env/meta.json +0 -4
  228. package/dist/center-credential.d.ts +0 -116
  229. package/dist/issue-text.d.ts +0 -10
  230. package/dist/issue-text.js +0 -8
  231. package/dist/migrate-workers.d.ts +0 -71
  232. package/dist/migrate-workers.js +0 -351
  233. package/dist/migrate.d.ts +0 -255
  234. package/dist/migrate.js +0 -542
  235. /package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/input.json +0 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `api/feedback` —— 壳的 /bug 报告送到 web-admin 的 wire 契约:`POST {web-admin}` + {@link FEEDBACK_V1_PATH} 的请求体
3
+ * ({@link FeedbackSubmitRequest})、一行报告的回体({@link FeedbackWire})与列表回体。写方(壳)按请求体铸、读方(web-admin)按同一只
4
+ * schema 判;两个上限一处定义。
5
+ *
6
+ * · 身份(`principal`)**不是**体字段:它恒取访问令牌的主体;体里带了也被剥掉(默认 strip)。
7
+ * · `datetime` 是客户端自报的时刻,只作参考 —— 回体的 `createdAt` 由服务端盖。
8
+ * · 上界:`description`({@link FEEDBACK_DESCRIPTION_MAX_CHARS})与 `transcript` 的序列化长度({@link FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS})。
9
+ * 其余附件格 —— `rawTranscriptJsonl` / `subagentTranscripts` / `lastApiRequest` / `errors` —— 本契约**不设上界**,整份请求体也没有;
10
+ * `transcript` 一格的上限不是存储的上界,读方要限总量须在自己的入口另设(与写方的长会话附件量一起定)。
11
+ *
12
+ * 纯 zod 契约,零 IO,browser-safe。
13
+ */
14
+ import { z } from "zod";
15
+ /** 报告的端点,相对 web-admin 根。 */
16
+ export declare const FEEDBACK_V1_PATH = "/api/v1/feedback";
17
+ /** `description`(去首尾空白后)的上限 —— 报告常带粘贴的报错与栈,所以宽,但有界。 */
18
+ export declare const FEEDBACK_DESCRIPTION_MAX_CHARS = 20000;
19
+ /** `transcript` 的 JSON 序列化长度上限。 */
20
+ export declare const FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS = 2000000;
21
+ /** /bug 报告的 POST 体(写方铸、读方判,同一只)。 */
22
+ export declare const FeedbackSubmitRequest: z.ZodObject<{
23
+ surface: z.ZodString;
24
+ description: z.ZodString;
25
+ platform: z.ZodString;
26
+ terminal: z.ZodString;
27
+ gitRepo: z.ZodBoolean;
28
+ version: z.ZodString;
29
+ message_count: z.ZodNumber;
30
+ datetime: z.ZodString;
31
+ latestAssistantMessageId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
32
+ transcript: z.ZodArray<z.ZodUnknown>;
33
+ errors: z.ZodOptional<z.ZodArray<z.ZodObject<{
34
+ error: z.ZodOptional<z.ZodString>;
35
+ timestamp: z.ZodOptional<z.ZodString>;
36
+ }, z.core.$strip>>>;
37
+ lastApiRequest: z.ZodOptional<z.ZodUnknown>;
38
+ subagentTranscripts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
39
+ rawTranscriptJsonl: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strip>;
41
+ export type FeedbackSubmitRequest = z.infer<typeof FeedbackSubmitRequest>;
42
+ /** 一行报告的回体(`POST` 的 201 体、`GET` 列表的一员)。`principal` = 提交者令牌的主体;`createdAt` = 服务端时刻(epoch ms)。 */
43
+ export declare const FeedbackWire: z.ZodObject<{
44
+ id: z.ZodString;
45
+ principal: z.ZodString;
46
+ surface: z.ZodString;
47
+ description: z.ZodString;
48
+ platform: z.ZodString;
49
+ terminal: z.ZodString;
50
+ gitRepo: z.ZodBoolean;
51
+ version: z.ZodString;
52
+ messageCount: z.ZodNumber;
53
+ latestAssistantMessageId: z.ZodNullable<z.ZodString>;
54
+ transcript: z.ZodArray<z.ZodUnknown>;
55
+ errors: z.ZodNullable<z.ZodArray<z.ZodObject<{
56
+ error: z.ZodOptional<z.ZodString>;
57
+ timestamp: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strip>>>;
59
+ subagentTranscripts: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
60
+ hasRawTranscript: z.ZodBoolean;
61
+ createdAt: z.ZodNumber;
62
+ }, z.core.$strip>;
63
+ export type FeedbackWire = z.infer<typeof FeedbackWire>;
64
+ /** `GET` 的回体(管理员浏览队列)。 */
65
+ export declare const FeedbackListResponse: z.ZodObject<{
66
+ feedback: z.ZodArray<z.ZodObject<{
67
+ id: z.ZodString;
68
+ principal: z.ZodString;
69
+ surface: z.ZodString;
70
+ description: z.ZodString;
71
+ platform: z.ZodString;
72
+ terminal: z.ZodString;
73
+ gitRepo: z.ZodBoolean;
74
+ version: z.ZodString;
75
+ messageCount: z.ZodNumber;
76
+ latestAssistantMessageId: z.ZodNullable<z.ZodString>;
77
+ transcript: z.ZodArray<z.ZodUnknown>;
78
+ errors: z.ZodNullable<z.ZodArray<z.ZodObject<{
79
+ error: z.ZodOptional<z.ZodString>;
80
+ timestamp: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strip>>>;
82
+ subagentTranscripts: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
83
+ hasRawTranscript: z.ZodBoolean;
84
+ createdAt: z.ZodNumber;
85
+ }, z.core.$strip>>;
86
+ }, z.core.$strip>;
87
+ export type FeedbackListResponse = z.infer<typeof FeedbackListResponse>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `api/feedback` —— 壳的 /bug 报告送到 web-admin 的 wire 契约:`POST {web-admin}` + {@link FEEDBACK_V1_PATH} 的请求体
3
+ * ({@link FeedbackSubmitRequest})、一行报告的回体({@link FeedbackWire})与列表回体。写方(壳)按请求体铸、读方(web-admin)按同一只
4
+ * schema 判;两个上限一处定义。
5
+ *
6
+ * · 身份(`principal`)**不是**体字段:它恒取访问令牌的主体;体里带了也被剥掉(默认 strip)。
7
+ * · `datetime` 是客户端自报的时刻,只作参考 —— 回体的 `createdAt` 由服务端盖。
8
+ * · 上界:`description`({@link FEEDBACK_DESCRIPTION_MAX_CHARS})与 `transcript` 的序列化长度({@link FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS})。
9
+ * 其余附件格 —— `rawTranscriptJsonl` / `subagentTranscripts` / `lastApiRequest` / `errors` —— 本契约**不设上界**,整份请求体也没有;
10
+ * `transcript` 一格的上限不是存储的上界,读方要限总量须在自己的入口另设(与写方的长会话附件量一起定)。
11
+ *
12
+ * 纯 zod 契约,零 IO,browser-safe。
13
+ */
14
+ import { z } from "zod";
15
+ import { INTEGER } from "../integer.js";
16
+ /** 报告的端点,相对 web-admin 根。 */
17
+ export const FEEDBACK_V1_PATH = "/api/v1/feedback";
18
+ /** `description`(去首尾空白后)的上限 —— 报告常带粘贴的报错与栈,所以宽,但有界。 */
19
+ export const FEEDBACK_DESCRIPTION_MAX_CHARS = 20_000;
20
+ /** `transcript` 的 JSON 序列化长度上限。 */
21
+ export const FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS = 2_000_000;
22
+ /** 壳报告的一条错误。 */
23
+ const FeedbackError = z.object({ error: z.string().optional(), timestamp: z.string().optional() });
24
+ /** /bug 报告的 POST 体(写方铸、读方判,同一只)。 */
25
+ export const FeedbackSubmitRequest = z.object({
26
+ /** 报告从哪个界面发起(壳恒写 `cli`)。 */
27
+ surface: z.string().min(1),
28
+ description: z.string().trim().min(1).max(FEEDBACK_DESCRIPTION_MAX_CHARS),
29
+ platform: z.string(),
30
+ terminal: z.string(),
31
+ /** 报告时是否在 git 仓里。 */
32
+ gitRepo: z.boolean(),
33
+ /** 发报告的壳的版本。 */
34
+ version: z.string(),
35
+ message_count: INTEGER.nonnegative(),
36
+ /** 客户端自报的时刻(参考,不信任 —— 回体的 `createdAt` 由服务端盖)。 */
37
+ datetime: z.string(),
38
+ latestAssistantMessageId: z.string().nullable().optional(),
39
+ transcript: z.array(z.unknown()).refine((t) => JSON.stringify(t).length <= FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS, {
40
+ message: `transcript must serialize to at most ${FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS} characters`,
41
+ }),
42
+ errors: z.array(FeedbackError).optional(),
43
+ lastApiRequest: z.unknown().optional(),
44
+ subagentTranscripts: z.record(z.string(), z.unknown()).optional(),
45
+ rawTranscriptJsonl: z.string().optional(),
46
+ });
47
+ /** 一行报告的回体(`POST` 的 201 体、`GET` 列表的一员)。`principal` = 提交者令牌的主体;`createdAt` = 服务端时刻(epoch ms)。 */
48
+ export const FeedbackWire = z.object({
49
+ id: z.string().min(1),
50
+ principal: z.string().min(1),
51
+ surface: z.string(),
52
+ description: z.string(),
53
+ platform: z.string(),
54
+ terminal: z.string(),
55
+ gitRepo: z.boolean(),
56
+ version: z.string(),
57
+ messageCount: INTEGER.nonnegative(),
58
+ latestAssistantMessageId: z.string().nullable(),
59
+ transcript: z.array(z.unknown()),
60
+ errors: z.array(FeedbackError).nullable(),
61
+ subagentTranscripts: z.record(z.string(), z.unknown()).nullable(),
62
+ hasRawTranscript: z.boolean(),
63
+ createdAt: INTEGER.nonnegative(),
64
+ });
65
+ /** `GET` 的回体(管理员浏览队列)。 */
66
+ export const FeedbackListResponse = z.object({ feedback: z.array(FeedbackWire) });
@@ -0,0 +1,32 @@
1
+ /** 一个令牌族:v1 / v2 两个前缀 + MAC 消息的标签。族是数据,铸法与验法只有一份。 */
2
+ export interface ScopedTokenFamily {
3
+ readonly v1Prefix: string;
4
+ readonly v2Prefix: string;
5
+ readonly macTag: string;
6
+ }
7
+ /** worker 拉取令牌族(`WEB_ADMIN_ENV.token` 的值)。 */
8
+ export declare const WORKER_PULL_TOKEN: {
9
+ readonly v1Prefix: "wpt_";
10
+ readonly v2Prefix: "wpt2_";
11
+ readonly macTag: "worker-pull";
12
+ };
13
+ /** 一枚令牌绑定的(worker, scope)。 */
14
+ export interface ScopedTokenBinding {
15
+ worker: string;
16
+ scope: string;
17
+ }
18
+ /**
19
+ * 铸一枚按 scope 绑定的令牌(见模块头的两种形)。铸不出 ⇒ **抛**:共享密钥为空、scope 不是 scope id、worker 名为空或带 `_`。
20
+ * `scope` 缺省 = {@link GLOBAL_SCOPE}(v1)。
21
+ */
22
+ export declare function mintScopedToken(family: ScopedTokenFamily, worker: string, shared: string, scope?: string): string;
23
+ /**
24
+ * 读一枚令牌**声称**的绑定 —— 不验真(验真用 {@link verifyScopedToken})。v1 ⇒ 缺省 scope;v2 ⇒ 帧里的 scope;不是这一族的前缀、
25
+ * 帧切不开(缺 worker / 缺 mac / v2 缺 scope)⇒ `undefined`。
26
+ */
27
+ export declare function readScopedTokenBinding(family: ScopedTokenFamily, presented: string): ScopedTokenBinding | undefined;
28
+ /**
29
+ * 验一枚令牌:认领 → 按认领重铸正本 → 常量时间比对。是正本 ⇒ 它绑定的(worker, scope);否则(认领不出、认领的 scope 不是
30
+ * scope id、共享密钥为空、MAC 不对、v2 冒充缺省 scope)⇒ `undefined`。从不抛。
31
+ */
32
+ export declare function verifyScopedToken(family: ScopedTokenFamily, presented: string, shared: string): ScopedTokenBinding | undefined;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `api/pull-token` —— 按 scope 绑定的**令牌形与铸法**:web-admin 给每台 worker 铸一枚只够读它自己那份配置的拉取令牌
3
+ * (`WEB_ADMIN_ENV.token` 的值),收到时按同一条规则验。一条规则,**令牌族是数据**(前缀对 + MAC 标签):
4
+ *
5
+ * · 缺省 scope(`GLOBAL_SCOPE`)⇒ v1:`<v1Prefix><worker>_<mac>`,`mac` = HMAC-SHA256(共享密钥, `<macTag>:v1:<worker>`) 的小写 hex;
6
+ * · 其余 scope ⇒ v2:`<v2Prefix><scope>_<worker>_<mac>`,`mac` = HMAC-SHA256(共享密钥, `<macTag>:v2:<scope>:<worker>`)
7
+ * —— MAC 消息里带着 scope,别的 scope 里同名的 worker 拿不出它。
8
+ *
9
+ * 帧按 `_` 切:scope id 是 slug(`SCOPE_ID_REGEX`,无 `_`),worker 名同样不许带 `_` —— 铸不出一枚切得开的令牌就**抛**,
10
+ * 不铸一枚永远验不过的令牌。共享密钥为空同样抛(不拿空钥签)。hex + 前缀 ⇒ 令牌里没有 `,` / `=`(可以原样放进
11
+ * `SERVICE_AUTH_TOKENS` 一类的 CSV)。
12
+ *
13
+ * 验 = 认领(从帧里读出它**声称**的 worker / scope,不信它)→ 按认领重铸正本 → 常量时间比对。一枚 v2 令牌声称缺省 scope 永远
14
+ * 验不过(缺省 scope 的正本是 v1)。
15
+ *
16
+ * 引擎只把这枚令牌当不透明 bearer 带着(它不铸、不验);铸与验都在 web-admin。纯函数,零 IO,browser-safe(HMAC 是本包自带的
17
+ * 纯 JS 实现,与 `node:crypto` 逐字节同)。
18
+ */
19
+ import { hmacSha256Hex } from "../sha256-core.js";
20
+ import { GLOBAL_SCOPE, SCOPE_ID_REGEX } from "./scopes.js";
21
+ /** worker 拉取令牌族(`WEB_ADMIN_ENV.token` 的值)。 */
22
+ export const WORKER_PULL_TOKEN = { v1Prefix: "wpt_", v2Prefix: "wpt2_", macTag: "worker-pull" };
23
+ /** worker 名能不能进帧:非空、不带 `_`(帧的分隔符)。 */
24
+ const framableWorker = (worker) => worker.length > 0 && !worker.includes("_");
25
+ /**
26
+ * 铸一枚按 scope 绑定的令牌(见模块头的两种形)。铸不出 ⇒ **抛**:共享密钥为空、scope 不是 scope id、worker 名为空或带 `_`。
27
+ * `scope` 缺省 = {@link GLOBAL_SCOPE}(v1)。
28
+ */
29
+ export function mintScopedToken(family, worker, shared, scope = GLOBAL_SCOPE) {
30
+ if (shared.length === 0)
31
+ throw new Error(`cannot mint a ${family.macTag} token: the shared key is empty`);
32
+ if (!framableWorker(worker))
33
+ throw new Error(`cannot mint a ${family.macTag} token: worker ${JSON.stringify(worker)} is empty or contains "_" (the frame separator)`);
34
+ if (scope === GLOBAL_SCOPE)
35
+ return `${family.v1Prefix}${worker}_${hmacSha256Hex(shared, `${family.macTag}:v1:${worker}`)}`;
36
+ if (!SCOPE_ID_REGEX.test(scope))
37
+ throw new Error(`cannot mint a ${family.macTag} token: scope ${JSON.stringify(scope)} is not a scope id`);
38
+ return `${family.v2Prefix}${scope}_${worker}_${hmacSha256Hex(shared, `${family.macTag}:v2:${scope}:${worker}`)}`;
39
+ }
40
+ /**
41
+ * 读一枚令牌**声称**的绑定 —— 不验真(验真用 {@link verifyScopedToken})。v1 ⇒ 缺省 scope;v2 ⇒ 帧里的 scope;不是这一族的前缀、
42
+ * 帧切不开(缺 worker / 缺 mac / v2 缺 scope)⇒ `undefined`。
43
+ */
44
+ export function readScopedTokenBinding(family, presented) {
45
+ // v2 先判:族的 v2 前缀可能以 v1 前缀开头(`wpt2_` 不以 `wpt_` 开头,但规则不该依赖具体拼法)。
46
+ if (presented.startsWith(family.v2Prefix)) {
47
+ const rest = presented.slice(family.v2Prefix.length);
48
+ const first = rest.indexOf("_");
49
+ const last = rest.lastIndexOf("_");
50
+ if (first <= 0 || last <= first + 1 || last === rest.length - 1)
51
+ return undefined;
52
+ return { scope: rest.slice(0, first), worker: rest.slice(first + 1, last) };
53
+ }
54
+ if (presented.startsWith(family.v1Prefix)) {
55
+ const rest = presented.slice(family.v1Prefix.length);
56
+ const sep = rest.lastIndexOf("_");
57
+ if (sep <= 0 || sep === rest.length - 1)
58
+ return undefined;
59
+ return { worker: rest.slice(0, sep), scope: GLOBAL_SCOPE };
60
+ }
61
+ return undefined;
62
+ }
63
+ /** 常量时间比对(长度先比 —— 正本的长度不是秘密:前缀 + 名 + 64 位 hex)。 */
64
+ function constantTimeEqual(a, b) {
65
+ if (a.length !== b.length)
66
+ return false;
67
+ let diff = 0;
68
+ for (let i = 0; i < a.length; i++)
69
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
70
+ return diff === 0;
71
+ }
72
+ /**
73
+ * 验一枚令牌:认领 → 按认领重铸正本 → 常量时间比对。是正本 ⇒ 它绑定的(worker, scope);否则(认领不出、认领的 scope 不是
74
+ * scope id、共享密钥为空、MAC 不对、v2 冒充缺省 scope)⇒ `undefined`。从不抛。
75
+ */
76
+ export function verifyScopedToken(family, presented, shared) {
77
+ const claimed = readScopedTokenBinding(family, presented);
78
+ if (claimed === undefined)
79
+ return undefined;
80
+ let canonical;
81
+ try {
82
+ canonical = mintScopedToken(family, claimed.worker, shared, claimed.scope);
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ return constantTimeEqual(presented, canonical) ? claimed : undefined;
88
+ }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * `api/scopes` — the MULTI-TENANT SCOPE wire contract for the registry (/api/v1/scopes* +
3
- * /api/v1/auth/scope). Frozen here so sema-registry (the implementation), the orchestrator, workers and
2
+ * `api/scopes` — the MULTI-TENANT SCOPE wire contract for web-admin (/api/v1/scopes* +
3
+ * /api/v1/auth/scope). Frozen here so web-admin (the implementation), the orchestrator, workers and
4
4
  * the sema shell import ONE source of truth — same "契约先行" discipline as `api/auth`.
5
5
  *
6
- * THE SCOPE MODEL (normative — the registry implements EXACTLY this):
6
+ * THE SCOPE MODEL (normative — web-admin implements EXACTLY this):
7
7
  *
8
8
  * - A **scope** is a tenant boundary: `{id, name, createdAt}`. `id` is a slug (SCOPE_ID_REGEX).
9
9
  * The reserved id `"global"` (GLOBAL_SCOPE) is the DEFAULT scope: it always exists, is never
@@ -13,10 +13,10 @@
13
13
  * and `viewer` in scope B.
14
14
  * - The **instance-level** `users.role` is unchanged and orthogonal: an instance `admin` administers
15
15
  * ALL scopes — inside any scope their effective role is always `"admin"` (see effectiveScopeRole).
16
- * - The **access JWT** gains an OPTIONAL `scope` claim (SCOPE_CLAIM) binding the token to its active
17
- * scope. A token WITHOUT the claim means `"global"` (tokenScopeOf) — full backward compatibility:
18
- * every pre-scope token keeps working, scoped to global. The refresh token is NOT scope-bound;
19
- * refresh re-mints per resolveTokenScope, and the client re-switches if it wants another scope.
16
+ * - The **access JWT** carries a `scope` claim (SCOPE_CLAIM) binding the token to its active scope —
17
+ * REQUIRED on read: a token without it is refused (readTokenScope ⇒ `scope_claim_missing`, answered 401);
18
+ * it is not read as `"global"`. The refresh token is NOT scope-bound; refresh re-mints per
19
+ * resolveTokenScope (always with the claim), and the client re-switches if it wants another scope.
20
20
  *
21
21
  * ENDPOINTS (paths in SCOPES_V1_PATHS / scopeMembersPath):
22
22
  *
@@ -37,10 +37,10 @@
37
37
  *
38
38
  * WIRE CASING: /api/v1/auth/scope is part of the OAuth face — snake_case on the wire (access_token,
39
39
  * expires_in), matching `api/auth`. The /api/v1/scopes management face uses this package's camelCase
40
- * (createdAt, scopeId) — it is a registry management API, not an OAuth endpoint.
40
+ * (createdAt, scopeId) — it is a web-admin management API, not an OAuth endpoint.
41
41
  *
42
42
  * Pure zod contract + pure decision functions — zero IO, browser-safe. The decision functions
43
- * (resolveTokenScope / canSwitchScope / effectiveScopeRole) are THE semantics: the registry
43
+ * (resolveTokenScope / canSwitchScope / effectiveScopeRole) are THE semantics: web-admin
44
44
  * implementation imports and calls these — it must not re-implement them.
45
45
  */
46
46
  import { z } from "zod";
@@ -51,14 +51,15 @@ export declare const GLOBAL_SCOPE = "global";
51
51
  export declare const RESERVED_SCOPE_IDS: readonly ["global"];
52
52
  /** Slug shape for scope ids: lowercase alphanumeric + inner dashes, 1–64 chars, no leading/trailing dash. */
53
53
  export declare const SCOPE_ID_REGEX: RegExp;
54
- /** The scope endpoints, relative to the registry origin. `members` needs the scope id — use scopeMembersPath. */
54
+ /** The scope endpoints, relative to web-admin origin. `members` needs the scope id — use scopeMembersPath. */
55
55
  export declare const SCOPES_V1_PATHS: {
56
56
  /** GET (list) + POST (create). */
57
57
  readonly scopes: "/api/v1/scopes";
58
58
  /** POST — mint a new access token bound to another scope. */
59
59
  readonly authScope: "/api/v1/auth/scope";
60
60
  };
61
- /** The JWT claim carrying the token's active scope. OPTIONAL — absent means GLOBAL_SCOPE (tokenScopeOf). */
61
+ /** The JWT claim carrying the token's active scope. Every minted access token carries it; a token without it is refused on read
62
+ * ({@link readTokenScope}). */
62
63
  export declare const SCOPE_CLAIM = "scope";
63
64
  /** `/api/v1/scopes/{id}/members` for PUT (upsert) and DELETE (remove). */
64
65
  export declare function scopeMembersPath(scopeId: string): string;
@@ -69,230 +70,129 @@ export declare function isReservedScopeId(id: string): boolean;
69
70
  export declare const ScopeId: z.ZodString;
70
71
  export type ScopeId = z.infer<typeof ScopeId>;
71
72
  /** The per-scope role ladder = the existing instance RBAC ladder (viewer < editor < publisher < admin). */
72
- export declare const ScopeRole: z.ZodEnum<[UserRole, ...UserRole[]]>;
73
+ export declare const ScopeRole: z.ZodEnum<{
74
+ viewer: "viewer";
75
+ editor: "editor";
76
+ publisher: "publisher";
77
+ admin: "admin";
78
+ }>;
73
79
  export type ScopeRole = UserRole;
74
80
  /** The scope entity. */
75
81
  export declare const Scope: z.ZodObject<{
76
82
  id: z.ZodString;
77
- /** Human display name (non-unique, non-secret). */
78
83
  name: z.ZodString;
79
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
80
84
  createdAt: z.ZodNumber;
81
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
82
- id: z.ZodString;
83
- /** Human display name (non-unique, non-secret). */
84
- name: z.ZodString;
85
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
86
- createdAt: z.ZodNumber;
87
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
88
- id: z.ZodString;
89
- /** Human display name (non-unique, non-secret). */
90
- name: z.ZodString;
91
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
92
- createdAt: z.ZodNumber;
93
- }, z.ZodTypeAny, "passthrough">>;
85
+ }, z.core.$loose>;
94
86
  export type Scope = z.infer<typeof Scope>;
95
87
  /** One membership row: `principal` holds `role` inside `scopeId`. Per-scope — the same principal may
96
88
  * hold different roles in different scopes. */
97
89
  export declare const ScopeMember: z.ZodObject<{
98
90
  scopeId: z.ZodString;
99
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
100
- principal: z.ZodString;
101
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
102
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
103
- scopeId: z.ZodString;
104
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
105
91
  principal: z.ZodString;
106
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
107
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
108
- scopeId: z.ZodString;
109
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
110
- principal: z.ZodString;
111
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
112
- }, z.ZodTypeAny, "passthrough">>;
92
+ role: z.ZodEnum<{
93
+ viewer: "viewer";
94
+ editor: "editor";
95
+ publisher: "publisher";
96
+ admin: "admin";
97
+ }>;
98
+ }, z.core.$loose>;
113
99
  export type ScopeMember = z.infer<typeof ScopeMember>;
114
100
  /** One list entry: a scope the caller can act in, with the caller's EFFECTIVE role there
115
101
  * (effectiveScopeRole — instance admins list every scope with role "admin"). */
116
102
  export declare const ScopeListEntry: z.ZodObject<{
117
103
  id: z.ZodString;
118
104
  name: z.ZodString;
119
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
120
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
121
- id: z.ZodString;
122
- name: z.ZodString;
123
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
124
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
125
- id: z.ZodString;
126
- name: z.ZodString;
127
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
128
- }, z.ZodTypeAny, "passthrough">>;
105
+ role: z.ZodEnum<{
106
+ viewer: "viewer";
107
+ editor: "editor";
108
+ publisher: "publisher";
109
+ admin: "admin";
110
+ }>;
111
+ }, z.core.$loose>;
129
112
  export type ScopeListEntry = z.infer<typeof ScopeListEntry>;
130
113
  export declare const ScopesListResponse: z.ZodObject<{
131
114
  scopes: z.ZodArray<z.ZodObject<{
132
115
  id: z.ZodString;
133
116
  name: z.ZodString;
134
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
135
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
136
- id: z.ZodString;
137
- name: z.ZodString;
138
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
139
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
140
- id: z.ZodString;
141
- name: z.ZodString;
142
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
143
- }, z.ZodTypeAny, "passthrough">>, "many">;
144
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
145
- scopes: z.ZodArray<z.ZodObject<{
146
- id: z.ZodString;
147
- name: z.ZodString;
148
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
149
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
150
- id: z.ZodString;
151
- name: z.ZodString;
152
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
153
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
154
- id: z.ZodString;
155
- name: z.ZodString;
156
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
157
- }, z.ZodTypeAny, "passthrough">>, "many">;
158
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
159
- scopes: z.ZodArray<z.ZodObject<{
160
- id: z.ZodString;
161
- name: z.ZodString;
162
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
163
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
164
- id: z.ZodString;
165
- name: z.ZodString;
166
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
167
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
168
- id: z.ZodString;
169
- name: z.ZodString;
170
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
171
- }, z.ZodTypeAny, "passthrough">>, "many">;
172
- }, z.ZodTypeAny, "passthrough">>;
117
+ role: z.ZodEnum<{
118
+ viewer: "viewer";
119
+ editor: "editor";
120
+ publisher: "publisher";
121
+ admin: "admin";
122
+ }>;
123
+ }, z.core.$loose>>;
124
+ }, z.core.$loose>;
173
125
  export type ScopesListResponse = z.infer<typeof ScopesListResponse>;
174
126
  /** Request: the target scope. Errors: `invalid_request` (malformed), `access_denied` (not a member and
175
127
  * not an instance admin — canSwitchScope false). */
176
128
  export declare const ScopeSwitchRequest: z.ZodObject<{
177
129
  scope: z.ZodString;
178
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
179
- scope: z.ZodString;
180
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
181
- scope: z.ZodString;
182
- }, z.ZodTypeAny, "passthrough">>;
130
+ }, z.core.$loose>;
183
131
  export type ScopeSwitchRequest = z.infer<typeof ScopeSwitchRequest>;
184
132
  /** A fresh access token bound to the requested scope (`scope` claim = `scope` field below). The refresh
185
133
  * grant is untouched — this endpoint never returns a refresh_token. `role` = the caller's effective role
186
134
  * inside the new scope (effectiveScopeRole). */
187
135
  export declare const ScopeSwitchResponse: z.ZodObject<{
188
- /** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
189
- access_token: z.ZodString;
190
- token_type: z.ZodLiteral<"Bearer">;
191
- /** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
192
- expires_in: z.ZodNumber;
193
- scope: z.ZodString;
194
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
195
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
196
- /** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
197
- access_token: z.ZodString;
198
- token_type: z.ZodLiteral<"Bearer">;
199
- /** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
200
- expires_in: z.ZodNumber;
201
- scope: z.ZodString;
202
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
203
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
204
- /** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
205
136
  access_token: z.ZodString;
206
137
  token_type: z.ZodLiteral<"Bearer">;
207
- /** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
208
138
  expires_in: z.ZodNumber;
209
139
  scope: z.ZodString;
210
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
211
- }, z.ZodTypeAny, "passthrough">>;
140
+ role: z.ZodEnum<{
141
+ viewer: "viewer";
142
+ editor: "editor";
143
+ publisher: "publisher";
144
+ admin: "admin";
145
+ }>;
146
+ }, z.core.$loose>;
212
147
  export type ScopeSwitchResponse = z.infer<typeof ScopeSwitchResponse>;
213
148
  /** Re-exported convenience: the switch endpoint mints through the SAME exit as api/auth (1h JWT). */
214
149
  export declare const SCOPE_SWITCH_TOKEN_TTL_SECONDS: number;
215
150
  /** Reserved ids ("global") are rejected AT THE SCHEMA — the default scope pre-exists and is not creatable. */
216
151
  export declare const ScopeCreateRequest: z.ZodObject<{
217
- id: z.ZodEffects<z.ZodString, string, string>;
218
- name: z.ZodString;
219
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
220
- id: z.ZodEffects<z.ZodString, string, string>;
221
- name: z.ZodString;
222
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
223
- id: z.ZodEffects<z.ZodString, string, string>;
152
+ id: z.ZodString;
224
153
  name: z.ZodString;
225
- }, z.ZodTypeAny, "passthrough">>;
154
+ }, z.core.$loose>;
226
155
  export type ScopeCreateRequest = z.infer<typeof ScopeCreateRequest>;
227
156
  /** The created scope, as stored. */
228
157
  export declare const ScopeCreateResponse: z.ZodObject<{
229
158
  id: z.ZodString;
230
- /** Human display name (non-unique, non-secret). */
231
- name: z.ZodString;
232
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
233
- createdAt: z.ZodNumber;
234
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
235
- id: z.ZodString;
236
- /** Human display name (non-unique, non-secret). */
237
159
  name: z.ZodString;
238
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
239
160
  createdAt: z.ZodNumber;
240
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
241
- id: z.ZodString;
242
- /** Human display name (non-unique, non-secret). */
243
- name: z.ZodString;
244
- /** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
245
- createdAt: z.ZodNumber;
246
- }, z.ZodTypeAny, "passthrough">>;
161
+ }, z.core.$loose>;
247
162
  export type ScopeCreateResponse = Scope;
248
163
  /** PUT body — upsert: sets `principal`'s role in the path scope, creating or replacing the row. */
249
164
  export declare const ScopeMemberUpsertRequest: z.ZodObject<{
250
165
  principal: z.ZodString;
251
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
252
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
253
- principal: z.ZodString;
254
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
255
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
256
- principal: z.ZodString;
257
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
258
- }, z.ZodTypeAny, "passthrough">>;
166
+ role: z.ZodEnum<{
167
+ viewer: "viewer";
168
+ editor: "editor";
169
+ publisher: "publisher";
170
+ admin: "admin";
171
+ }>;
172
+ }, z.core.$loose>;
259
173
  export type ScopeMemberUpsertRequest = z.infer<typeof ScopeMemberUpsertRequest>;
260
174
  /** The stored membership row (scopeId echoes the path). */
261
175
  export declare const ScopeMemberUpsertResponse: z.ZodObject<{
262
176
  scopeId: z.ZodString;
263
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
264
- principal: z.ZodString;
265
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
266
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
267
- scopeId: z.ZodString;
268
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
269
177
  principal: z.ZodString;
270
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
271
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
272
- scopeId: z.ZodString;
273
- /** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
274
- principal: z.ZodString;
275
- role: z.ZodEnum<[UserRole, ...UserRole[]]>;
276
- }, z.ZodTypeAny, "passthrough">>;
178
+ role: z.ZodEnum<{
179
+ viewer: "viewer";
180
+ editor: "editor";
181
+ publisher: "publisher";
182
+ admin: "admin";
183
+ }>;
184
+ }, z.core.$loose>;
277
185
  export type ScopeMemberUpsertResponse = ScopeMember;
278
186
  /** DELETE body — remove `principal` from the path scope. */
279
187
  export declare const ScopeMemberDeleteRequest: z.ZodObject<{
280
188
  principal: z.ZodString;
281
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
282
- principal: z.ZodString;
283
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
284
- principal: z.ZodString;
285
- }, z.ZodTypeAny, "passthrough">>;
189
+ }, z.core.$loose>;
286
190
  export type ScopeMemberDeleteRequest = z.infer<typeof ScopeMemberDeleteRequest>;
287
191
  /** Idempotent: `{ok:true}` whether or not the principal was a member (no membership disclosure beyond
288
192
  * what the admin caller already sees). */
289
193
  export declare const ScopeMemberDeleteResponse: z.ZodObject<{
290
194
  ok: z.ZodLiteral<true>;
291
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
292
- ok: z.ZodLiteral<true>;
293
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
294
- ok: z.ZodLiteral<true>;
295
- }, z.ZodTypeAny, "passthrough">>;
195
+ }, z.core.$loose>;
296
196
  export type ScopeMemberDeleteResponse = z.infer<typeof ScopeMemberDeleteResponse>;
297
197
  /** A membership as the decision functions see it (a projection of ScopeMember). */
298
198
  export interface ScopeMembershipInput {
@@ -317,7 +217,7 @@ export interface ResolvedTokenScope {
317
217
  export declare function effectiveScopeRole(membershipRole: UserRole | undefined, instanceRole: string): UserRole | undefined;
318
218
  /**
319
219
  * THE token-minting scope decision — which scope (and role claim) a fresh access token binds to, given
320
- * the principal's scope memberships and instance role. Deterministic; the registry (Node/Next) imports
220
+ * the principal's scope memberships and instance role. Deterministic; web-admin (Node/Next) imports
321
221
  * and calls this at every mint (device-flow grant AND refresh). NORMATIVE RULES (verbatim, 任务书拍定):
322
222
  *
323
223
  * 1. **唯一成员 scope** — exactly one membership → bind that scope, role = that membership's effective
@@ -342,11 +242,19 @@ export declare function resolveTokenScope(memberships: readonly ScopeMembershipI
342
242
  * membership cannot switch to global (their default mint never lands there either, rule 3 above).
343
243
  */
344
244
  export declare function canSwitchScope(memberships: readonly ScopeMembershipInput[], instanceRole: string, targetScope: string): boolean;
245
+ /** Why {@link readTokenScope} refuses — a CLOSED set (one word today). */
246
+ export declare const TOKEN_SCOPE_REFUSALS: readonly ["scope_claim_missing"];
247
+ export type TokenScopeRefusal = (typeof TOKEN_SCOPE_REFUSALS)[number];
345
248
  /**
346
- * Read the active scope off verified JWT claims: a non-empty string `scope` claim, else GLOBAL_SCOPE.
347
- * This is the ONE place the "missing claim = global" backward-compat rule lives — workers and the
348
- * registry both call this instead of defaulting ad hoc.
249
+ * Read the active scope off verified JWT claims — the ONE reader. A non-empty string `scope` claim ⇒ `{ scope }`; a claim that is
250
+ * missing, empty or not a string ⇒ `{ refused: "scope_claim_missing" }` (the reader answers 401). There is no default scope here:
251
+ * every access token web-admin mints carries the claim, an access token lives at most {@link ACCESS_TOKEN_TTL_SECONDS}, and a refresh
252
+ * re-mints one with the claim — a token without it is not a token this contract reads (it used to be read as `"global"`).
349
253
  */
350
- export declare function tokenScopeOf(claims: {
254
+ export declare function readTokenScope(claims: {
351
255
  scope?: unknown;
352
- }): string;
256
+ }): {
257
+ scope: string;
258
+ } | {
259
+ refused: TokenScopeRefusal;
260
+ };