@google-cloud/kcmd 0.0.0-stage → 0.1.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 (150) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +266 -2
  3. package/build/ts/kcmd/cli.js +19578 -0
  4. package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
  5. package/build/ts/kcmd/gcp/alloydb.js +70 -0
  6. package/build/ts/kcmd/gcp/api.d.ts +29 -0
  7. package/build/ts/kcmd/gcp/api.js +162 -0
  8. package/build/ts/kcmd/gcp/biglake.d.ts +16 -0
  9. package/build/ts/kcmd/gcp/biglake.js +50 -0
  10. package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
  11. package/build/ts/kcmd/gcp/bigquery.js +134 -0
  12. package/build/ts/kcmd/gcp/context.d.ts +10 -0
  13. package/build/ts/kcmd/gcp/context.js +74 -0
  14. package/build/ts/kcmd/gcp/crm.d.ts +13 -0
  15. package/build/ts/kcmd/gcp/crm.js +91 -0
  16. package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
  17. package/build/ts/kcmd/gcp/dataplex.js +406 -0
  18. package/build/ts/kcmd/gcp/index.d.ts +3 -0
  19. package/build/ts/kcmd/gcp/index.js +21 -0
  20. package/build/ts/kcmd/gcp/spanner.d.ts +63 -0
  21. package/build/ts/kcmd/gcp/spanner.js +123 -0
  22. package/build/ts/kcmd/index.d.ts +7 -0
  23. package/build/ts/kcmd/index.js +46 -0
  24. package/build/ts/kcmd/layout.d.ts +16 -0
  25. package/build/ts/kcmd/layout.js +31 -0
  26. package/build/ts/kcmd/layouts/documents.d.ts +24 -0
  27. package/build/ts/kcmd/layouts/documents.js +264 -0
  28. package/build/ts/kcmd/layouts/okf.d.ts +31 -0
  29. package/build/ts/kcmd/layouts/okf.js +344 -0
  30. package/build/ts/kcmd/layouts/semantic-model.d.ts +61 -0
  31. package/build/ts/kcmd/layouts/semantic-model.js +542 -0
  32. package/build/ts/kcmd/layouts/standard.d.ts +13 -0
  33. package/build/ts/kcmd/layouts/standard.js +108 -0
  34. package/build/ts/kcmd/manifest.d.ts +34 -0
  35. package/build/ts/kcmd/manifest.js +262 -0
  36. package/build/ts/kcmd/metadata.d.ts +25 -0
  37. package/build/ts/kcmd/metadata.js +8 -0
  38. package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
  39. package/build/ts/kcmd/okf/catalog_residue.js +124 -0
  40. package/build/ts/kcmd/okf/concept.d.ts +14 -0
  41. package/build/ts/kcmd/okf/concept.js +104 -0
  42. package/build/ts/kcmd/okf/concept_to_entry.d.ts +8 -0
  43. package/build/ts/kcmd/okf/concept_to_entry.js +254 -0
  44. package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
  45. package/build/ts/kcmd/okf/init_okf_workspace.js +115 -0
  46. package/build/ts/kcmd/okf/links.d.ts +23 -0
  47. package/build/ts/kcmd/okf/links.js +468 -0
  48. package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
  49. package/build/ts/kcmd/okf/okf_metadata.js +82 -0
  50. package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
  51. package/build/ts/kcmd/semantic/bigquery.js +525 -0
  52. package/build/ts/kcmd/semantic/binding.d.ts +3 -0
  53. package/build/ts/kcmd/semantic/binding.js +97 -0
  54. package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
  55. package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
  56. package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
  57. package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
  58. package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
  59. package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
  60. package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
  61. package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
  62. package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
  63. package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
  64. package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
  65. package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
  66. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +129 -0
  67. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1396 -0
  68. package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
  69. package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
  70. package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
  71. package/build/ts/kcmd/semantic/deployment_target.js +379 -0
  72. package/build/ts/kcmd/semantic/graph_ddl.d.ts +34 -0
  73. package/build/ts/kcmd/semantic/graph_ddl.js +563 -0
  74. package/build/ts/kcmd/semantic/import_model.d.ts +50 -0
  75. package/build/ts/kcmd/semantic/import_model.js +1275 -0
  76. package/build/ts/kcmd/semantic/ir.d.ts +589 -0
  77. package/build/ts/kcmd/semantic/ir.js +247 -0
  78. package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
  79. package/build/ts/kcmd/semantic/kc_actions.js +531 -0
  80. package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
  81. package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
  82. package/build/ts/kcmd/semantic/kc_converter.d.ts +31 -0
  83. package/build/ts/kcmd/semantic/kc_converter.js +824 -0
  84. package/build/ts/kcmd/semantic/kc_custom_types.d.ts +70 -0
  85. package/build/ts/kcmd/semantic/kc_custom_types.js +828 -0
  86. package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
  87. package/build/ts/kcmd/semantic/kc_entries.js +284 -0
  88. package/build/ts/kcmd/semantic/kc_ids.d.ts +103 -0
  89. package/build/ts/kcmd/semantic/kc_ids.js +191 -0
  90. package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +32 -0
  91. package/build/ts/kcmd/semantic/knowledge_catalog.js +1043 -0
  92. package/build/ts/kcmd/semantic/loader.d.ts +262 -0
  93. package/build/ts/kcmd/semantic/loader.js +3440 -0
  94. package/build/ts/kcmd/semantic/names.d.ts +10 -0
  95. package/build/ts/kcmd/semantic/names.js +85 -0
  96. package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
  97. package/build/ts/kcmd/semantic/osi_converter.js +911 -0
  98. package/build/ts/kcmd/semantic/parameters.d.ts +18 -0
  99. package/build/ts/kcmd/semantic/parameters.js +152 -0
  100. package/build/ts/kcmd/semantic/pull_kc.d.ts +14 -0
  101. package/build/ts/kcmd/semantic/pull_kc.js +346 -0
  102. package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +54 -0
  103. package/build/ts/kcmd/semantic/resolve_inheritance.js +330 -0
  104. package/build/ts/kcmd/semantic/resolve_profiles.d.ts +250 -0
  105. package/build/ts/kcmd/semantic/resolve_profiles.js +1822 -0
  106. package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
  107. package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
  108. package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
  109. package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
  110. package/build/ts/kcmd/semantic/runtime/runtime.d.ts +94 -0
  111. package/build/ts/kcmd/semantic/runtime/runtime.js +318 -0
  112. package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
  113. package/build/ts/kcmd/semantic/runtime/store.js +344 -0
  114. package/build/ts/kcmd/semantic/skills.d.ts +110 -0
  115. package/build/ts/kcmd/semantic/skills.js +697 -0
  116. package/build/ts/kcmd/semantic/source_dialect.d.ts +10 -0
  117. package/build/ts/kcmd/semantic/source_dialect.js +261 -0
  118. package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
  119. package/build/ts/kcmd/semantic/source_entries.js +282 -0
  120. package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
  121. package/build/ts/kcmd/semantic/spanner.js +60 -0
  122. package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +17 -0
  123. package/build/ts/kcmd/semantic/sql_expr_utils.js +221 -0
  124. package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
  125. package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
  126. package/build/ts/kcmd/semantic/sql_parser.d.ts +44 -0
  127. package/build/ts/kcmd/semantic/sql_parser.js +378 -0
  128. package/build/ts/kcmd/semantic/transpile.d.ts +74 -0
  129. package/build/ts/kcmd/semantic/transpile.js +563 -0
  130. package/build/ts/kcmd/semantic/validate.d.ts +35 -0
  131. package/build/ts/kcmd/semantic/validate.js +1899 -0
  132. package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
  133. package/build/ts/kcmd/semantic/yaml_options.js +58 -0
  134. package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
  135. package/build/ts/kcmd/semantic/yaml_update.js +782 -0
  136. package/build/ts/kcmd/snapshot.d.ts +33 -0
  137. package/build/ts/kcmd/snapshot.js +379 -0
  138. package/build/ts/kcmd/source.d.ts +31 -0
  139. package/build/ts/kcmd/source.js +119 -0
  140. package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
  141. package/build/ts/kcmd/sources/bq-dataset.js +131 -0
  142. package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
  143. package/build/ts/kcmd/sources/entrygroup.js +83 -0
  144. package/build/ts/kcmd/sources/kb.d.ts +21 -0
  145. package/build/ts/kcmd/sources/kb.js +82 -0
  146. package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
  147. package/build/ts/kcmd/sources/semantic-model.js +48 -0
  148. package/build/ts/kcmd/sync.d.ts +35 -0
  149. package/build/ts/kcmd/sync.js +697 -0
  150. package/package.json +62 -4
