@jskit-ai/kernel 0.1.245 → 0.1.246

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 (101) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +13 -75
  4. package/client/index.js +1 -1
  5. package/client/logging.js +8 -2
  6. package/client/moduleBootstrap.js +41 -33
  7. package/client/moduleBootstrap.test.js +35 -0
  8. package/client/shellBootstrap.js +1 -8
  9. package/client/shellBootstrap.test.js +32 -5
  10. package/client/shellRouting.js +16 -61
  11. package/client/shellRouting.test.js +30 -1
  12. package/client/vite/clientBootstrapPlugin.js +0 -6
  13. package/client/vite/clientBootstrapPlugin.test.js +2 -3
  14. package/internal/node/installedPackages.js +14 -58
  15. package/package.json +2 -4
  16. package/server/actions/actionCatalogue.js +13 -34
  17. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  18. package/{shared → server}/actions/actionDefinitions.js +24 -131
  19. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  20. package/server/actions/actionEvents.js +97 -2
  21. package/server/actions/actionEvents.test.js +79 -0
  22. package/{shared → server}/actions/executionContext.js +3 -13
  23. package/server/actions/idempotency.js +24 -0
  24. package/server/actions/pipeline.js +87 -0
  25. package/{shared → server}/actions/policies.js +20 -33
  26. package/{shared → server}/actions/policies.test.js +6 -6
  27. package/{shared → server}/actions/registry.js +3 -22
  28. package/{shared → server}/actions/registry.test.js +70 -32
  29. package/server/actions/requestMeta.js +17 -0
  30. package/server/http/capabilityHttpRuntime.js +1 -1
  31. package/server/http/capabilityHttpRuntime.test.js +56 -0
  32. package/server/http/errorHandling.js +291 -0
  33. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  34. package/server/http/lib/routeRegistration.js +36 -150
  35. package/server/http/lib/routeTransport.js +2 -0
  36. package/server/http/lib/routeValidator.js +5 -47
  37. package/server/http/lib/routeValidator.test.js +15 -17
  38. package/server/http/lib/router.js +10 -94
  39. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  40. package/server/platform/providerRuntime/helpers.js +1 -34
  41. package/server/runtime/integers.js +1 -5
  42. package/server/runtime/serviceAuthorization.js +4 -9
  43. package/server/support/appConfig.js +2 -1
  44. package/server/support/appConfigFiles.js +1 -38
  45. package/server/support/appConfigFiles.test.js +3 -39
  46. package/server/support/index.js +1 -20
  47. package/shared/capabilities/defineProvider.js +1 -1
  48. package/shared/capabilities/index.js +1 -1
  49. package/shared/index.js +1 -1
  50. package/shared/support/index.js +1 -4
  51. package/shared/support/mobileConfig.js +99 -0
  52. package/shared/support/mobileConfig.test.js +113 -0
  53. package/shared/support/normalize.js +0 -108
  54. package/shared/support/normalize.test.js +0 -134
  55. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  56. package/shared/support/returnToPath.js +1 -20
  57. package/shared/support/returnToPath.test.js +1 -36
  58. package/shared/support/shellLayoutTargets.js +2 -140
  59. package/shared/support/shellLayoutTargets.test.js +1 -110
  60. package/shared/support/sorting.js +1 -9
  61. package/shared/surface/apiPaths.js +1 -82
  62. package/shared/surface/paths.js +38 -171
  63. package/shared/surface/registry.js +2 -1
  64. package/shared/surface/runtime.js +18 -109
  65. package/shared/surface/runtime.test.js +57 -0
  66. package/shared/validators/index.js +1 -4
  67. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  68. package/shared/validators/recordIdParamsValidator.js +0 -6
  69. package/shared/validators/schemaDefinitions.js +4 -30
  70. package/shared/validators/schemaPayloadValidation.js +2 -6
  71. package/test/barrelExposure.test.js +3 -7
  72. package/test/exportsContract.test.js +1 -3
  73. package/client/mobileLaunchRouting.js +0 -231
  74. package/client/mobileLaunchRouting.test.js +0 -230
  75. package/server/http/lib/controller.js +0 -183
  76. package/server/http/lib/controller.test.js +0 -143
  77. package/server/http/lib/index.js +0 -5
  78. package/server/runtime/canonicalJson.js +0 -74
  79. package/server/runtime/entityChangeEvents.js +0 -307
  80. package/server/runtime/entityChangeEvents.test.js +0 -274
  81. package/server/runtime/fastifyBootstrap.js +0 -553
  82. package/server/runtime/moduleConfig.js +0 -223
  83. package/server/runtime/moduleConfig.test.js +0 -165
  84. package/server/runtime/pagination.js +0 -13
  85. package/server/runtime/realtimeNormalization.js +0 -21
  86. package/server/runtime/routeUtils.js +0 -20
  87. package/server/runtime/securityAudit.js +0 -269
  88. package/server/runtime/securityAudit.test.js +0 -41
  89. package/server/support/pageTargets.js +0 -772
  90. package/server/support/pageTargets.test.js +0 -659
  91. package/server/support/shellOutlets.js +0 -576
  92. package/server/support/shellOutlets.test.js +0 -336
  93. package/server/surface/index.js +0 -18
  94. package/shared/actions/audit.js +0 -7
  95. package/shared/actions/idempotency.js +0 -62
  96. package/shared/actions/observability.js +0 -10
  97. package/shared/actions/pipeline.js +0 -309
  98. package/shared/actions/requestMeta.js +0 -36
  99. package/shared/actions/textNormalization.js +0 -3
  100. package/shared/actions/withActionDefaults.js +0 -34
  101. /package/{shared → server}/actions/executionContext.test.js +0 -0
