@yss123/cage-kernel 0.2.0 → 0.3.1

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 (140) hide show
  1. package/README.md +70 -10
  2. package/dist/authorization.d.ts +34 -0
  3. package/dist/authorization.js +89 -0
  4. package/dist/authorization.js.map +1 -0
  5. package/dist/brands.d.ts +10 -0
  6. package/dist/brands.js +11 -1
  7. package/dist/brands.js.map +1 -1
  8. package/dist/db/connect.d.ts +14 -0
  9. package/dist/db/connect.js +18 -2
  10. package/dist/db/connect.js.map +1 -1
  11. package/dist/db/ddl.d.ts +32 -0
  12. package/dist/db/ddl.js +36 -0
  13. package/dist/db/ddl.js.map +1 -1
  14. package/dist/db/event-version-schema.d.ts +9 -0
  15. package/dist/db/event-version-schema.js +10 -0
  16. package/dist/db/event-version-schema.js.map +1 -0
  17. package/dist/db/index.d.ts +4 -0
  18. package/dist/db/index.js +4 -0
  19. package/dist/db/index.js.map +1 -1
  20. package/dist/db/migrate.d.ts +10 -0
  21. package/dist/db/migrate.js +9 -1
  22. package/dist/db/migrate.js.map +1 -1
  23. package/dist/db/rows.d.ts +2 -0
  24. package/dist/db/rows.js +3 -0
  25. package/dist/db/rows.js.map +1 -1
  26. package/dist/db/runtime-schema.d.ts +9 -0
  27. package/dist/db/runtime-schema.js +41 -0
  28. package/dist/db/runtime-schema.js.map +1 -0
  29. package/dist/db/session.d.ts +6 -0
  30. package/dist/db/session.js +6 -0
  31. package/dist/db/session.js.map +1 -1
  32. package/dist/errors.d.ts +12 -0
  33. package/dist/errors.js +12 -0
  34. package/dist/errors.js.map +1 -1
  35. package/dist/files/links.d.ts +10 -0
  36. package/dist/files/links.js +50 -0
  37. package/dist/files/links.js.map +1 -0
  38. package/dist/files/schema.d.ts +9 -0
  39. package/dist/files/schema.js +51 -0
  40. package/dist/files/schema.js.map +1 -0
  41. package/dist/index.d.ts +11 -2
  42. package/dist/index.js +11 -2
  43. package/dist/index.js.map +1 -1
  44. package/dist/kernel.d.ts +86 -0
  45. package/dist/kernel.js +71 -41
  46. package/dist/kernel.js.map +1 -1
  47. package/dist/mask.d.ts +6 -0
  48. package/dist/mask.js +6 -0
  49. package/dist/mask.js.map +1 -1
  50. package/dist/org.d.ts +7 -0
  51. package/dist/org.js +5 -0
  52. package/dist/org.js.map +1 -1
  53. package/dist/pipeline/draft.d.ts +16 -0
  54. package/dist/pipeline/draft.js +39 -0
  55. package/dist/pipeline/draft.js.map +1 -0
  56. package/dist/pipeline/idempotency.d.ts +6 -0
  57. package/dist/pipeline/idempotency.js +6 -0
  58. package/dist/pipeline/idempotency.js.map +1 -1
  59. package/dist/pipeline/invoker.d.ts +9 -0
  60. package/dist/pipeline/invoker.js +22 -0
  61. package/dist/pipeline/invoker.js.map +1 -0
  62. package/dist/pipeline/load.d.ts +13 -0
  63. package/dist/pipeline/load.js +13 -0
  64. package/dist/pipeline/load.js.map +1 -1
  65. package/dist/primitives/aggregate.d.ts +95 -2
  66. package/dist/primitives/aggregate.js +13 -1
  67. package/dist/primitives/aggregate.js.map +1 -1
  68. package/dist/primitives/business-process.d.ts +128 -0
  69. package/dist/primitives/business-process.js +62 -0
  70. package/dist/primitives/business-process.js.map +1 -0
  71. package/dist/primitives/collection.d.ts +65 -0
  72. package/dist/primitives/collection.js +79 -0
  73. package/dist/primitives/collection.js.map +1 -0
  74. package/dist/primitives/command.d.ts +110 -11
  75. package/dist/primitives/command.js +16 -0
  76. package/dist/primitives/command.js.map +1 -1
  77. package/dist/primitives/constraints.d.ts +30 -1
  78. package/dist/primitives/constraints.js +14 -0
  79. package/dist/primitives/constraints.js.map +1 -1
  80. package/dist/primitives/event.d.ts +36 -0
  81. package/dist/primitives/event.js +10 -1
  82. package/dist/primitives/event.js.map +1 -1
  83. package/dist/primitives/fields.d.ts +136 -16
  84. package/dist/primitives/fields.js +104 -13
  85. package/dist/primitives/fields.js.map +1 -1
  86. package/dist/primitives/flow.d.ts +173 -8
  87. package/dist/primitives/flow.js +29 -2
  88. package/dist/primitives/flow.js.map +1 -1
  89. package/dist/primitives/states.d.ts +34 -2
  90. package/dist/primitives/states.js +15 -0
  91. package/dist/primitives/states.js.map +1 -1
  92. package/dist/read.d.ts +31 -1
  93. package/dist/read.js +137 -39
  94. package/dist/read.js.map +1 -1
  95. package/dist/registry.d.ts +51 -1
  96. package/dist/registry.js +79 -5
  97. package/dist/registry.js.map +1 -1
  98. package/dist/relations.d.ts +7 -0
  99. package/dist/relations.js +7 -0
  100. package/dist/relations.js.map +1 -1
  101. package/dist/runtime.d.ts +35 -0
  102. package/dist/runtime.js +9 -0
  103. package/dist/runtime.js.map +1 -0
  104. package/dist/scope/index.d.ts +5 -0
  105. package/dist/scope/index.js +5 -0
  106. package/dist/scope/index.js.map +1 -1
  107. package/dist/telemetry.d.ts +8 -0
  108. package/dist/telemetry.js +28 -3
  109. package/dist/telemetry.js.map +1 -1
  110. package/dist/transfer/csv.d.ts +11 -0
  111. package/dist/transfer/csv.js +11 -0
  112. package/dist/transfer/csv.js.map +1 -1
  113. package/dist/transfer/index.d.ts +18 -0
  114. package/dist/transfer/index.js +34 -8
  115. package/dist/transfer/index.js.map +1 -1
  116. package/dist/transfer/schema.d.ts +5 -0
  117. package/dist/transfer/schema.js +5 -0
  118. package/dist/transfer/schema.js.map +1 -1
  119. package/dist/types.d.ts +38 -2
  120. package/dist/types.js +8 -0
  121. package/dist/types.js.map +1 -1
  122. package/dist/util/names.d.ts +6 -0
  123. package/dist/util/names.js +6 -0
  124. package/dist/util/names.js.map +1 -1
  125. package/dist/worker/consumers.d.ts +7 -0
  126. package/dist/worker/consumers.js +21 -9
  127. package/dist/worker/consumers.js.map +1 -1
  128. package/dist/worker/health.d.ts +38 -0
  129. package/dist/worker/health.js +20 -0
  130. package/dist/worker/health.js.map +1 -0
  131. package/dist/worker/index.d.ts +34 -1
  132. package/dist/worker/index.js +175 -38
  133. package/dist/worker/index.js.map +1 -1
  134. package/dist/worker/outbox.d.ts +25 -2
  135. package/dist/worker/outbox.js +40 -2
  136. package/dist/worker/outbox.js.map +1 -1
  137. package/dist/worker/scheduler.d.ts +10 -1
  138. package/dist/worker/scheduler.js +34 -4
  139. package/dist/worker/scheduler.js.map +1 -1
  140. package/package.json +1 -1