@@ -0,0 +1,828 @@
1
+ "use strict";
2
+ // The entry and aspect types kcmd creates itself.
3
+ //
4
+ // READ THIS FIRST IF YOU OWN DATAPLEX TYPES. Everything a semantic model
5
+ // publishes maps to a BUILT-IN system type under
6
+ // `projects/dataplex-types/locations/global`: a model, an entity and a metric
7
+ // each get an entry (`semantic-model` / `semantic-entity` / `semantic-metric`),
8
+ // a relationship becomes a `schema-join` entry link, and the built-in `schema`
9
+ // and `guidelines` aspects carry the rest. The types listed in CUSTOM_TYPES
10
+ // below are the exceptions. Dataplex has no built-in equivalent for them, so
11
+ // `kcmd init --semantic-model` creates them in the destination project instead.
12
+ // Each record here is a request: please make this one built-in.
13
+ //
14
+ // TO MAKE ONE BUILT-IN. Publish an entry type and an aspect type under
15
+ // `dataplex-types/global` with the same id and the same metadata template as
16
+ // the record below, then delete that record from CUSTOM_TYPES and name the
17
+ // type through `Namer.typeName` in knowledge_catalog.ts, the way every built-in
18
+ // type is already named. Nothing else moves: entry ids, entry shape, the owned
19
+ // prefix for delete reconciliation, and the readers all match a type by its id
20
+ // suffix, so they do not care which project it lives in.
21
+ //
22
+ // TO ADD A NEW CUSTOM TYPE. Append a record. Provisioning, naming and the init
23
+ // wiring are generic over this list, so no other file in this directory needs
24
+ // to change; what does need writing is the encoding that fills the aspect, the
25
+ // way kc_actions.ts does for `semantic-action` and kc_constraints.ts for
26
+ // `semantic-constraint`.
27
+ //
28
+ // A CUSTOM TYPE IS ONE ENTRY TYPE PLUS ONE ASPECT TYPE that share an id, the
29
+ // way the built-in `semantic-metric` entry type and aspect type share theirs.
30
+ // The entry type requires its own aspect type, so an entry of that type cannot
31
+ // exist without the aspect that gives it meaning. A future type that needs a
32
+ // different arrangement is a reason to generalize this file rather than to add
33
+ // a second mechanism elsewhere.
34
+ //
35
+ // TEMPLATES ARE APPEND-ONLY. Dataplex rejects a backwards-incompatible template
36
+ // change, so a new field must be APPENDED with a fresh index. Renumbering or
37
+ // removing an existing field breaks the update for every project that already
38
+ // provisioned the type.
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.CUSTOM_TYPES = exports.PUSH_ID_LABEL = exports.FILE_COPY_TYPE_ID = exports.CONSTRAINT_TYPE_ID = exports.ACTION_TYPE_ID = void 0;
41
+ exports.aiContextField = aiContextField;
42
+ exports.aiContextAspectValue = aiContextAspectValue;
43
+ exports.aiContextFromAspect = aiContextFromAspect;
44
+ exports.isCustomType = isCustomType;
45
+ exports.collectCustomTypes = collectCustomTypes;
46
+ exports.customTypeHome = customTypeHome;
47
+ exports.customEntryTypeName = customEntryTypeName;
48
+ exports.customAspectTypeName = customAspectTypeName;
49
+ exports.customAspectKey = customAspectKey;
50
+ exports.provisionCustomTypes = provisionCustomTypes;
51
+ const kc_ids_1 = require("./kc_ids");
52
+ // ---------------------------------------------------------------------------
53
+ // `ai_context`, shared by every custom aspect.
54
+ // ---------------------------------------------------------------------------
55
+ // The whole of a model element's `ai_context`, as one template field.
56
+ //
57
+ // Every element in the format carries the same three-part annotation:
58
+ // instructions, synonyms, examples. A custom aspect carries all three. Carrying
59
+ // a chosen part instead would mean a document that loads cleanly comes back
60
+ // from a pull missing fields it declared. The built-in `guidelines` aspect does
61
+ // have a home for `instructions` alone, and an element routed there loses the
62
+ // other two, but kcmd owns this template and has no reason to inherit that
63
+ // limit.
64
+ //
65
+ // The annotation goes on the custom aspect rather than on a `guidelines` aspect
66
+ // beside it, because a pull hydrates aspect types from the project the ENTRY
67
+ // type lives in, and `guidelines` is published under `dataplex-types`.
68
+ //
69
+ // `index` is a parameter because template field indexes are positional and
70
+ // append-only (see the header), so each aspect places this field wherever its
71
+ // own template has room. `semantic-action` still carries a flat `instructions`
72
+ // string at index 9. That type was provisioned before this field existed, and
73
+ // renaming a template field is the backwards-incompatible change Dataplex
74
+ // rejects, so it moves over by APPENDING this field and retiring that one.
75
+ function aiContextField(index) {
76
+ return {
77
+ index,
78
+ name: 'aiContext',
79
+ type: 'record',
80
+ recordFields: [
81
+ {
82
+ index: 1,
83
+ name: 'instructions',
84
+ type: 'string',
85
+ annotations: {
86
+ displayName: 'Instructions',
87
+ description: 'Free-form guidance for AI consumers.',
88
+ },
89
+ },
90
+ {
91
+ index: 2,
92
+ name: 'synonyms',
93
+ type: 'array',
94
+ arrayItems: { name: 'synonym', type: 'string' },
95
+ annotations: {
96
+ displayName: 'Synonyms',
97
+ description: 'Alternate names for the annotated object.',
98
+ },
99
+ },
100
+ {
101
+ index: 3,
102
+ name: 'examples',
103
+ type: 'array',
104
+ arrayItems: { name: 'example', type: 'string' },
105
+ annotations: {
106
+ displayName: 'Examples',
107
+ description: 'Example questions or usages illustrating the annotated object.',
108
+ },
109
+ },
110
+ ],
111
+ annotations: {
112
+ displayName: 'AI Context',
113
+ description: 'The element\'s `ai_context`: guidance, alternate names and ' +
114
+ 'examples for AI consumers.',
115
+ },
116
+ };
117
+ }
118
+ // The aspect value for an `ai_context`. Undefined when it carries nothing, so
119
+ // an element without one attaches no empty record and its golden stays quiet.
120
+ function aiContextAspectValue(ai) {
121
+ if (!ai)
122
+ return undefined;
123
+ const out = {};
124
+ if (ai.instructions)
125
+ out.instructions = ai.instructions;
126
+ if (ai.synonyms?.length)
127
+ out.synonyms = [...ai.synonyms];
128
+ if (ai.examples?.length)
129
+ out.examples = [...ai.examples];
130
+ return Object.keys(out).length ? out : undefined;
131
+ }
132
+ // The inverse: an `ai_context` from an aspect value, or undefined when there is
133
+ // nothing usable in it. Every part is checked rather than trusted, because an
134
+ // aspect can be hand-edited in the catalog into a shape the emitter never
135
+ // writes.
136
+ function aiContextFromAspect(value) {
137
+ if (!value || typeof value !== 'object')
138
+ return undefined;
139
+ const strings = (v) => {
140
+ if (!Array.isArray(v))
141
+ return undefined;
142
+ const out = v.filter((s) => typeof s === 'string' && s !== '');
143
+ return out.length ? out : undefined;
144
+ };
145
+ const ai = {};
146
+ if (typeof value.instructions === 'string' && value.instructions !== '')
147
+ ai.instructions = value.instructions;
148
+ const synonyms = strings(value.synonyms);
149
+ if (synonyms)
150
+ ai.synonyms = synonyms;
151
+ const examples = strings(value.examples);
152
+ if (examples)
153
+ ai.examples = examples;
154
+ return Object.keys(ai).length ? ai : undefined;
155
+ }
156
+ // The id of the action type. An action is a write operation defined on a
157
+ // semantic model; kc_actions.ts holds the encoding that fills its aspect.
158
+ exports.ACTION_TYPE_ID = 'semantic-action';
159
+ // The aspect that carries an action's mechanics.
160
+ //
161
+ // The executor is FLATTENED rather than nested one record per kind: an aspect
162
+ // holds exactly one executor, and a flat record shows the reader which one at a
163
+ // glance instead of three sibling records of which two are empty. `parameters`
164
+ // mirrors the model's own parameter list. Every parameter carries a scalar
165
+ // `type`, and one projected from a field also names the `concept` and `field`
166
+ // it came from -- so a consumer that has not loaded the model can bind a value
167
+ // from the type alone, and follow the projection to the definition when it
168
+ // wants to know what the value MEANS.
169
+ const ACTION_ASPECT_TYPE = {
170
+ displayName: 'Semantic Action',
171
+ description: 'A write operation defined on a semantic model: how it is executed, ' +
172
+ 'and the inputs it takes.',
173
+ metadataTemplate: {
174
+ name: exports.ACTION_TYPE_ID,
175
+ type: 'record',
176
+ recordFields: [
177
+ {
178
+ // NOT required: an executor is a physical binding, so an action no
179
+ // binding performs here publishes with none. The rest of the record --
180
+ // the parameters, the guards, what it changes -- is the declaration,
181
+ // and that is what the catalog is for.
182
+ index: 1,
183
+ name: 'executorKind',
184
+ type: 'string',
185
+ annotations: {
186
+ displayName: 'Executor Kind',
187
+ description: 'How the action is executed: `mcp`, `rest`, `grpc`, or `sql`. ' +
188
+ 'The fields for that kind, and only those, are set. Absent ' +
189
+ 'when no binding supplies an executor.',
190
+ },
191
+ },
192
+ {
193
+ index: 2,
194
+ name: 'mcpServer',
195
+ type: 'string',
196
+ annotations: {
197
+ displayName: 'MCP Server',
198
+ description: 'Resource name of the MCP server hosting the tool.',
199
+ },
200
+ },
201
+ {
202
+ index: 3,
203
+ name: 'mcpTool',
204
+ type: 'string',
205
+ annotations: {
206
+ displayName: 'MCP Tool',
207
+ description: 'Name of the tool to call on the MCP server.',
208
+ },
209
+ },
210
+ {
211
+ index: 4,
212
+ name: 'restEndpoint',
213
+ type: 'string',
214
+ annotations: {
215
+ displayName: 'REST Endpoint',
216
+ description: 'URL the request is sent to.',
217
+ },
218
+ },
219
+ {
220
+ index: 5,
221
+ name: 'restMethod',
222
+ type: 'string',
223
+ annotations: {
224
+ displayName: 'REST Method',
225
+ description: 'HTTP method, for example POST.',
226
+ },
227
+ },
228
+ {
229
+ index: 6,
230
+ name: 'grpcService',
231
+ type: 'string',
232
+ annotations: {
233
+ displayName: 'gRPC Service',
234
+ description: 'Fully qualified name of the gRPC service.',
235
+ },
236
+ },
237
+ {
238
+ index: 7,
239
+ name: 'grpcMethod',
240
+ type: 'string',
241
+ annotations: {
242
+ displayName: 'gRPC Method',
243
+ description: 'Method on the gRPC service.',
244
+ },
245
+ },
246
+ {
247
+ index: 8,
248
+ name: 'parameters',
249
+ type: 'array',
250
+ arrayItems: {
251
+ name: 'parameter',
252
+ type: 'record',
253
+ recordFields: [
254
+ {
255
+ index: 1,
256
+ name: 'name',
257
+ type: 'string',
258
+ constraints: { required: true },
259
+ annotations: { displayName: 'Name' },
260
+ },
261
+ {
262
+ index: 2,
263
+ name: 'type',
264
+ type: 'string',
265
+ constraints: { required: true },
266
+ annotations: {
267
+ displayName: 'Type',
268
+ description: 'The scalar datatype of the value. Stated by ' +
269
+ 'the parameter, or taken from the field it projects.',
270
+ },
271
+ },
272
+ {
273
+ index: 3,
274
+ name: 'isEntityRef',
275
+ type: 'bool',
276
+ annotations: {
277
+ displayName: 'Is Entity Reference (reserved)',
278
+ description: 'Reserved and never written. It marked a parameter whose ' +
279
+ '`type` named an entity, so the parameter referred to an ' +
280
+ 'object rather than carrying a value. Those parameters ' +
281
+ 'were removed; every parameter now carries a scalar, and ' +
282
+ 'one taken from a field names it in `concept`/`field`.',
283
+ },
284
+ },
285
+ {
286
+ index: 4,
287
+ name: 'description',
288
+ type: 'string',
289
+ annotations: {
290
+ displayName: 'Description',
291
+ description: 'Human- and model-readable description of the parameter.',
292
+ },
293
+ },
294
+ {
295
+ index: 5,
296
+ name: 'required',
297
+ type: 'bool',
298
+ annotations: {
299
+ displayName: 'Required',
300
+ description: 'Whether the parameter must be supplied when invoking the action.',
301
+ },
302
+ },
303
+ {
304
+ index: 6,
305
+ name: 'default',
306
+ type: 'string',
307
+ annotations: {
308
+ displayName: 'Default Value',
309
+ description: 'Default value substituted when omitted, serialized as text.',
310
+ },
311
+ },
312
+ {
313
+ index: 7,
314
+ name: 'concept',
315
+ type: 'string',
316
+ annotations: {
317
+ displayName: 'Concept',
318
+ description: 'The entity or relationship this parameter projects a ' +
319
+ 'field of. Absent on a parameter that stands alone.',
320
+ },
321
+ },
322
+ {
323
+ index: 8,
324
+ name: 'field',
325
+ type: 'string',
326
+ annotations: {
327
+ displayName: 'Field',
328
+ description: 'The field within `concept` whose type and wording this ' +
329
+ 'parameter takes. Present exactly when `concept` is.',
330
+ },
331
+ },
332
+ ],
333
+ },
334
+ annotations: {
335
+ displayName: 'Parameters',
336
+ description: 'The inputs the action takes, each typed by the ontology.',
337
+ },
338
+ },
339
+ {
340
+ index: 9,
341
+ name: 'instructions',
342
+ type: 'string',
343
+ annotations: {
344
+ displayName: 'Instructions',
345
+ description: 'Guidance for AI consumers (the action\'s ai_context ' +
346
+ 'instructions).',
347
+ },
348
+ },
349
+ {
350
+ index: 10,
351
+ name: 'guards',
352
+ type: 'array',
353
+ arrayItems: { name: 'guard', type: 'string' },
354
+ annotations: {
355
+ displayName: 'Guards',
356
+ description: 'Names of the constraints that gate this action, each a ' +
357
+ '`semantic-constraint` entry on the same model. They are ' +
358
+ 'checked before the action runs.',
359
+ },
360
+ },
361
+ {
362
+ index: 11,
363
+ name: 'affects',
364
+ type: 'array',
365
+ arrayItems: {
366
+ name: 'affectedConcept',
367
+ type: 'record',
368
+ recordFields: [
369
+ {
370
+ index: 1,
371
+ name: 'concept',
372
+ type: 'string',
373
+ constraints: { required: true },
374
+ annotations: {
375
+ displayName: 'Concept',
376
+ description: 'The entity or relationship this action changes, by name.',
377
+ },
378
+ },
379
+ {
380
+ index: 2,
381
+ name: 'operation',
382
+ type: 'string',
383
+ annotations: {
384
+ displayName: 'Operation',
385
+ description: 'How the concept is changed: `create`, `modify` or ' +
386
+ '`delete`. The same three apply to an entity and to a ' +
387
+ 'relationship. Absent means unspecified.',
388
+ },
389
+ },
390
+ {
391
+ index: 3,
392
+ name: 'fields',
393
+ type: 'array',
394
+ arrayItems: { name: 'field', type: 'string' },
395
+ annotations: {
396
+ displayName: 'Fields',
397
+ description: 'The fields the operation touches, when it touches only ' +
398
+ 'some of them. Absent means the whole instance.',
399
+ },
400
+ },
401
+ ],
402
+ },
403
+ annotations: {
404
+ displayName: 'Affects',
405
+ description: 'What a call changes: one record per concept -- an entity or ' +
406
+ 'a relationship -- the action touches, with the operation and ' +
407
+ 'the fields where the model states them. Declared, because an ' +
408
+ 'executor is opaque and nothing reading the model can see ' +
409
+ 'what it writes.',
410
+ },
411
+ },
412
+ {
413
+ index: 12,
414
+ name: 'sqlStatements',
415
+ type: 'array',
416
+ arrayItems: { name: 'statement', type: 'string' },
417
+ annotations: {
418
+ displayName: 'SQL Statements',
419
+ description: 'The DML statements a `sql` executor runs, in order, each a ' +
420
+ 'single INSERT, UPDATE or DELETE with its values bound as ' +
421
+ '`@parameter` references. Present only for that executor ' +
422
+ 'kind: the other three name a system that performs the write ' +
423
+ 'and carry no text. A reader with these can check the ' +
424
+ 'declared `affects` against the write itself instead of ' +
425
+ 'taking it on trust.',
426
+ },
427
+ },
428
+ ],
429
+ },
430
+ };
431
+ // The id of the constraint type. A constraint is a named invariant over a
432
+ // semantic model; kc_constraints.ts holds the encoding that fills its aspect.
433
+ exports.CONSTRAINT_TYPE_ID = 'semantic-constraint';
434
+ // The aspect that carries a constraint's rule.
435
+ //
436
+ // A constraint states its rule in `judgment`: the rule in words -- whether a
437
+ // discount is justified, whether a refund reason is plausible, whether an order
438
+ // is within a standing limit -- which a language-model judge settles against
439
+ // the proposed write. The push refuses a constraint that states none, so the
440
+ // field is required in the sense that matters and the template cannot say so.
441
+ //
442
+ // `expression` and `evaluation` are RESERVED and no push writes either. They
443
+ // carried a second, deterministic body, a boolean in the model's own language,
444
+ // which was removed. They stay DECLARED rather than deleted for two reasons: an
445
+ // aspect type already created in a project keeps matching the one `kcmd init`
446
+ // would create, and a catalog still holding an expression from an older push
447
+ // reads back as a named skip instead of a silently empty rule.
448
+ //
449
+ // The other two authored fields are the ones every model element carries, and
450
+ // they land in different places. `description` rides the entry source, the way
451
+ // an action's and a metric's do: a violation quotes it back to the caller as
452
+ // the error, which makes it the entry's summary rather than part of the rule.
453
+ // `ai_context` rides this aspect whole
454
+ // (see aiContextField).
455
+ //
456
+ // `onViolation` and `severity` ride the aspect rather than the entry source
457
+ // because they are machine-readable routing and not prose. They answer
458
+ // different questions and are separate fields for that reason: `onViolation` is
459
+ // what the engine does about a breach, `severity` is how grave the breach is.
460
+ // A `low` rule can still be an absolute refusal, and a `critical` one can be a
461
+ // warning because the organization is not ready to block on it. Published
462
+ // without `onViolation`, a rule a supervisor may override reads the same as one
463
+ // nobody can.
464
+ const CONSTRAINT_ASPECT_TYPE = {
465
+ displayName: 'Semantic Constraint',
466
+ description: 'An invariant over a semantic model: a condition that must hold for ' +
467
+ 'every instance of an entity, however that instance was written, ' +
468
+ 'stated in words for a reader to settle.',
469
+ metadataTemplate: {
470
+ name: exports.CONSTRAINT_TYPE_ID,
471
+ type: 'record',
472
+ recordFields: [
473
+ {
474
+ index: 1,
475
+ name: 'expression',
476
+ type: 'string',
477
+ annotations: {
478
+ displayName: 'Expression (reserved)',
479
+ description: 'Reserved and never written. It carried a deterministic body, ' +
480
+ 'a boolean in the model\'s own expression language, which was ' +
481
+ 'removed. A constraint states its rule in `judgment`. An entry ' +
482
+ 'still holding a value here predates the removal and its rule ' +
483
+ 'has to be restated.',
484
+ },
485
+ },
486
+ aiContextField(2),
487
+ {
488
+ index: 3,
489
+ name: 'onViolation',
490
+ type: 'string',
491
+ annotations: {
492
+ displayName: 'On Violation',
493
+ description: 'What a violation does to the write that tripped it: ' +
494
+ '`reject` refuses the write and nobody may approve it, ' +
495
+ '`escalate` holds the write for a human decision, `warn` lets ' +
496
+ 'it proceed and reports it. Absent means `reject`. `escalate` ' +
497
+ 'states that an approver exists, not who they are. A ' +
498
+ 'constraint states one condition and this is that ' +
499
+ 'condition\'s consequence, so a policy whose conditions end ' +
500
+ 'differently is published as several constraints, each with ' +
501
+ 'its own word, listed together by an action\'s `guards`; the ' +
502
+ 'strictest consequence among the violated ones is what the ' +
503
+ 'action does. Required, and any of the three words: a ' +
504
+ 'constraint a judge settles must say what its verdict costs.',
505
+ },
506
+ },
507
+ {
508
+ index: 4,
509
+ name: 'severity',
510
+ type: 'string',
511
+ annotations: {
512
+ displayName: 'Severity',
513
+ description: 'How grave a violation is, for ranking and reporting: ' +
514
+ '`critical`, `high`, `medium` or `low`. Independent of what ' +
515
+ 'the engine does about it, which is `onViolation`. Absent ' +
516
+ 'means the model did not say; there is no default.',
517
+ },
518
+ },
519
+ {
520
+ index: 5,
521
+ name: 'judgment',
522
+ type: 'string',
523
+ annotations: {
524
+ displayName: 'Judgment',
525
+ description: 'The invariant in words, for example `A discount over 30% ' +
526
+ 'must be justified by the reason given in ' +
527
+ 'Order.discount_reason`. A ' +
528
+ 'language-model judge settles it against the proposed write at ' +
529
+ 'review time. Field names are written model-qualified so a ' +
530
+ 'reader can resolve them. States one condition, as what must ' +
531
+ 'be true rather than what to do; the consequence is ' +
532
+ '`onViolation`, and a policy whose conditions end differently ' +
533
+ 'is published as several constraints. The only body a ' +
534
+ 'constraint has, and always present.',
535
+ },
536
+ },
537
+ {
538
+ index: 6,
539
+ name: 'evaluation',
540
+ type: 'string',
541
+ annotations: {
542
+ displayName: 'Evaluation (reserved)',
543
+ description: 'Reserved and never written. It said which of two bodies a ' +
544
+ 'constraint used, and there is one body: every constraint is ' +
545
+ 'settled by a language-model judge reading the write that ' +
546
+ 'would break it.',
547
+ },
548
+ },
549
+ ],
550
+ },
551
+ };
552
+ // The id of the file-copy type. A file copy holds the text of one model or
553
+ // profile file as push read it, so a pull can return the file unchanged.
554
+ exports.FILE_COPY_TYPE_ID = 'semantic-model-file';
555
+ // The entrySource label a push puts on the model entry when it stores file
556
+ // copies. Its value is the push id those copies carry. A model entry whose
557
+ // label names a push the catalog holds no copies of was last written by a
558
+ // push that stopped before storing them. kcmd owns this label and no other.
559
+ exports.PUSH_ID_LABEL = 'kcmd-push-id';
560
+ // The aspect that carries a file copy.
561
+ const FILE_COPY_ASPECT_TYPE = {
562
+ displayName: 'Semantic Model File',
563
+ description: 'The text of one semantic model file or profile file, exactly as ' +
564
+ 'pushed.',
565
+ metadataTemplate: {
566
+ name: exports.FILE_COPY_TYPE_ID,
567
+ type: 'record',
568
+ recordFields: [
569
+ {
570
+ index: 1,
571
+ name: 'fileName',
572
+ type: 'string',
573
+ constraints: { required: true },
574
+ annotations: {
575
+ displayName: 'File Name',
576
+ description: 'The file\'s path relative to the entry group directory, for ' +
577
+ 'example `retail.profile.prod.yaml`.',
578
+ },
579
+ },
580
+ {
581
+ index: 2,
582
+ name: 'content',
583
+ type: 'string',
584
+ constraints: { required: true },
585
+ annotations: {
586
+ displayName: 'Content',
587
+ description: 'The file\'s text, exactly as read.',
588
+ },
589
+ },
590
+ {
591
+ index: 3,
592
+ name: 'pushId',
593
+ type: 'string',
594
+ annotations: {
595
+ displayName: 'Push ID',
596
+ description: 'The id of the push that stored this copy. Every copy one push ' +
597
+ 'stores carries the same id.',
598
+ },
599
+ },
600
+ {
601
+ index: 4,
602
+ name: 'pushFileCount',
603
+ type: 'int',
604
+ annotations: {
605
+ displayName: 'Push File Count',
606
+ description: 'How many file copies the push that stored this one ' +
607
+ 'stored in all.',
608
+ },
609
+ },
610
+ ],
611
+ },
612
+ };
613
+ // Every type kcmd provisions. This list is the whole of what is custom.
614
+ exports.CUSTOM_TYPES = [
615
+ {
616
+ id: exports.ACTION_TYPE_ID,
617
+ entryType: {
618
+ displayName: 'Semantic Action',
619
+ description: 'A write operation defined on a semantic model.',
620
+ },
621
+ aspectType: ACTION_ASPECT_TYPE,
622
+ },
623
+ {
624
+ id: exports.CONSTRAINT_TYPE_ID,
625
+ entryType: {
626
+ displayName: 'Semantic Constraint',
627
+ description: 'An invariant a semantic model requires to hold.',
628
+ },
629
+ aspectType: CONSTRAINT_ASPECT_TYPE,
630
+ },
631
+ {
632
+ id: exports.FILE_COPY_TYPE_ID,
633
+ entryType: {
634
+ displayName: 'Semantic Model File',
635
+ description: 'A copy of one semantic model file or profile file.',
636
+ },
637
+ aspectType: FILE_COPY_ASPECT_TYPE,
638
+ // Push stores every model file with it.
639
+ required: true,
640
+ },
641
+ ];
642
+ // Custom types are provisioned at `global` so an entry group in any region can
643
+ // reference them, matching where the built-in system types live.
644
+ const CUSTOM_TYPE_LOCATION = 'global';
645
+ // True when kcmd still provisions this id. It turns false the moment the type
646
+ // is deleted from CUSTOM_TYPES, which is how the rest of the code learns that a
647
+ // built-in has taken over.
648
+ function isCustomType(id) {
649
+ return exports.CUSTOM_TYPES.some(t => t.id === id);
650
+ }
651
+ /**
652
+ * Collects the deduplicated list of `kcmd`-owned custom type IDs
653
+ * (`semantic-action`, `semantic-constraint`, etc.) used by `entries`.
654
+ */
655
+ function collectCustomTypes(entries) {
656
+ const used = new Set();
657
+ for (const entry of entries) {
658
+ const typeId = (0, kc_ids_1.entryIdOf)(entry.entryType ?? '');
659
+ if (isCustomType(typeId))
660
+ used.add(typeId);
661
+ }
662
+ return [...used];
663
+ }
664
+ // Where a destination's custom types live: the destination project itself,
665
+ // because `kcmd init` is what creates them there.
666
+ function customTypeHome(dest) {
667
+ return { project: dest.project, location: CUSTOM_TYPE_LOCATION };
668
+ }
669
+ // Full resource name of a custom entry type in a destination.
670
+ function customEntryTypeName(id, dest) {
671
+ const home = customTypeHome(dest);
672
+ return `projects/${home.project}/locations/${home.location}/entryTypes/${id}`;
673
+ }
674
+ // Full resource name of a custom aspect type in a destination.
675
+ function customAspectTypeName(id, dest) {
676
+ const home = customTypeHome(dest);
677
+ return `projects/${home.project}/locations/${home.location}/aspectTypes/${id}`;
678
+ }
679
+ // Aspect-map key: the `project.location.type` reference form the client keys an
680
+ // entry's aspects by (see dataplex._nameToTypeRef).
681
+ function customAspectKey(id, dest) {
682
+ const home = customTypeHome(dest);
683
+ return `${home.project}.${home.location}.${id}`;
684
+ }
685
+ /**
686
+ * Creates every type in CUSTOM_TYPES in a destination.
687
+ *
688
+ * Called from `kcmd init --semantic-model`, beside the entry group it already
689
+ * provisions, so a push still writes nothing but entries. Each aspect type is
690
+ * created and then, if it is already there, patched: a project provisioned by
691
+ * an earlier version of kcmd holds an older template, and pushing a field that
692
+ * template lacks fails with an opaque parsing error. Dataplex rejects a
693
+ * backwards-incompatible change, so the patch only ever adds appended fields.
694
+ *
695
+ * Stops at the first hard failure, which includes a permission refusal for a
696
+ * required type that does not exist. A refusal to patch a type that exists is
697
+ * a warning. A refusal for an optional type does NOT stop it: the refusals
698
+ * differ per type and per operation -- patching a type that already exists
699
+ * needs `update`, creating one that does not needs `create` -- so a caller
700
+ * refused one type may well be allowed the next, and giving up early would
701
+ * leave a type uncreated that nothing was stopping. Every type is attempted
702
+ * and the first refusal is reported once they have been.
703
+ */
704
+ async function provisionCustomTypes(cat, dest) {
705
+ const warnings = [];
706
+ let denied;
707
+ for (const type of exports.CUSTOM_TYPES) {
708
+ const result = await provisionOne(cat, dest, type);
709
+ if (result.warnings)
710
+ warnings.push(...result.warnings);
711
+ // A hard failure is infrastructure-level and says nothing about the next
712
+ // type, so it still stops everything.
713
+ if (result.error)
714
+ return { ...result, warnings: warnings.length ? warnings : undefined };
715
+ if (result.denied && !denied)
716
+ denied = result;
717
+ }
718
+ if (denied)
719
+ return { ...denied, warnings: warnings.length ? warnings : undefined };
720
+ return warnings.length ? { warnings } : {};
721
+ }
722
+ // One custom type: its aspect type, then the entry type that requires it.
723
+ async function provisionOne(cat, dest, type) {
724
+ const home = customTypeHome(dest);
725
+ // Creating a type needs permissions that publishing entries does not. A
726
+ // caller who lacks them can still init, push and pull every model that uses
727
+ // none of the optional types, so that refusal is reported rather than fatal.
728
+ // A required type is needed by every push, so its refusal stops init.
729
+ const denial = (what, status, message) => {
730
+ if (status !== 403)
731
+ return undefined;
732
+ const refused = `no permission to create ${what} in project '${home.project}' (${message || status})`;
733
+ return type.required ?
734
+ { error: `${refused}; every push needs it, so init cannot finish` } :
735
+ {
736
+ denied: `${refused}; models that need it cannot be pushed until ` +
737
+ `it exists`
738
+ };
739
+ };
740
+ // A caller without create permission may be refused the create of a type
741
+ // that already exists. For a required type a lookup tells the two apart, so
742
+ // init fails only when the type is missing.
743
+ const refusedButExists = async (status, lookup) => status === 403 && !!type.required && (await lookup()).status === 200;
744
+ const warnings = [];
745
+ const aspectLabel = `aspect type '${type.id}'`;
746
+ const aspect = await cat.createAspectType(home.project, home.location, type.id, type.aspectType);
747
+ if (aspect.status === 409 ||
748
+ await refusedButExists(aspect.status, () => cat.getAspectType(home.project, home.location, type.id))) {
749
+ const upd = await cat.updateAspectType(home.project, home.location, type.id, type.aspectType, ['description', 'display_name', 'metadata_template']);
750
+ // Patching an existing type forward is best effort. Dataplex refuses a
751
+ // template change it judges backwards-incompatible -- removing a field, or
752
+ // renumbering one -- and the type already there is the one every published
753
+ // entry depends on. Leaving it alone and saying so beats aborting an init
754
+ // that has already created the entry group.
755
+ //
756
+ // What the warning has to carry is the way OUT, because there is no way
757
+ // forward: `kcmd` creates and patches aspect types and does not delete
758
+ // them, so a project holding a template this build cannot patch stays that
759
+ // way until someone removes it by hand. Naming the resource and the
760
+ // command is the whole of the fix; a reader who has only "left it
761
+ // unchanged" has to work out which of several types in which project, and
762
+ // then that `gcloud dataplex aspect-types` is where it lives.
763
+ const reason = upd.status !== 200 ?
764
+ `${upd.message || upd.status}` :
765
+ await cat.awaitOperation(upd.result, `updating ${aspectLabel}`);
766
+ if (upd.status === 403) {
767
+ // The type exists, so a refusal to patch it leaves it as it is.
768
+ warnings.push(`no permission to update the existing ${aspectLabel} in project ` +
769
+ `'${home.project}' (${reason}); left it unchanged`);
770
+ }
771
+ else if (reason) {
772
+ const resource = customAspectTypeName(type.id, dest);
773
+ warnings.push(`left the existing ${aspectLabel} unchanged (${reason}). It is ` +
774
+ `${resource}, and Dataplex will not patch it into the template ` +
775
+ `this build states -- a template field that was removed or ` +
776
+ `renumbered is a backwards-incompatible change and is refused. A ` +
777
+ `push will fail wherever the two disagree. To move it forward, ` +
778
+ `delete it and re-run this command: gcloud dataplex aspect-types ` +
779
+ `delete ${type.id} --project=${home.project} ` +
780
+ `--location=${home.location}. Deleting takes the aspects of every ` +
781
+ `entry already published with this type, so re-push those models ` +
782
+ `afterwards.`);
783
+ }
784
+ }
785
+ else if (aspect.status !== 200) {
786
+ const refused = denial(aspectLabel, aspect.status, aspect.message);
787
+ if (refused)
788
+ return refused;
789
+ return {
790
+ error: `creating ${aspectLabel}: ${aspect.message || aspect.status}`
791
+ };
792
+ }
793
+ else {
794
+ // The entry type below names this aspect type in its required aspects, and
795
+ // the server rejects that while the create is still in flight, so the wait
796
+ // is load-bearing rather than tidiness.
797
+ const failed = await cat.awaitOperation(aspect.result, `creating ${aspectLabel}`);
798
+ if (failed)
799
+ return { error: failed };
800
+ }
801
+ // The entry type requires the aspect type above, so it is created second. An
802
+ // existing one is left alone rather than patched: unlike the aspect type it
803
+ // carries no template that grows over time, and its required-aspect list is
804
+ // what an entry already published depends on.
805
+ const entryLabel = `entry type '${type.id}'`;
806
+ const entryType = await cat.createEntryType(home.project, home.location, type.id, {
807
+ ...type.entryType,
808
+ requiredAspects: [{ type: customAspectTypeName(type.id, dest) }],
809
+ });
810
+ const collected = warnings.length ? { warnings } : {};
811
+ if (entryType.status === 409 ||
812
+ await refusedButExists(entryType.status, () => cat.getEntryType(home.project, home.location, type.id))) {
813
+ return collected;
814
+ }
815
+ const refused = denial(entryLabel, entryType.status, entryType.message);
816
+ if (refused)
817
+ return { ...refused, ...collected };
818
+ if (entryType.status !== 200) {
819
+ return {
820
+ ...collected,
821
+ error: `creating ${entryLabel}: ${entryType.message || entryType.status}`
822
+ };
823
+ }
824
+ // A push may follow immediately, and an entry naming a type that is still
825
+ // being created is rejected the same way, so wait here too.
826
+ const failed = await cat.awaitOperation(entryType.result, `creating ${entryLabel}`);
827
+ return failed ? { ...collected, error: failed } : collected;
828
+ }