@@ -1,8 +1,8 @@
1
- import { readFile, realpath } from "node:fs/promises";
1
+ import { realpath } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { normalizeObject } from "../../shared/support/normalize.js";
4
4
  import { sortStrings } from "../../shared/support/sorting.js";
5
- import { fileExists } from "./fileSystem.js";
5
+ import { fileExists, readJsonFile } from "./fileSystem.js";
6
6
 
7
7
  const ROOT_DEPENDENCY_SECTIONS = Object.freeze([
8
8
  "dependencies",
@@ -18,14 +18,6 @@ const JSKIT_PACKAGE_CONFIG_KEYS = Object.freeze([
18
18
  "vite"
19
19
  ]);
20
20
 
21
- async function readJsonFile(filePath, fallback = {}) {
22
- try {
23
- return JSON.parse(await readFile(filePath, "utf8"));
24
- } catch {
25
- return fallback;
26
- }
27
- }
28
-
29
21
  function collectRootDependencySpecifiers(packageJson = {}) {
30
22
  const specifiers = new Map();
31
23
  for (const sectionName of ROOT_DEPENDENCY_SECTIONS) {
@@ -42,7 +34,7 @@ function collectRootDependencySpecifiers(packageJson = {}) {
42
34
 
43
35
  function collectPackageDependencyIds(packageJson = {}) {
44
36
  const dependencyIds = new Set();
45
- for (const sectionName of ["dependencies", "optionalDependencies", "peerDependencies"]) {
37
+ for (const sectionName of ROOT_DEPENDENCY_SECTIONS) {
46
38
  for (const packageId of Object.keys(normalizeObject(packageJson?.[sectionName]))) {
47
39
  const normalizedPackageId = String(packageId || "").trim();
48
40
  if (normalizedPackageId) {
@@ -175,56 +167,27 @@ async function discoverInstalledPackages({ appRoot } = {}) {
175
167
  const rootPackageJson = await readJsonFile(path.resolve(normalizedAppRoot, "package.json"), {});
176
168
  const directSpecifiers = collectRootDependencySpecifiers(rootPackageJson);
177
169
  const directPackageIds = new Set(directSpecifiers.keys());
178
- const runtimeRootPackageIds = new Set(
179
- ["dependencies", "optionalDependencies", "peerDependencies"]
180
- .flatMap((sectionName) => Object.keys(normalizeObject(rootPackageJson?.[sectionName])))
181
- );
182
170
  const visited = new Set();
171
+ // Breadth-first walk of the dependency graph; the queue stays ordered by packageId.
183
172
  const queue = [];
184
- const queued = new Map();
173
+ const queued = new Set();
185
174
  const packages = [];
186
175
 
187
- const sortQueue = () => {
188
- queue.sort((left, right) => {
189
- if (left.traverseDependencies !== right.traverseDependencies) {
190
- return left.traverseDependencies ? -1 : 1;
191
- }
192
- return left.packageId.localeCompare(right.packageId);
193
- });
194
- };
195
- const enqueue = ({ packageId, parentPackageRoot = "", traverseDependencies = false }) => {
196
- const normalizedPackageId = String(packageId || "").trim();
197
- if (!normalizedPackageId || visited.has(normalizedPackageId)) {
198
- return;
199
- }
200
- const existing = queued.get(normalizedPackageId);
201
- if (existing) {
202
- if (traverseDependencies && !existing.traverseDependencies) {
203
- existing.traverseDependencies = true;
204
- existing.parentPackageRoot = parentPackageRoot || existing.parentPackageRoot;
205
- sortQueue();
206
- }
176
+ const enqueue = ({ packageId, parentPackageRoot = "" }) => {
177
+ if (!packageId || visited.has(packageId) || queued.has(packageId)) {
207
178
  return;
208
179
  }
209
- const entry = {
210
- packageId: normalizedPackageId,
211
- parentPackageRoot,
212
- traverseDependencies: Boolean(traverseDependencies)
213
- };
214
- queue.push(entry);
215
- queued.set(normalizedPackageId, entry);
216
- sortQueue();
180
+ queue.push({ packageId, parentPackageRoot });
181
+ queued.add(packageId);
182
+ queue.sort((left, right) => left.packageId.localeCompare(right.packageId));
217
183
  };
218
184
 
219
185
  for (const packageId of sortStrings([...directPackageIds])) {
220
- enqueue({
221
- packageId,
222
- traverseDependencies: runtimeRootPackageIds.has(packageId)
223
- });
186
+ enqueue({ packageId });
224
187
  }
225
188
 
226
189
  while (queue.length > 0) {
227
- const { packageId, parentPackageRoot, traverseDependencies } = queue.shift();
190
+ const { packageId, parentPackageRoot } = queue.shift();
228
191
  queued.delete(packageId);
229
192
  if (!packageId || visited.has(packageId)) {
230
193
  continue;
@@ -252,15 +215,8 @@ async function discoverInstalledPackages({ appRoot } = {}) {
252
215
  }
253
216
 
254
217
  const dependencyIds = collectPackageDependencyIds(packageJson);
255
- if (traverseDependencies) {
256
- for (const dependencyId of dependencyIds) {
257
- const normalizedDependencyId = String(dependencyId || "").trim();
258
- enqueue({
259
- packageId: normalizedDependencyId,
260
- parentPackageRoot: roots.packageRoot,
261
- traverseDependencies: true
262
- });
263
- }
218
+ for (const dependencyId of dependencyIds) {
219
+ enqueue({ packageId: dependencyId, parentPackageRoot: roots.packageRoot });
264
220
  }
265
221
 
266
222
  const packageMetadata = createPackageMetadata(packageJson);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/kernel",
3
- "version": "0.1.245",
3
+ "version": "0.1.246",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "json-rest-schema": "^1.0.17"
@@ -25,18 +25,16 @@
25
25
  "./client/moduleBootstrap": "./client/moduleBootstrap.js",
26
26
  "./client/vite": "./client/vite/index.js",
27
27
  "./server/actions": "./server/actions/index.js",
28
+ "./server/actions/actionContributorHelpers": "./server/actions/actionContributorHelpers.js",
28
29
  "./server/features": "./server/features/index.js",
29
30
  "./server/http": "./server/http/index.js",
30
31
  "./server/platform": "./server/platform/index.js",
31
32
  "./server/runtime": "./server/runtime/index.js",
32
- "./server/runtime/entityChangeEvents": "./server/runtime/entityChangeEvents.js",
33
33
  "./server/runtime/errors": "./server/runtime/errors.js",
34
34
  "./server/runtime/requestUrl": "./server/runtime/requestUrl.js",
35
35
  "./server/support": "./server/support/index.js",
36
36
  "./shared": "./shared/index.js",
37
37
  "./shared/capabilities": "./shared/capabilities/index.js",
38
- "./shared/actions/actionContributorHelpers": "./shared/actions/actionContributorHelpers.js",
39
- "./shared/actions/textNormalization": "./shared/actions/textNormalization.js",
40
38
  "./shared/validators": "./shared/validators/index.js",
41
39
  "./shared/validators/inputNormalization": "./shared/validators/inputNormalization.js",
42
40
  "./shared/support": "./shared/support/index.js",
@@ -1,4 +1,4 @@
1
- import { createActionRegistry } from "../../shared/actions/registry.js";
1
+ import { createActionRegistry } from "./registry.js";
2
2
  import { isRecord } from "../../shared/support/normalize.js";
3
3
 
4
4
  function normalizeRegistration(value) {
@@ -9,21 +9,12 @@ function normalizeRegistration(value) {
9
9
  return value;
10
10
  }
11
11
 
12
- function createActionCatalogue({
13
- idempotencyAdapter,
14
- auditAdapter,
15
- observabilityAdapter,
16
- events,
17
- logger = null
18
- } = {}) {
12
+ function createActionCatalogue({ events, logger = null } = {}) {
19
13
  const contributors = [];
20
14
  const contextContributors = [];
21
15
  const contextContributorIds = new Set();
22
16
  let registry = createActionRegistry({
23
17
  contributors,
24
- idempotencyAdapter,
25
- auditAdapter,
26
- observabilityAdapter,
27
18
  events,
28
19
  logger
29
20
  });
@@ -36,9 +27,6 @@ function createActionCatalogue({
36
27
  const nextContributors = [...contributors, normalizeRegistration(contributor)];
37
28
  const nextRegistry = createActionRegistry({
38
29
  contributors: nextContributors,
39
- idempotencyAdapter,
40
- auditAdapter,
41
- observabilityAdapter,
42
30
  events,
43
31
  logger
44
32
  });
@@ -104,40 +92,31 @@ function createActionCatalogue({
104
92
  return Object.freeze(enriched);
105
93
  }
106
94
 
107
- async function execute(payload = {}) {
95
+ async function run(method, payload = {}) {
108
96
  const source = payload && typeof payload === "object" ? payload : {};
109
97
  const input = source.input && typeof source.input === "object" ? source.input : {};
98
+ const version = source.version == null ? null : source.version;
110
99
  const context = await buildExecutionContext({
111
100
  actionId: source.actionId,
112
- version: source.version == null ? null : source.version,
101
+ version,
113
102
  input,
114
103
  context: source.context
115
104
  });
116
- return currentRegistry().execute({
105
+ return currentRegistry()[method]({
117
106
  actionId: source.actionId,
118
- version: source.version == null ? null : source.version,
107
+ version,
119
108
  input,
120
109
  context,
121
110
  deps: isRecord(source.deps) ? source.deps : {}
122
111
  });
123
112
  }
124
113
 
125
- async function executeStream(payload = {}) {
126
- const source = payload && typeof payload === "object" ? payload : {};
127
- const input = source.input && typeof source.input === "object" ? source.input : {};
128
- const context = await buildExecutionContext({
129
- actionId: source.actionId,
130
- version: source.version == null ? null : source.version,
131
- input,
132
- context: source.context
133
- });
134
- return currentRegistry().executeStream({
135
- actionId: source.actionId,
136
- version: source.version == null ? null : source.version,
137
- input,
138
- context,
139
- deps: isRecord(source.deps) ? source.deps : {}
140
- });
114
+ function execute(payload) {
115
+ return run("execute", payload);
116
+ }
117
+
118
+ function executeStream(payload) {
119
+ return run("executeStream", payload);
141
120
  }
142
121
 
143
122
  function listDefinitions() {
@@ -1,6 +1,4 @@
1
1
  import { createSchema } from "json-rest-schema";
2
- import { normalizeObject, normalizePositiveInteger as toPositiveInteger } from "../support/normalize.js";
3
- import { hasPermission } from "../support/permissions.js";
4
2
 
5
3
  function requireServiceMethod(service, methodName, contributorId, { serviceLabel } = {}) {
6
4
  if (!service || typeof service[methodName] !== "function") {
@@ -19,10 +17,7 @@ const emptyInputValidator = Object.freeze({
19
17
  });
20
18
 
21
19
  export {
22
- normalizeObject,
23
- toPositiveInteger,
24
20
  requireServiceMethod,
25
21
  resolveRequest,
26
- hasPermission,
27
22
  emptyInputValidator
28
23
  };
@@ -1,9 +1,7 @@
1
- import {
2
- normalizeSingleSchemaDefinition
3
- } from "../validators/index.js";
4
- import { isRecord as isPlainObject, normalizePositiveInteger } from "../support/normalize.js";
5
- import { normalizePermissionList } from "../support/permissions.js";
6
- import { normalizeText } from "./textNormalization.js";
1
+ import { normalizeSingleSchemaDefinition } from "../../shared/validators/index.js";
2
+ import { isRecord as isPlainObject, normalizePositiveInteger, normalizeText } from "../../shared/support/normalize.js";
3
+ import { normalizePermissionList } from "../../shared/support/permissions.js";
4
+ import { ARCHITECTURE_ID_PATTERN } from "../../shared/capabilities/defineProvider.js";
7
5
 
8
6
  const ACTION_KINDS = Object.freeze(["query", "command", "stream"]);
9
7
  const ACTION_IDEMPOTENCY_POLICIES = Object.freeze(["none", "optional", "required", "domain_native"]);
@@ -12,7 +10,6 @@ const ACTION_PERMISSION_REQUIRE_MODES = Object.freeze(["none", "authenticated",
12
10
  const ACTION_KIND_SET = new Set(ACTION_KINDS);
13
11
  const ACTION_IDEMPOTENCY_SET = new Set(ACTION_IDEMPOTENCY_POLICIES);
14
12
  const ACTION_PERMISSION_REQUIRE_SET = new Set(ACTION_PERMISSION_REQUIRE_MODES);
15
- const ACTION_DOMAIN_PATTERN = /^[a-z][a-z0-9_.-]*$/;
16
13
 
17
14
  class ActionRuntimeError extends Error {
18
15
  constructor(status, message, options = {}) {
@@ -38,10 +35,10 @@ function normalizeActionDomain(value, { context = "domain", errorCode = "ACTION_
38
35
  code: errorCode
39
36
  });
40
37
  }
41
- if (!ACTION_DOMAIN_PATTERN.test(domain)) {
38
+ if (!ARCHITECTURE_ID_PATTERN.test(domain)) {
42
39
  throw createActionRuntimeError(
43
40
  500,
44
- `${context} must match ${ACTION_DOMAIN_PATTERN.toString()} (lowercase letters, digits, _, -, .).`,
41
+ `${context} must match ${ARCHITECTURE_ID_PATTERN.toString()} (lowercase letters, digits, _, -, .).`,
45
42
  {
46
43
  code: errorCode
47
44
  }
@@ -51,23 +48,13 @@ function normalizeActionDomain(value, { context = "domain", errorCode = "ACTION_
51
48
  return domain;
52
49
  }
53
50
 
54
- function normalizeStringArray(value, { fieldName, allowedSet, allowEmpty = false } = {}) {
51
+ function normalizeStringArray(value, fieldName) {
55
52
  const source = Array.isArray(value) ? value : [];
56
53
  const normalized = Array.from(
57
- new Set(
58
- source
59
- .map((entry) => normalizeText(entry).toLowerCase())
60
- .filter(Boolean)
61
- .filter((entry) => {
62
- if (!(allowedSet instanceof Set)) {
63
- return true;
64
- }
65
- return allowedSet.has(entry);
66
- })
67
- )
54
+ new Set(source.map((entry) => normalizeText(entry).toLowerCase()).filter(Boolean))
68
55
  );
69
56
 
70
- if (!allowEmpty && normalized.length < 1) {
57
+ if (normalized.length < 1) {
71
58
  throw createActionRuntimeError(500, `Action definition ${fieldName} must include at least one value.`, {
72
59
  code: "ACTION_DEFINITION_INVALID"
73
60
  });
@@ -76,7 +63,7 @@ function normalizeStringArray(value, { fieldName, allowedSet, allowEmpty = false
76
63
  return normalized;
77
64
  }
78
65
 
79
- function normalizeSingleActionSchema(value, fieldName, { required = false, defaultMode = "" } = {}) {
66
+ function normalizeActionSchema(value, fieldName, { required = false, defaultMode = "" } = {}) {
80
67
  if (value == null) {
81
68
  if (!required) {
82
69
  return null;
@@ -87,6 +74,12 @@ function normalizeSingleActionSchema(value, fieldName, { required = false, defau
87
74
  });
88
75
  }
89
76
 
77
+ if (Array.isArray(value)) {
78
+ throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
79
+ code: "ACTION_DEFINITION_INVALID"
80
+ });
81
+ }
82
+
90
83
  if (!isPlainObject(value)) {
91
84
  throw createActionRuntimeError(500, `Action definition ${fieldName} must be a json-rest-schema schema definition.`, {
92
85
  code: "ACTION_DEFINITION_INVALID"
@@ -105,52 +98,6 @@ function normalizeSingleActionSchema(value, fieldName, { required = false, defau
105
98
  }
106
99
  }
107
100
 
108
- function normalizeActionInputDefinition(value, fieldName, { required = false } = {}) {
109
- if (value == null) {
110
- if (!required) {
111
- return null;
112
- }
113
-
114
- throw createActionRuntimeError(500, `Action definition ${fieldName} is required.`, {
115
- code: "ACTION_DEFINITION_INVALID"
116
- });
117
- }
118
-
119
- if (Array.isArray(value)) {
120
- throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
121
- code: "ACTION_DEFINITION_INVALID"
122
- });
123
- }
124
-
125
- return normalizeSingleActionSchema(value, fieldName, {
126
- required,
127
- defaultMode: "patch"
128
- });
129
- }
130
-
131
- function normalizeActionOutputDefinition(value, fieldName, { required = false } = {}) {
132
- if (value == null) {
133
- if (!required) {
134
- return null;
135
- }
136
-
137
- throw createActionRuntimeError(500, `Action definition ${fieldName} is required.`, {
138
- code: "ACTION_DEFINITION_INVALID"
139
- });
140
- }
141
-
142
- if (Array.isArray(value)) {
143
- throw createActionRuntimeError(500, `Action definition ${fieldName} must be a single schema definition.`, {
144
- code: "ACTION_DEFINITION_INVALID"
145
- });
146
- }
147
-
148
- return normalizeSingleActionSchema(value, fieldName, {
149
- required,
150
- defaultMode: "replace"
151
- });
152
- }
153
-
154
101
  function normalizeActionPermission(permission, actionId) {
155
102
  if (permission == null) {
156
103
  return Object.freeze({
@@ -190,42 +137,6 @@ function normalizeActionPermission(permission, actionId) {
190
137
  });
191
138
  }
192
139
 
193
- function normalizeAuditConfig(audit, { actionId }) {
194
- const source = audit && typeof audit === "object" ? audit : {};
195
- const actionName = normalizeText(source.actionName || actionId);
196
- const metadataBuilder = source.metadataBuilder;
197
- const piiTags = normalizeStringArray(source.piiTags, {
198
- fieldName: "audit.piiTags",
199
- allowEmpty: true
200
- });
201
-
202
- if (metadataBuilder != null && typeof metadataBuilder !== "function") {
203
- throw createActionRuntimeError(500, "Action definition audit.metadataBuilder must be a function when provided.", {
204
- code: "ACTION_DEFINITION_INVALID"
205
- });
206
- }
207
-
208
- return Object.freeze({
209
- actionName,
210
- metadataBuilder: typeof metadataBuilder === "function" ? metadataBuilder : null,
211
- piiTags
212
- });
213
- }
214
-
215
- function normalizeObservabilityConfig(observability) {
216
- const source = observability && typeof observability === "object" ? observability : {};
217
- const metricTags = normalizeStringArray(source.metricTags, {
218
- fieldName: "observability.metricTags",
219
- allowEmpty: true
220
- });
221
- const sampleRate = Number(source.sampleRate);
222
-
223
- return Object.freeze({
224
- metricTags,
225
- sampleRate: Number.isFinite(sampleRate) && sampleRate >= 0 && sampleRate <= 1 ? sampleRate : null
226
- });
227
- }
228
-
229
140
  function normalizeActionExtensions(value) {
230
141
  if (value == null) {
231
142
  return Object.freeze({});
@@ -286,13 +197,8 @@ function normalizeActionDefinition(definition, { contributorId = "", contributor
286
197
  });
287
198
  }
288
199
 
289
- const channels = normalizeStringArray(source.channels, {
290
- fieldName: "channels"
291
- });
292
-
293
- const surfaces = normalizeStringArray(source.surfaces, {
294
- fieldName: "surfaces"
295
- });
200
+ const channels = normalizeStringArray(source.channels, "channels");
201
+ const surfaces = normalizeStringArray(source.surfaces, "surfaces");
296
202
 
297
203
  const idempotency = normalizeText(source.idempotency || "none").toLowerCase();
298
204
  if (!ACTION_IDEMPOTENCY_SET.has(idempotency)) {
@@ -318,18 +224,15 @@ function normalizeActionDefinition(definition, { contributorId = "", contributor
318
224
  kind,
319
225
  channels,
320
226
  surfaces,
321
- input: normalizeActionInputDefinition(source.input, "input", {
322
- required: true
227
+ input: normalizeActionSchema(source.input, "input", {
228
+ required: true,
229
+ defaultMode: "patch"
323
230
  }),
324
- output: normalizeActionOutputDefinition(source.output, "output", {
325
- required: false
231
+ output: normalizeActionSchema(source.output, "output", {
232
+ defaultMode: "replace"
326
233
  }),
327
234
  idempotency,
328
235
  permission: normalizeActionPermission(source.permission, id),
329
- audit: normalizeAuditConfig(source.audit, {
330
- actionId: id
331
- }),
332
- observability: normalizeObservabilityConfig(source.observability),
333
236
  extensions: normalizeActionExtensions(source.extensions),
334
237
  events: normalizeActionEvents(source.events, id),
335
238
  execute: source.execute,
@@ -371,15 +274,7 @@ function createActionVersionKey(actionId, version) {
371
274
  }
372
275
 
373
276
  const __testables = {
374
- normalizeText,
375
- isPlainObject,
376
- normalizeStringArray,
377
- normalizeSingleActionSchema,
378
- normalizeActionInputDefinition,
379
- normalizeActionOutputDefinition,
380
- normalizeActionPermission,
381
- normalizeAuditConfig,
382
- normalizeObservabilityConfig,
277
+ normalizeActionSchema,
383
278
  normalizeActionExtensions
384
279
  };
385
280
 
@@ -388,8 +283,6 @@ export {
388
283
  createActionRuntimeError,
389
284
  normalizeActionDefinition,
390
285
  normalizeActionContributor,
391
- normalizeActionDomain,
392
286
  createActionVersionKey,
393
- isPlainObject,
394
287
  __testables
395
288
  };
@@ -56,34 +56,34 @@ function createPatchSchema() {
56
56
  };
57
57
  }
58
58
 
59
- test("normalizeActionInputDefinition accepts a single schema definition", () => {
60
- const definition = __testables.normalizeActionInputDefinition(
59
+ test("normalizeActionSchema accepts a single schema definition", () => {
60
+ const definition = __testables.normalizeActionSchema(
61
61
  createPatchSchema(),
62
62
  "input",
63
- { required: true }
63
+ { required: true, defaultMode: "patch" }
64
64
  );
65
65
 
66
66
  assertJsonRestSchemaDefinition(definition);
67
67
  });
68
68
 
69
- test("normalizeActionInputDefinition rejects section-map syntax", () => {
69
+ test("normalizeActionSchema rejects section-map syntax", () => {
70
70
  assert.throws(
71
71
  () =>
72
- __testables.normalizeActionInputDefinition(
72
+ __testables.normalizeActionSchema(
73
73
  {
74
74
  payload: createPatchSchema()
75
75
  },
76
76
  "input",
77
- { required: true }
77
+ { required: true, defaultMode: "patch" }
78
78
  ),
79
79
  /Action definition input must be a schema definition object/
80
80
  );
81
81
  });
82
82
 
83
- test("normalizeActionInputDefinition rejects bare schema instances", () => {
83
+ test("normalizeActionSchema rejects bare schema instances", () => {
84
84
  assert.throws(
85
85
  () =>
86
- __testables.normalizeActionInputDefinition(
86
+ __testables.normalizeActionSchema(
87
87
  createSchema({
88
88
  name: {
89
89
  type: "string",
@@ -91,22 +91,22 @@ test("normalizeActionInputDefinition rejects bare schema instances", () => {
91
91
  }
92
92
  }),
93
93
  "input",
94
- { required: true }
94
+ { required: true, defaultMode: "patch" }
95
95
  ),
96
96
  /Action definition input must be a schema definition object/
97
97
  );
98
98
  });
99
99
 
100
- test("normalizeActionInputDefinition rejects validator arrays", () => {
100
+ test("normalizeActionSchema rejects validator arrays", () => {
101
101
  assert.throws(
102
102
  () =>
103
- __testables.normalizeActionInputDefinition(
103
+ __testables.normalizeActionSchema(
104
104
  [
105
105
  createWorkspaceSlugSchema(),
106
106
  createPatchSchema()
107
107
  ],
108
108
  "input",
109
- { required: true }
109
+ { required: true, defaultMode: "patch" }
110
110
  ),
111
111
  /input must be a single schema definition/
112
112
  );
@@ -148,8 +148,8 @@ test("normalizeActionDefinition stays channel-agnostic and ignores unknown extra
148
148
  assert.equal(Object.prototype.hasOwnProperty.call(definition, "customExtension"), false);
149
149
  });
150
150
 
151
- test("normalizeActionOutputDefinition accepts single schema definitions", () => {
152
- const output = __testables.normalizeActionOutputDefinition(
151
+ test("normalizeActionSchema accepts single schema definitions", () => {
152
+ const output = __testables.normalizeActionSchema(
153
153
  {
154
154
  schema: createSchema({
155
155
  ok: {
@@ -159,35 +159,89 @@ test("normalizeActionOutputDefinition accepts single schema definitions", () =>
159
159
  })
160
160
  },
161
161
  "output",
162
- { required: false }
162
+ { defaultMode: "replace" }
163
163
  );
164
164
 
165
165
  assertJsonRestSchemaDefinition(output);
166
166
  });
167
167
 
168
- test("normalizeActionOutputDefinition rejects section-map syntax", () => {
168
+ test("normalizeActionSchema rejects section-map syntax", () => {
169
169
  assert.throws(
170
170
  () =>
171
- __testables.normalizeActionOutputDefinition(
171
+ __testables.normalizeActionSchema(
172
172
  {
173
173
  payload: createPatchSchema()
174
174
  },
175
175
  "output",
176
- { required: false }
176
+ { defaultMode: "replace" }
177
177
  ),
178
178
  /Action definition output must be a schema definition object/
179
179
  );
180
180
  });
181
181
 
182
- test("normalizeActionInputDefinition preserves mode for json-rest-schema definitions", () => {
183
- const definition = __testables.normalizeActionInputDefinition(
182
+ test("normalizeActionSchema preserves mode for json-rest-schema definitions", () => {
183
+ const definition = __testables.normalizeActionSchema(
184
184
  {
185
185
  schema: createMockJsonRestSchema(),
186
186
  mode: "patch"
187
187
  },
188
188
  "input",
189
- { required: true }
189
+ { required: true, defaultMode: "patch" }
190
190
  );
191
191
 
192
192
  assert.equal(definition.mode, "patch");
193
193
  });
194
+
195
+ test("normalizeActionSchema rejects a missing required schema and allows a missing optional one", () => {
196
+ assert.equal(__testables.normalizeActionSchema(null, "output"), null);
197
+ assert.throws(
198
+ () => __testables.normalizeActionSchema(undefined, "input", { required: true }),
199
+ /Action definition input is required/
200
+ );
201
+ });
202
+
203
+ test("normalizeActionDefinition keeps only the contract the runtime enforces", () => {
204
+ const definition = normalizeActionDefinition({
205
+ id: "demo.status.read",
206
+ domain: "demo",
207
+ channels: ["api"],
208
+ surfaces: ["app"],
209
+ input: { schema: createSchema({}) },
210
+ audit: { actionName: "demo.status.read" },
211
+ observability: {},
212
+ execute: async () => ({})
213
+ });
214
+
215
+ assert.equal(Object.hasOwn(definition, "audit"), false);
216
+ assert.equal(Object.hasOwn(definition, "observability"), false);
217
+ assert.deepEqual(Object.keys(definition).sort(), [
218
+ "channels",
219
+ "contributorId",
220
+ "domain",
221
+ "events",
222
+ "execute",
223
+ "extensions",
224
+ "id",
225
+ "idempotency",
226
+ "input",
227
+ "kind",
228
+ "output",
229
+ "permission",
230
+ "surfaces",
231
+ "version"
232
+ ]);
233
+ });
234
+
235
+ test("normalizeActionDefinition validates domains with the architecture id pattern", () => {
236
+ const base = {
237
+ id: "demo.status.read",
238
+ channels: ["api"],
239
+ surfaces: ["app"],
240
+ input: { schema: createSchema({}) },
241
+ execute: async () => ({})
242
+ };
243
+ assert.equal(normalizeActionDefinition({ ...base, domain: " Demo.Status " }).domain, "demo.status");
244
+ assert.throws(() => normalizeActionDefinition({ ...base, domain: "1demo" }), {
245
+ code: "ACTION_DEFINITION_INVALID"
246
+ });
247
+ });