@@ -0,0 +1,51 @@
1
+ // 追加系统迁移,文件字节不进入数据库、审计或 outbox。
2
+ /**
3
+ * 上传会话记录用户、目标命令/字段、字节政策和幂等摘要;bound 与 bound_id 的约束固定文件归属。
4
+ * 分片由 upload_id/number 唯一定位,token/lease_until 解决并发写入和中断恢复,storage_key 指向字节存储。
5
+ * bound 文件不按临时会话过期清理;SQL 是历史迁移,修改结构须追加迁移而非编辑字符串。
6
+ */
7
+ export const FILE_MIGRATION = {
8
+ name: 'cage:0004_files',
9
+ sql: `
10
+ CREATE TABLE sys.file_upload (
11
+ id uuid PRIMARY KEY,
12
+ tenant_id uuid NOT NULL,
13
+ user_id uuid NOT NULL,
14
+ aggregate_name text NOT NULL,
15
+ field_name text NOT NULL,
16
+ command_name text NOT NULL,
17
+ target_id uuid,
18
+ filename text NOT NULL,
19
+ content_type text NOT NULL,
20
+ bytes bigint NOT NULL CHECK (bytes > 0),
21
+ part_bytes int NOT NULL CHECK (part_bytes > 0),
22
+ part_count int NOT NULL CHECK (part_count > 0),
23
+ storage text NOT NULL,
24
+ storage_key text,
25
+ status text NOT NULL CHECK (status IN ('uploading','completing','ready','bound','deleting','deleted')),
26
+ bound_id uuid,
27
+ operation_token uuid,
28
+ lease_until timestamptz,
29
+ pruned_at timestamptz,
30
+ idempotency_key text NOT NULL,
31
+ request_hash text NOT NULL,
32
+ created_at timestamptz NOT NULL DEFAULT now(),
33
+ expires_at timestamptz NOT NULL DEFAULT now() + interval '24 hours',
34
+ UNIQUE (tenant_id, user_id, idempotency_key),
35
+ CHECK ((status = 'bound') = (bound_id IS NOT NULL))
36
+ );
37
+ CREATE INDEX file_upload_owner_idx ON sys.file_upload(tenant_id, user_id, created_at DESC);
38
+ CREATE INDEX file_upload_expiry_idx ON sys.file_upload(expires_at) WHERE status <> 'bound';
39
+ CREATE TABLE sys.file_part (
40
+ upload_id uuid NOT NULL REFERENCES sys.file_upload(id),
41
+ number int NOT NULL CHECK (number > 0),
42
+ bytes int NOT NULL CHECK (bytes > 0),
43
+ sha256 text NOT NULL,
44
+ token uuid NOT NULL,
45
+ storage_key text,
46
+ lease_until timestamptz,
47
+ PRIMARY KEY (upload_id, number)
48
+ );
49
+ `,
50
+ };
51
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/files/schema.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,iBAAiB;IACvB,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCN;CACA,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,16 +1,25 @@
1
+ /**
2
+ * 公共入口按声明原语、执行服务和基础类型组织;业务应优先消费声明与受限运行接口。
3
+ * 数据库迁移/连接与 worker 生命周期另有子路径入口,浏览器只依赖生成契约和 client。
4
+ * 增加导出意味着扩大公开 API,应同步能力发现和学习资料,不能仅凭实现文件存在让应用深层导入。
5
+ */
1
6
  export * from './brands.js';
2
7
  export * from './errors.js';
3
8
  export * from './types.js';
4
- export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, list, json, isField, objectSchema, SENSITIVE_KINDS, type SensitiveKind, type FieldKind, type FieldMeta, type AnyField, type Fields, type FieldValues, type ValueOf, } from './primitives/fields.js';
9
+ export { runtimeServices, type RuntimeDefinition, type RuntimeService } from './runtime.js';
10
+ export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, file, files, isFile, list, json, isField, objectSchema, SENSITIVE_KINDS, type SensitiveKind, type FieldKind, type FieldMeta, type FilePolicy, type AnyField, type Fields, type FieldValues, type ValueOf, } from './primitives/fields.js';
5
11
  export { states, isStates, DEFAULT_STATES, type StatesDef } from './primitives/states.js';
12
+ export { businessProcess, type BusinessProcess, type BusinessStage, type BusinessAction, type BusinessTransition } from './primitives/business-process.js';
6
13
  export { aggregate, isAggregate, hasStates, hiddenFields, sensitiveFields, RESERVED_FIELDS, type Aggregate, type AnyAggregate, type Snapshot, type SnapshotOf, type StateOf, type FieldsOf, type FieldValuesOf, type SystemCols, } from './primitives/aggregate.js';
7
14
  export { command, isCommand, idOnly, empty, type Command, type AnyCommand, type Reads, type ReadRepo, type DepsOf, type Draft, type HandlerCtx, type HandlerResult, type Invoker, type FindOptions, } from './primitives/command.js';
8
15
  export { event, emit, isEvent, defaultPayload, type Event, type AnyEvent, type PayloadOf, type EmitSpec } from './primitives/event.js';
16
+ export { reactionEach, reactionInputs } from './primitives/collection.js';
9
17
  export { sod, ceiling, stepUp, ok, violation, type Constraint, type ConstraintResult, type ConstraintMeta, } from './primitives/constraints.js';
10
18
  export { reaction, query, approval, job, unsafe, daysAgo, type Reaction, type AnyReaction, type Query, type AnyQuery, type Approval, type Job, type JobApi, type JobQueryApi, type TxMode, type UnsafeTag, } from './primitives/flow.js';
11
19
  export { scope, compileScope, compileGrant, scopeMatches, grantsFor, DEFAULT_GRANTS, type ScopeSpec } from './scope/index.js';
12
- export { buildRegistry, type Registry, type RegisteredCommand, type CatalogEntry, type RegistryOptions, type RuntimeExtension } from './registry.js';
20
+ export { buildRegistry, registryFingerprint, type Registry, type RegisteredCommand, type CatalogEntry, type RegistryOptions, type RuntimeExtension } from './registry.js';
13
21
  export { Kernel, createKernel, type KernelOptions, type AuditEntry, type BatchResult, type EmittedEvent } from './kernel.js';
22
+ export { explainAuthorization, type AuthorizationCheck } from './authorization.js';
14
23
  export { ReadSide, compileNamedSql, type ListParams, type Page } from './read.js';
15
24
  export { DataTransfers, importRequestSchema, type TransferTask } from './transfer/index.js';
16
25
  export { maskValue, present, canUnmask, auditDiff } from './mask.js';
package/dist/index.js CHANGED
@@ -1,17 +1,26 @@
1
1
  // @yss123/cage-kernel:声明原语、写入流水线、范围组合子、读侧上下文。不含任何业务名词。
2
+ /**
3
+ * 公共入口按声明原语、执行服务和基础类型组织;业务应优先消费声明与受限运行接口。
4
+ * 数据库迁移/连接与 worker 生命周期另有子路径入口,浏览器只依赖生成契约和 client。
5
+ * 增加导出意味着扩大公开 API,应同步能力发现和学习资料,不能仅凭实现文件存在让应用深层导入。
6
+ */
2
7
  export * from './brands.js';
3
8
  export * from './errors.js';
4
9
  export * from './types.js';
5
- export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, list, json, isField, objectSchema, SENSITIVE_KINDS, } from './primitives/fields.js';
10
+ export { runtimeServices } from './runtime.js';
11
+ export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, file, files, isFile, list, json, isField, objectSchema, SENSITIVE_KINDS, } from './primitives/fields.js';
6
12
  export { states, isStates, DEFAULT_STATES } from './primitives/states.js';
13
+ export { businessProcess } from './primitives/business-process.js';
7
14
  export { aggregate, isAggregate, hasStates, hiddenFields, sensitiveFields, RESERVED_FIELDS, } from './primitives/aggregate.js';
8
15
  export { command, isCommand, idOnly, empty, } from './primitives/command.js';
9
16
  export { event, emit, isEvent, defaultPayload } from './primitives/event.js';
17
+ export { reactionEach, reactionInputs } from './primitives/collection.js';
10
18
  export { sod, ceiling, stepUp, ok, violation, } from './primitives/constraints.js';
11
19
  export { reaction, query, approval, job, unsafe, daysAgo, } from './primitives/flow.js';
12
20
  export { scope, compileScope, compileGrant, scopeMatches, grantsFor, DEFAULT_GRANTS } from './scope/index.js';
13
- export { buildRegistry } from './registry.js';
21
+ export { buildRegistry, registryFingerprint } from './registry.js';
14
22
  export { Kernel, createKernel } from './kernel.js';
23
+ export { explainAuthorization } from './authorization.js';
15
24
  export { ReadSide, compileNamedSql } from './read.js';
16
25
  export { DataTransfers, importRequestSchema } from './transfer/index.js';
17
26
  export { maskValue, present, canUnmask, auditDiff } from './mask.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAC1I,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,GAElF,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAA4D,MAAM,uBAAuB,CAAC;AACvI,OAAO,EACL,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GACpC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAEhD,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAyG,MAAM,eAAe,CAAC;AACrJ,OAAO,EAAE,MAAM,EAAE,YAAY,EAA4E,MAAM,aAAa,CAAC;AAC7H,OAAO,EAAE,QAAQ,EAAE,eAAe,EAA8B,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAqB,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAsC,MAAM,UAAU,CAAC;AACpJ,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAqB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD;;;;GAIG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,eAAe,EAA+C,MAAM,cAAc,CAAC;AAC5F,OAAO,EACL,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAC/J,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,eAAe,EAA0F,MAAM,kCAAkC,CAAC;AAC3J,OAAO,EACL,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,GAElF,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAA4D,MAAM,uBAAuB,CAAC;AACvI,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EACL,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GACpC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAEhD,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAyG,MAAM,eAAe,CAAC;AAC1K,OAAO,EAAE,MAAM,EAAE,YAAY,EAA4E,MAAM,aAAa,CAAC;AAC7H,OAAO,EAAE,oBAAoB,EAA2B,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,QAAQ,EAAE,eAAe,EAA8B,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAqB,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAsC,MAAM,UAAU,CAAC;AACpJ,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAqB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
package/dist/kernel.d.ts CHANGED
@@ -8,81 +8,166 @@ import { type RegisteredCommand, type Registry, type RegistryOptions } from './r
8
8
  import { type Logger } from './telemetry.js';
9
9
  import type { CapabilityParams, Ctx } from './types.js';
10
10
  import { DataTransfers } from './transfer/index.js';
11
+ /** 内核实例配置;传 db 时连接归宿主所有,传 urls 时内核自行创建并在 close 时销毁池。 */
11
12
  export interface KernelOptions {
12
13
  /** domain 包的导出(可以是多个模块,例如框架自带的组织模型 + 业务声明) */
13
14
  domain: object | object[];
14
15
  /** 数据库连接串;或者直接传入已有的连接 */
15
16
  urls?: DatabaseUrls;
17
+ /** 宿主注入的写库与只读库句柄;提供后优先于 urls,close 不销毁这些外部连接。 */
16
18
  db?: {
17
19
  app: Kysely<any>;
18
20
  read: Kysely<any>;
19
21
  };
22
+ /** 注册表的额外能力等选项;不用于运行期动态改变角色授权。 */
20
23
  registryOptions?: RegistryOptions;
24
+ /** 结构化日志实现;省略时使用默认 consoleLogger,调用方仍不能在元数据中塞入敏感业务原文。 */
21
25
  logger?: Logger;
22
26
  /** 同事务反应的最大嵌套深度 */
23
27
  maxReactionDepth?: number;
24
28
  /** 连接池大小 */
25
29
  poolSize?: number;
26
30
  }
31
+ /** 当前事务已登记的事件,含 outbox ID;事务尚未提交时不可将其视为外部已收到的事实。 */
27
32
  export interface EmittedEvent {
33
+ /** 同事务写入的 outbox 数字 ID,用于关联投递记录;不是业务对象 ID。 */
28
34
  outboxId: number;
35
+ /** 已注册的事件名称,决定匹配哪些消费者。 */
29
36
  event: string;
37
+ /** 通过事件 schema 校验的业务载荷,仅供内部消费者使用,不可直接作为公共诊断响应。 */
30
38
  payload: Record<string, unknown>;
31
39
  }
40
+ /** index 为输入行的 0 基索引;atomic 模式任一失败会把其余行标为已回滚,不能交付暂存成功结果。 */
32
41
  export interface BatchResult {
42
+ /** 输入 rows 中从零开始的位置,调用方据此对应原始导入行。 */
33
43
  index: number;
44
+ /** 该行最终是否成功提交;整批原子失败时此前暂存成功也会被改为失败。 */
34
45
  ok: boolean;
46
+ /** 成功时可交付的记录快照,已按主体权限处理敏感字段;失败时省略。 */
35
47
  data?: Record<string, unknown>;
48
+ /** 失败时的框架错误码、可选领域码和详情;不包含成功数据。 */
36
49
  error?: {
37
50
  code: string;
38
51
  domainCode?: string;
39
52
  detail?: unknown;
40
53
  };
41
54
  }
55
+ /** 审计写入规格;diff/client 必须由可信调用方脱敏,writeAudit 不推断业务字段的敏感政策。 */
42
56
  export interface AuditEntry {
57
+ /** 审计所属租户,必须来自可信执行上下文。 */
43
58
  tenantId: string;
59
+ /** 实际执行主体标识,可能是真人用户 ID 或流程/任务身份名。 */
44
60
  actor: string;
61
+ /** 执行主体种类,与 actor 共同解释身份;不能仅凭字符串把系统主体当作真人。 */
45
62
  actorKind: string;
63
+ /** 系统执行代表的真人 ID;没有委托时省略或 null。 */
46
64
  onBehalfOf?: string | null;
65
+ /** 触发入口来源,如 api、process、job、external;用于追踪,不授予权限。 */
47
66
  source: string;
67
+ /** write 业务写审计、read 读取审计、denied 拒绝记录、ops 运维动作。 */
48
68
  kind: 'write' | 'read' | 'denied' | 'ops';
69
+ /** 本次操作关联的能力名;无单一能力时可空。 */
49
70
  capability?: string | null;
71
+ /** 关联的完整命令名;读取或普通运维事件可空。 */
50
72
  command?: string | null;
73
+ /** 关联聚合名;无具体业务对象时可空。 */
51
74
  aggregate?: string | null;
75
+ /** 单一关联记录 ID;多记录读取或无对象时可空。 */
52
76
  aggregateId?: string | null;
77
+ /** 执行结论或机器错误码,由调用者按实际提交/拒绝结果填写。 */
53
78
  result: string;
79
+ /** 经调用方脱敏的变化或操作摘要;writeAudit 不替调用方恢复字段策略。 */
54
80
  diff?: unknown;
81
+ /** 跨 HTTP、命令和异步事件关联的追踪标识;省略或 null 表示未提供。 */
55
82
  requestId?: string | null;
83
+ /** 本次逻辑提交的去重键,仅记录审计关联,不在本方法执行去重。 */
56
84
  idempotencyKey?: string | null;
85
+ /** 调用方允许记录的客户端摘要,例如 IP/UA;不得包含 Cookie、密码或完整请求体。 */
57
86
  client?: unknown;
58
87
  }
88
+ /**
89
+ * 领域写入的统一执行器。权限、版本、状态、约束、不变量、关联、审计和事件在此汇合,
90
+ * 业务 handler 仅计算字段变化,不能越过这些阶段。读服务使用独立账号,worker 复用注册规格。
91
+ * 实例关闭只释放自身创建的池;运行时扩展和 worker 的生命周期由其装配方先行停止。
92
+ */
59
93
  export declare class Kernel {
94
+ /** 解释能力、范围、状态及约束的当前证据,不执行 handler 或提交业务写入;结果不是未来执行一定成功的承诺。 */
95
+ explain(capability: string, input: Record<string, unknown>, ctx: Ctx): Promise<{
96
+ command: string;
97
+ actor: import("./types.js").Actor | null;
98
+ observedAt: string;
99
+ allowed: boolean;
100
+ checks: import("./authorization.js").AuthorizationCheck[];
101
+ scope?: undefined;
102
+ params?: undefined;
103
+ limitations?: undefined;
104
+ } | {
105
+ command: string;
106
+ actor: import("./types.js").Actor | null;
107
+ observedAt: string;
108
+ allowed: boolean | null;
109
+ checks: import("./authorization.js").AuthorizationCheck[];
110
+ scope: import("./types.js").ScopeGrant[];
111
+ params: CapabilityParams;
112
+ limitations: string[];
113
+ }>;
114
+ /** 由 domain 导出建立的运行规格,供命令解析、读侧、DDL 和 worker 共享。 */
60
115
  readonly registry: Registry;
116
+ /** 内部写账号连接;业务应用应走命令,不能据此绕过流水线直接写表。 */
61
117
  readonly app: Kysely<any>;
118
+ /** 只读账号连接,用于 safe 视图事务;不能用写账号代替以省略数据库权限隔离。 */
62
119
  readonly readDb: Kysely<any>;
120
+ /** 按当前 Ctx 授权、范围与掩码读取的公共读服务。 */
63
121
  readonly read: ReadSide;
122
+ /** 后台导入导出协调器,任务状态和结果文件不等同于单次同步读写返回。 */
64
123
  readonly transfers: DataTransfers;
124
+ /** 实例日志出口,用于安全元数据与机器码,不传未经处理的敏感业务数据。 */
65
125
  readonly log: Logger;
66
126
  private readonly handles;
67
127
  private readonly maxDepth;
68
128
  constructor(opts: KernelOptions);
129
+ /** 释放由本实例创建的数据库句柄;外部注入 db 不在 handles 中,不会被擅自销毁。 */
69
130
  close(): Promise<void>;
131
+ /** 按能力字符串或声明对象解析命令;未从 domain 导出时明确报错,不动态注册陌生命令。 */
70
132
  resolve(ref: string | AnyCommand): RegisteredCommand;
71
133
  /** 执行一个命令(自己开事务)。返回按当前主体掩码后的新快照。 */
134
+ /**
135
+ * 事务拥有者:run 任一阶段失败都会回滚,最终返回掩码后的结果或已提交幂等回放。
136
+ * 预期拒绝转换为统一错误;部分拒绝另做尽力审计,审计失败不改变原请求的拒绝结论。
137
+ */
72
138
  execute(ref: string | AnyCommand, raw: unknown, ctx: Ctx): Promise<Record<string, any>>;
73
139
  /** 在已有事务里执行(同事务反应、审批桥接、批量整批原子模式用)。返回未掩码的快照。 */
140
+ /** 外层持有提交权;返回完整内部快照只供受信任框架流程使用,不可直接写入 HTTP 响应。 */
74
141
  executeIn(tx: Transaction<any>, ref: string | AnyCommand, raw: unknown, ctx: Ctx, depth?: number): Promise<Record<string, any>>;
142
+ /** 可选服务在同一事务登记执行回执时使用;只返回可交付字段,避免泄漏内部快照。 */
143
+ executePresentedIn(tx: Transaction<any>, ref: string | AnyCommand, raw: unknown, ctx: Ctx): Promise<Record<string, any>>;
75
144
  /** 批量写入:每一行都完整走 12 关;默认逐行独立提交,可选整批原子。批量没有快捷路径。 */
76
145
  executeBatch(ref: string | AnyCommand, rows: unknown[], ctx: Ctx, opts?: {
77
146
  atomic?: boolean;
78
147
  }): Promise<BatchResult[]>;
148
+ /**
149
+ * 完整事务流水线:输入/身份→幂等→加锁加载→状态/版本→硬约束→受限 handler→合并/不变量→持久化。
150
+ * 写记录、关系、附件、成功审计、outbox 和 same 反应共同提交;任何失败都必须传播到事务拥有者。
151
+ * depth 限制同事务递归,只在最外层使用请求幂等键,避免不同反应错误共享同一回放结果。
152
+ * next 是内部完整快照,presented 是可交付快照,replay 是已有请求结果,三者用途不可混淆。
153
+ */
79
154
  private run;
155
+ /**
156
+ * 在业务事务内映射并校验每个事件,遮盖敏感载荷后持久化提交版本、真人委托与追踪上下文。
157
+ * 任一映射/schema 错误使整个命令回滚;NOTIFY 也随事务提交生效,不会唤醒 worker 消费未提交数据。
158
+ */
80
159
  private enqueueEvents;
160
+ /**
161
+ * 只负责持久化审计,不吞异常。传事务时与业务共同提交;可靠读审计失败应阻止交付明文。
162
+ * 拒绝审计等尽力路径由上层自行 catch,不能在此统一忽略错误而破坏成功写入的审计保证。
163
+ */
81
164
  writeAudit(db: Transaction<any> | Kysely<any>, e: AuditEntry): Promise<void>;
82
165
  /** 被拒绝的尝试异步记录,用于发现探测和配置问题;记录失败不影响请求 */
83
166
  auditDenied(rc: RegisteredCommand, raw: unknown, ctx: Ctx, err: CageError): Promise<void>;
84
167
  private catalogCache?;
168
+ /** 当前实例注册表固定,故可缓存能力名称;角色实际授权仍需按用户从数据库读取。 */
85
169
  catalogCaps(): string[];
170
+ /** 实时加载真人能力供额度约束使用;用户不存在或禁用时返回空集合。 */
86
171
  capabilitiesOf(userId: string, tenantId: string): Promise<Map<string, CapabilityParams>>;
87
172
  /** 把一个用户解析成上下文(HTTP 层会加缓存) */
88
173
  contextFor(userId: string, tenantId: string, extra?: Partial<Ctx>): Promise<Ctx | null>;
@@ -95,5 +180,6 @@ export declare class Kernel {
95
180
  requestId?: string;
96
181
  }): Ctx;
97
182
  }
183
+ /** 构造注册表、连接句柄及读/传输服务;不会自动迁移、初始化组织或启动 worker。 */
98
184
  export declare function createKernel(opts: KernelOptions): Kernel;
99
185
  export type { AnyAggregate };
package/dist/kernel.js CHANGED
@@ -1,6 +1,3 @@
1
- // 内核:唯一的写入入口 execute。HTTP、审批流程、定时任务、反应、批量导入都调用它,
2
- // 所以检查只写一遍,顺序永远一样(设计文档第 5 节的 12 关)。
3
- import { randomUUID } from 'node:crypto';
4
1
  import { sql } from 'kysely';
5
2
  import { connect } from './db/connect.js';
6
3
  import { diffFields, fieldsToColumns } from './db/rows.js';
@@ -11,20 +8,38 @@ import { loadUserGrants, systemCtx, userCtx } from './org.js';
11
8
  import { bindReads, loadScoped } from './pipeline/load.js';
12
9
  import { idempotency } from './pipeline/idempotency.js';
13
10
  import { defaultPayload } from './primitives/event.js';
11
+ import { reactionEntries, reactionEntryError } from './primitives/collection.js';
14
12
  import { ReadSide } from './read.js';
15
13
  import { buildRegistry } from './registry.js';
16
14
  import { consoleLogger, step, traceCarrier } from './telemetry.js';
17
15
  import { DataTransfers } from './transfer/index.js';
18
16
  import { writeRelations } from './relations.js';
17
+ import { assertInvoker } from './pipeline/invoker.js';
18
+ import { explainAuthorization } from './authorization.js';
19
+ import { newDraft, splitRaw } from './pipeline/draft.js';
20
+ import { writeFileLinks } from './files/links.js';
19
21
  function zodIssues(e) {
20
22
  return e.issues.map((i) => ({ path: i.path.join('.'), message: i.message }));
21
23
  }
24
+ /**
25
+ * 领域写入的统一执行器。权限、版本、状态、约束、不变量、关联、审计和事件在此汇合,
26
+ * 业务 handler 仅计算字段变化,不能越过这些阶段。读服务使用独立账号,worker 复用注册规格。
27
+ * 实例关闭只释放自身创建的池;运行时扩展和 worker 的生命周期由其装配方先行停止。
28
+ */
22
29
  export class Kernel {
30
+ /** 解释能力、范围、状态及约束的当前证据,不执行 handler 或提交业务写入;结果不是未来执行一定成功的承诺。 */
31
+ explain(capability, input, ctx) { return explainAuthorization(this, capability, input, ctx); }
32
+ /** 由 domain 导出建立的运行规格,供命令解析、读侧、DDL 和 worker 共享。 */
23
33
  registry;
34
+ /** 内部写账号连接;业务应用应走命令,不能据此绕过流水线直接写表。 */
24
35
  app;
36
+ /** 只读账号连接,用于 safe 视图事务;不能用写账号代替以省略数据库权限隔离。 */
25
37
  readDb;
38
+ /** 按当前 Ctx 授权、范围与掩码读取的公共读服务。 */
26
39
  read;
40
+ /** 后台导入导出协调器,任务状态和结果文件不等同于单次同步读写返回。 */
27
41
  transfers;
42
+ /** 实例日志出口,用于安全元数据与机器码,不传未经处理的敏感业务数据。 */
28
43
  log;
29
44
  handles = [];
30
45
  maxDepth;
@@ -49,10 +64,12 @@ export class Kernel {
49
64
  this.read = new ReadSide(this);
50
65
  this.transfers = new DataTransfers(this);
51
66
  }
67
+ /** 释放由本实例创建的数据库句柄;外部注入 db 不在 handles 中,不会被擅自销毁。 */
52
68
  async close() {
53
69
  for (const h of this.handles)
54
70
  await h.close();
55
71
  }
72
+ /** 按能力字符串或声明对象解析命令;未从 domain 导出时明确报错,不动态注册陌生命令。 */
56
73
  resolve(ref) {
57
74
  const rc = this.registry.command(ref);
58
75
  if (!rc) {
@@ -62,6 +79,10 @@ export class Kernel {
62
79
  }
63
80
  // ---------------- 写入 ----------------
64
81
  /** 执行一个命令(自己开事务)。返回按当前主体掩码后的新快照。 */
82
+ /**
83
+ * 事务拥有者:run 任一阶段失败都会回滚,最终返回掩码后的结果或已提交幂等回放。
84
+ * 预期拒绝转换为统一错误;部分拒绝另做尽力审计,审计失败不改变原请求的拒绝结论。
85
+ */
65
86
  async execute(ref, raw, ctx) {
66
87
  const rc = this.resolve(ref);
67
88
  try {
@@ -81,11 +102,18 @@ export class Kernel {
81
102
  }
82
103
  }
83
104
  /** 在已有事务里执行(同事务反应、审批桥接、批量整批原子模式用)。返回未掩码的快照。 */
105
+ /** 外层持有提交权;返回完整内部快照只供受信任框架流程使用,不可直接写入 HTTP 响应。 */
84
106
  async executeIn(tx, ref, raw, ctx, depth = 1) {
85
107
  const rc = this.resolve(ref);
86
108
  const r = await this.run(tx, rc, raw, ctx, depth);
87
109
  return (r.next ?? r.replay);
88
110
  }
111
+ /** 可选服务在同一事务登记执行回执时使用;只返回可交付字段,避免泄漏内部快照。 */
112
+ async executePresentedIn(tx, ref, raw, ctx) {
113
+ const rc = this.resolve(ref);
114
+ const result = await this.run(tx, rc, raw, ctx, 0);
115
+ return result.presented ?? present(rc.cmd.agg, result.replay, ctx);
116
+ }
89
117
  /** 批量写入:每一行都完整走 12 关;默认逐行独立提交,可选整批原子。批量没有快捷路径。 */
90
118
  async executeBatch(ref, rows, ctx, opts = {}) {
91
119
  const rc = this.resolve(ref);
@@ -129,6 +157,12 @@ export class Kernel {
129
157
  }));
130
158
  }
131
159
  }
160
+ /**
161
+ * 完整事务流水线:输入/身份→幂等→加锁加载→状态/版本→硬约束→受限 handler→合并/不变量→持久化。
162
+ * 写记录、关系、附件、成功审计、outbox 和 same 反应共同提交;任何失败都必须传播到事务拥有者。
163
+ * depth 限制同事务递归,只在最外层使用请求幂等键,避免不同反应错误共享同一回放结果。
164
+ * next 是内部完整快照,presented 是可交付快照,replay 是已有请求结果,三者用途不可混淆。
165
+ */
132
166
  async run(tx, rc, raw, ctx, depth) {
133
167
  const { cmd, capability } = rc;
134
168
  const agg = cmd.agg;
@@ -166,12 +200,7 @@ export class Kernel {
166
200
  if (isNew) {
167
201
  // 新建:跳过第 4、5 关,由内核生成 id 和初始版本号;输入里的同名字段预填进草稿
168
202
  // 输入里带了 id(确定性主键,例如库存锁用采购单 id)时使用它,重复时报 CONFLICT
169
- const givenId = typeof input.id === 'string' ? input.id : undefined;
170
- cur = { id: givenId ?? randomUUID(), status: cmd.to, version: 0, createdAt: now.toISOString(), updatedAt: now.toISOString() };
171
- for (const k of fieldKeys)
172
- cur[k] = k in input ? input[k] : fields[k].kind === 'manyToMany' ? [] : null;
173
- if (agg.scope.deptField && agg.scope.deptField !== 'id' && cur[agg.scope.deptField] == null)
174
- cur[agg.scope.deptField] = ctx.deptId;
203
+ cur = newDraft(cmd, input, ctx, now);
175
204
  }
176
205
  else {
177
206
  if (typeof input.id !== 'string')
@@ -191,7 +220,12 @@ export class Kernel {
191
220
  // 第 6 关:硬约束
192
221
  const meta = {
193
222
  capability,
194
- capabilitiesOf: (uid) => this.capabilitiesOf(uid, ctx.tenantId),
223
+ // 额度等约束复用当前写事务连接,避免同对象的并发等待者占满池后,锁持有者再借连接形成死锁。
224
+ // 仍实时加载角色并检查账号 active;不缓存能力,也不改变约束看到的权限集合。
225
+ capabilitiesOf: async (uid) => {
226
+ const grants = await loadUserGrants(tx, uid, ctx.tenantId);
227
+ return grants?.active ? grants.capabilities : new Map();
228
+ },
195
229
  load: async (aggName, id) => {
196
230
  const a = this.registry.aggregate(aggName);
197
231
  return a && /^[0-9a-f-]{36}$/i.test(id) ? loadScoped(tx, a, id, ctx, false) : null;
@@ -232,6 +266,11 @@ export class Kernel {
232
266
  for (const k of fieldKeys)
233
267
  if (fields[k].kind === 'manyToMany')
234
268
  next[k] = [...new Set(next[k].map((id) => id.toLowerCase()))].sort();
269
+ for (const k of fieldKeys)
270
+ if (fields[k].kind === 'file' || fields[k].kind === 'files') {
271
+ next[k] = Array.isArray(next[k]) ? next[k].map((id) => id.toLowerCase()) : next[k]?.toLowerCase() ?? null;
272
+ record[k] = next[k];
273
+ }
235
274
  // 第 10 关:不变量
236
275
  for (const [name, fn] of Object.entries(agg.invariants)) {
237
276
  let holds = false;
@@ -271,6 +310,7 @@ export class Kernel {
271
310
  }
272
311
  next.updatedAt = now.toISOString();
273
312
  await writeRelations(tx, this.registry, agg, isNew ? {} : cur, next, ctx);
313
+ await writeFileLinks(tx, agg, isNew ? {} : cur, next, ctx);
274
314
  const changed = isNew ? fieldKeys.filter((k) => next[k] !== null) : diffFields(agg, cur, next);
275
315
  await this.writeAudit(tx, {
276
316
  tenantId: ctx.tenantId,
@@ -294,14 +334,18 @@ export class Kernel {
294
334
  for (const { r, name } of this.registry.reactionsFor(ev.event)) {
295
335
  if (r.tx !== 'same')
296
336
  continue;
297
- const rin = r.map(ev.payload);
298
- if (rin === null || rin === undefined)
299
- continue;
300
337
  const rctx = systemCtx({
301
338
  kind: 'reaction', name, tenantId: ctx.tenantId, capabilities: [this.registry.capabilityOf(r.command)],
302
339
  onBehalfOf: actorUserId, requestId: ctx.requestId,
303
340
  });
304
- await this.run(tx, this.resolve(r.command), rin, rctx, depth + 1);
341
+ for (const entry of reactionEntries(r, ev.payload)) {
342
+ try {
343
+ await this.run(tx, this.resolve(r.command), entry.input, rctx, depth + 1);
344
+ }
345
+ catch (error) {
346
+ throw reactionEntryError(error, r, name, entry);
347
+ }
348
+ }
305
349
  }
306
350
  }
307
351
  if (agg.options.grants) {
@@ -314,6 +358,10 @@ export class Kernel {
314
358
  // 第 12 关:函数返回即提交事务;随后由 outbox 投递器异步分发事件
315
359
  return { next, presented };
316
360
  }
361
+ /**
362
+ * 在业务事务内映射并校验每个事件,遮盖敏感载荷后持久化提交版本、真人委托与追踪上下文。
363
+ * 任一映射/schema 错误使整个命令回滚;NOTIFY 也随事务提交生效,不会唤醒 worker 消费未提交数据。
364
+ */
317
365
  async enqueueEvents(tx, cmd, next, input, ctx, prev) {
318
366
  const out = [];
319
367
  if (cmd.emits.length === 0)
@@ -334,6 +382,7 @@ export class Kernel {
334
382
  event: ev.name,
335
383
  aggregate: cmd.agg.name,
336
384
  aggregate_id: next.id,
385
+ aggregate_version: next.version,
337
386
  payload: JSON.stringify(payload),
338
387
  actor: JSON.stringify({ kind: ctx.actor?.kind, id: ctx.actor?.id, userId: ctx.userId, onBehalfOf: ctx.onBehalfOf ?? null }),
339
388
  trace: JSON.stringify(carrier),
@@ -347,6 +396,10 @@ export class Kernel {
347
396
  return out;
348
397
  }
349
398
  // ---------------- 审计 ----------------
399
+ /**
400
+ * 只负责持久化审计,不吞异常。传事务时与业务共同提交;可靠读审计失败应阻止交付明文。
401
+ * 拒绝审计等尽力路径由上层自行 catch,不能在此统一忽略错误而破坏成功写入的审计保证。
402
+ */
350
403
  async writeAudit(db, e) {
351
404
  await db
352
405
  .insertInto('sys.audit_log')
@@ -396,9 +449,11 @@ export class Kernel {
396
449
  }
397
450
  // ---------------- 上下文 ----------------
398
451
  catalogCache;
452
+ /** 当前实例注册表固定,故可缓存能力名称;角色实际授权仍需按用户从数据库读取。 */
399
453
  catalogCaps() {
400
454
  return (this.catalogCache ??= this.registry.capabilityCatalog().map((c) => c.capability));
401
455
  }
456
+ /** 实时加载真人能力供额度约束使用;用户不存在或禁用时返回空集合。 */
402
457
  async capabilitiesOf(userId, tenantId) {
403
458
  const g = await loadUserGrants(this.app, userId, tenantId);
404
459
  return g && g.active ? g.capabilities : new Map();
@@ -413,36 +468,11 @@ export class Kernel {
413
468
  return systemCtx({ kind: 'process', name: opts.process, tenantId: opts.tenantId, capabilities: opts.capabilities, onBehalfOf: opts.onBehalfOf, requestId: opts.requestId });
414
469
  }
415
470
  }
471
+ /** 构造注册表、连接句柄及读/传输服务;不会自动迁移、初始化组织或启动 worker。 */
416
472
  export function createKernel(opts) {
417
473
  return new Kernel(opts);
418
474
  }
419
- function splitRaw(raw) {
420
- if (!raw || typeof raw !== 'object' || Array.isArray(raw))
421
- return { rest: raw ?? {} };
422
- const { expectedVersion, ...rest } = raw;
423
- if (expectedVersion === undefined || expectedVersion === null)
424
- return { rest };
425
- const v = Number(expectedVersion);
426
- if (!Number.isInteger(v))
427
- throw cageError('INVALID_INPUT', [{ path: 'expectedVersion', message: '版本号必须是整数' }]);
428
- return { expectedVersion: v, rest };
429
- }
430
- /** 第 3 关之一:流程专用命令只接受流程身份;用户命令可以被声明过它的系统身份调用 */
431
- function assertInvoker(cmd, ctx, capability) {
432
- const kind = ctx.actor.kind;
433
- if (kind === 'system')
434
- return;
435
- if (kind === 'user' || kind === 'anonymous') {
436
- if (!cmd.invokedBy.includes('user')) {
437
- throw cageError('FORBIDDEN', { capability, message: `该命令只接受 ${cmd.invokedBy.join('/')} 调用` });
438
- }
439
- return;
440
- }
441
- const as = kind === 'reaction' ? 'process' : kind;
442
- if (!cmd.invokedBy.includes('user') && !cmd.invokedBy.includes(as)) {
443
- throw cageError('FORBIDDEN', { capability, message: `${kind} 身份不能调用该命令` });
444
- }
445
- }
475
+ /** 事件持久化不继承操作者明文权限:按事件字段声明掩码/删除,避免异步消费者获得多余秘密。 */
446
476
  function maskEventPayload(ev, payload) {
447
477
  const out = { ...payload };
448
478
  for (const [k, f] of Object.entries(ev.fields)) {