@google-cloud/kcmd 0.0.0-stage → 0.2.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.
- package/LICENSE +203 -0
- package/README.md +264 -2
- package/build/ts/kcmd/cli.js +20252 -0
- package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
- package/build/ts/kcmd/gcp/alloydb.js +70 -0
- package/build/ts/kcmd/gcp/api.d.ts +29 -0
- package/build/ts/kcmd/gcp/api.js +162 -0
- package/build/ts/kcmd/gcp/biglake.d.ts +18 -0
- package/build/ts/kcmd/gcp/biglake.js +59 -0
- package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
- package/build/ts/kcmd/gcp/bigquery.js +134 -0
- package/build/ts/kcmd/gcp/context.d.ts +10 -0
- package/build/ts/kcmd/gcp/context.js +74 -0
- package/build/ts/kcmd/gcp/crm.d.ts +13 -0
- package/build/ts/kcmd/gcp/crm.js +91 -0
- package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
- package/build/ts/kcmd/gcp/dataplex.js +406 -0
- package/build/ts/kcmd/gcp/index.d.ts +3 -0
- package/build/ts/kcmd/gcp/index.js +21 -0
- package/build/ts/kcmd/gcp/spanner.d.ts +73 -0
- package/build/ts/kcmd/gcp/spanner.js +126 -0
- package/build/ts/kcmd/index.d.ts +7 -0
- package/build/ts/kcmd/index.js +46 -0
- package/build/ts/kcmd/layout.d.ts +16 -0
- package/build/ts/kcmd/layout.js +31 -0
- package/build/ts/kcmd/layouts/documents.d.ts +24 -0
- package/build/ts/kcmd/layouts/documents.js +264 -0
- package/build/ts/kcmd/layouts/okf.d.ts +31 -0
- package/build/ts/kcmd/layouts/okf.js +386 -0
- package/build/ts/kcmd/layouts/semantic-model.d.ts +74 -0
- package/build/ts/kcmd/layouts/semantic-model.js +621 -0
- package/build/ts/kcmd/layouts/standard.d.ts +13 -0
- package/build/ts/kcmd/layouts/standard.js +108 -0
- package/build/ts/kcmd/manifest.d.ts +34 -0
- package/build/ts/kcmd/manifest.js +262 -0
- package/build/ts/kcmd/metadata.d.ts +25 -0
- package/build/ts/kcmd/metadata.js +8 -0
- package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
- package/build/ts/kcmd/okf/catalog_residue.js +124 -0
- package/build/ts/kcmd/okf/concept.d.ts +14 -0
- package/build/ts/kcmd/okf/concept.js +110 -0
- package/build/ts/kcmd/okf/concept_to_entry.d.ts +10 -0
- package/build/ts/kcmd/okf/concept_to_entry.js +281 -0
- package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
- package/build/ts/kcmd/okf/init_okf_workspace.js +113 -0
- package/build/ts/kcmd/okf/links.d.ts +23 -0
- package/build/ts/kcmd/okf/links.js +468 -0
- package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
- package/build/ts/kcmd/okf/okf_metadata.js +99 -0
- package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
- package/build/ts/kcmd/semantic/bigquery.js +469 -0
- package/build/ts/kcmd/semantic/binding.d.ts +3 -0
- package/build/ts/kcmd/semantic/binding.js +92 -0
- package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
- package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
- package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
- package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +104 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1367 -0
- package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
- package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
- package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
- package/build/ts/kcmd/semantic/deployment_target.js +379 -0
- package/build/ts/kcmd/semantic/graph_ddl.d.ts +44 -0
- package/build/ts/kcmd/semantic/graph_ddl.js +656 -0
- package/build/ts/kcmd/semantic/import_model.d.ts +72 -0
- package/build/ts/kcmd/semantic/import_model.js +1698 -0
- package/build/ts/kcmd/semantic/ir.d.ts +589 -0
- package/build/ts/kcmd/semantic/ir.js +247 -0
- package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
- package/build/ts/kcmd/semantic/kc_actions.js +531 -0
- package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
- package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
- package/build/ts/kcmd/semantic/kc_converter.d.ts +37 -0
- package/build/ts/kcmd/semantic/kc_converter.js +839 -0
- package/build/ts/kcmd/semantic/kc_custom_types.d.ts +63 -0
- package/build/ts/kcmd/semantic/kc_custom_types.js +751 -0
- package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
- package/build/ts/kcmd/semantic/kc_entries.js +284 -0
- package/build/ts/kcmd/semantic/kc_ids.d.ts +102 -0
- package/build/ts/kcmd/semantic/kc_ids.js +188 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +29 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.js +960 -0
- package/build/ts/kcmd/semantic/loader.d.ts +258 -0
- package/build/ts/kcmd/semantic/loader.js +3576 -0
- package/build/ts/kcmd/semantic/names.d.ts +10 -0
- package/build/ts/kcmd/semantic/names.js +85 -0
- package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
- package/build/ts/kcmd/semantic/osi_converter.js +910 -0
- package/build/ts/kcmd/semantic/parameters.d.ts +19 -0
- package/build/ts/kcmd/semantic/parameters.js +171 -0
- package/build/ts/kcmd/semantic/pull_kc.d.ts +10 -0
- package/build/ts/kcmd/semantic/pull_kc.js +272 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +59 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.js +346 -0
- package/build/ts/kcmd/semantic/resolve_profiles.d.ts +291 -0
- package/build/ts/kcmd/semantic/resolve_profiles.js +2003 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
- package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
- package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
- package/build/ts/kcmd/semantic/runtime/runtime.d.ts +88 -0
- package/build/ts/kcmd/semantic/runtime/runtime.js +301 -0
- package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
- package/build/ts/kcmd/semantic/runtime/store.js +344 -0
- package/build/ts/kcmd/semantic/skills.d.ts +110 -0
- package/build/ts/kcmd/semantic/skills.js +697 -0
- package/build/ts/kcmd/semantic/source_dialect.d.ts +17 -0
- package/build/ts/kcmd/semantic/source_dialect.js +311 -0
- package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
- package/build/ts/kcmd/semantic/source_entries.js +341 -0
- package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
- package/build/ts/kcmd/semantic/spanner.js +60 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +23 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.js +394 -0
- package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
- package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
- package/build/ts/kcmd/semantic/sql_parser.d.ts +47 -0
- package/build/ts/kcmd/semantic/sql_parser.js +400 -0
- package/build/ts/kcmd/semantic/transpile.d.ts +76 -0
- package/build/ts/kcmd/semantic/transpile.js +581 -0
- package/build/ts/kcmd/semantic/validate.d.ts +70 -0
- package/build/ts/kcmd/semantic/validate.js +2266 -0
- package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
- package/build/ts/kcmd/semantic/yaml_options.js +58 -0
- package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
- package/build/ts/kcmd/semantic/yaml_update.js +782 -0
- package/build/ts/kcmd/snapshot.d.ts +37 -0
- package/build/ts/kcmd/snapshot.js +386 -0
- package/build/ts/kcmd/source.d.ts +31 -0
- package/build/ts/kcmd/source.js +119 -0
- package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
- package/build/ts/kcmd/sources/bq-dataset.js +131 -0
- package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
- package/build/ts/kcmd/sources/entrygroup.js +83 -0
- package/build/ts/kcmd/sources/kb.d.ts +21 -0
- package/build/ts/kcmd/sources/kb.js +82 -0
- package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
- package/build/ts/kcmd/sources/semantic-model.js +48 -0
- package/build/ts/kcmd/sync.d.ts +35 -0
- package/build/ts/kcmd/sync.js +734 -0
- package/package.json +62 -4
|
@@ -0,0 +1,751 @@
|
|
|
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_FINISHED = exports.PUSH_WRITING = exports.PUSH_STATE_LABEL = 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 entrySource label every push puts on the model entry: PUSH_WRITING with
|
|
553
|
+
// the model entry's write and PUSH_FINISHED as the push's last write. kcmd
|
|
554
|
+
// owns this label and no other.
|
|
555
|
+
exports.PUSH_STATE_LABEL = 'kcmd-push-state';
|
|
556
|
+
exports.PUSH_WRITING = 'writing';
|
|
557
|
+
exports.PUSH_FINISHED = 'finished';
|
|
558
|
+
// Every type kcmd provisions. This list is the whole of what is custom.
|
|
559
|
+
exports.CUSTOM_TYPES = [
|
|
560
|
+
{
|
|
561
|
+
id: exports.ACTION_TYPE_ID,
|
|
562
|
+
entryType: {
|
|
563
|
+
displayName: 'Semantic Action',
|
|
564
|
+
description: 'A write operation defined on a semantic model.',
|
|
565
|
+
},
|
|
566
|
+
aspectType: ACTION_ASPECT_TYPE,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: exports.CONSTRAINT_TYPE_ID,
|
|
570
|
+
entryType: {
|
|
571
|
+
displayName: 'Semantic Constraint',
|
|
572
|
+
description: 'An invariant a semantic model requires to hold.',
|
|
573
|
+
},
|
|
574
|
+
aspectType: CONSTRAINT_ASPECT_TYPE,
|
|
575
|
+
},
|
|
576
|
+
];
|
|
577
|
+
// Custom types are provisioned at `global` so an entry group in any region can
|
|
578
|
+
// reference them, matching where the built-in system types live.
|
|
579
|
+
const CUSTOM_TYPE_LOCATION = 'global';
|
|
580
|
+
// True when kcmd still provisions this id. It turns false the moment the type
|
|
581
|
+
// is deleted from CUSTOM_TYPES, which is how the rest of the code learns that a
|
|
582
|
+
// built-in has taken over.
|
|
583
|
+
function isCustomType(id) {
|
|
584
|
+
return exports.CUSTOM_TYPES.some(t => t.id === id);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Collects the deduplicated list of `kcmd`-owned custom type IDs
|
|
588
|
+
* (`semantic-action`, `semantic-constraint`, etc.) used by `entries`.
|
|
589
|
+
*/
|
|
590
|
+
function collectCustomTypes(entries) {
|
|
591
|
+
const used = new Set();
|
|
592
|
+
for (const entry of entries) {
|
|
593
|
+
const typeId = (0, kc_ids_1.entryIdOf)(entry.entryType ?? '');
|
|
594
|
+
if (isCustomType(typeId))
|
|
595
|
+
used.add(typeId);
|
|
596
|
+
}
|
|
597
|
+
return [...used];
|
|
598
|
+
}
|
|
599
|
+
// Where a destination's custom types live: the destination project itself,
|
|
600
|
+
// because `kcmd init` is what creates them there.
|
|
601
|
+
function customTypeHome(dest) {
|
|
602
|
+
return { project: dest.project, location: CUSTOM_TYPE_LOCATION };
|
|
603
|
+
}
|
|
604
|
+
// Full resource name of a custom entry type in a destination.
|
|
605
|
+
function customEntryTypeName(id, dest) {
|
|
606
|
+
const home = customTypeHome(dest);
|
|
607
|
+
return `projects/${home.project}/locations/${home.location}/entryTypes/${id}`;
|
|
608
|
+
}
|
|
609
|
+
// Full resource name of a custom aspect type in a destination.
|
|
610
|
+
function customAspectTypeName(id, dest) {
|
|
611
|
+
const home = customTypeHome(dest);
|
|
612
|
+
return `projects/${home.project}/locations/${home.location}/aspectTypes/${id}`;
|
|
613
|
+
}
|
|
614
|
+
// Aspect-map key: the `project.location.type` reference form the client keys an
|
|
615
|
+
// entry's aspects by (see dataplex._nameToTypeRef).
|
|
616
|
+
function customAspectKey(id, dest) {
|
|
617
|
+
const home = customTypeHome(dest);
|
|
618
|
+
return `${home.project}.${home.location}.${id}`;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Creates every type in CUSTOM_TYPES in a destination.
|
|
622
|
+
*
|
|
623
|
+
* Called from `kcmd init --semantic-model`, beside the entry group it already
|
|
624
|
+
* provisions, so a push still writes nothing but entries. Each aspect type is
|
|
625
|
+
* created and then, if it is already there, patched: a project provisioned by
|
|
626
|
+
* an earlier version of kcmd holds an older template, and pushing a field that
|
|
627
|
+
* template lacks fails with an opaque parsing error. Dataplex rejects a
|
|
628
|
+
* backwards-incompatible change, so the patch only ever adds appended fields.
|
|
629
|
+
*
|
|
630
|
+
* Stops at the first hard failure. A refusal to patch a type that exists is
|
|
631
|
+
* a warning. A refusal to create a type does NOT stop it: the refusals
|
|
632
|
+
* differ per type and per operation -- patching a type that already exists
|
|
633
|
+
* needs `update`, creating one that does not needs `create` -- so a caller
|
|
634
|
+
* refused one type may well be allowed the next, and giving up early would
|
|
635
|
+
* leave a type uncreated that nothing was stopping. Every type is attempted
|
|
636
|
+
* and the first refusal is reported once they have been.
|
|
637
|
+
*/
|
|
638
|
+
async function provisionCustomTypes(cat, dest) {
|
|
639
|
+
const warnings = [];
|
|
640
|
+
let denied;
|
|
641
|
+
for (const type of exports.CUSTOM_TYPES) {
|
|
642
|
+
const result = await provisionOne(cat, dest, type);
|
|
643
|
+
if (result.warnings)
|
|
644
|
+
warnings.push(...result.warnings);
|
|
645
|
+
// A hard failure is infrastructure-level and says nothing about the next
|
|
646
|
+
// type, so it still stops everything.
|
|
647
|
+
if (result.error)
|
|
648
|
+
return { ...result, warnings: warnings.length ? warnings : undefined };
|
|
649
|
+
if (result.denied && !denied)
|
|
650
|
+
denied = result;
|
|
651
|
+
}
|
|
652
|
+
if (denied)
|
|
653
|
+
return { ...denied, warnings: warnings.length ? warnings : undefined };
|
|
654
|
+
return warnings.length ? { warnings } : {};
|
|
655
|
+
}
|
|
656
|
+
// One custom type: its aspect type, then the entry type that requires it.
|
|
657
|
+
async function provisionOne(cat, dest, type) {
|
|
658
|
+
const home = customTypeHome(dest);
|
|
659
|
+
// Creating a type needs permissions that publishing entries does not. A
|
|
660
|
+
// caller who lacks them can still init, push and pull every model that uses
|
|
661
|
+
// none of the custom types, so that refusal is reported rather than fatal.
|
|
662
|
+
const denial = (what, status, message) => {
|
|
663
|
+
if (status !== 403)
|
|
664
|
+
return undefined;
|
|
665
|
+
return {
|
|
666
|
+
denied: `no permission to create ${what} in project '${home.project}' (${message || status}); models that need it cannot be pushed ` +
|
|
667
|
+
`until it exists`
|
|
668
|
+
};
|
|
669
|
+
};
|
|
670
|
+
const warnings = [];
|
|
671
|
+
const aspectLabel = `aspect type '${type.id}'`;
|
|
672
|
+
const aspect = await cat.createAspectType(home.project, home.location, type.id, type.aspectType);
|
|
673
|
+
if (aspect.status === 409) {
|
|
674
|
+
const upd = await cat.updateAspectType(home.project, home.location, type.id, type.aspectType, ['description', 'display_name', 'metadata_template']);
|
|
675
|
+
// Patching an existing type forward is best effort. Dataplex refuses a
|
|
676
|
+
// template change it judges backwards-incompatible -- removing a field, or
|
|
677
|
+
// renumbering one -- and the type already there is the one every published
|
|
678
|
+
// entry depends on. Leaving it alone and saying so beats aborting an init
|
|
679
|
+
// that has already created the entry group.
|
|
680
|
+
//
|
|
681
|
+
// What the warning has to carry is the way OUT, because there is no way
|
|
682
|
+
// forward: `kcmd` creates and patches aspect types and does not delete
|
|
683
|
+
// them, so a project holding a template this build cannot patch stays that
|
|
684
|
+
// way until someone removes it by hand. Naming the resource and the
|
|
685
|
+
// command is the whole of the fix; a reader who has only "left it
|
|
686
|
+
// unchanged" has to work out which of several types in which project, and
|
|
687
|
+
// then that `gcloud dataplex aspect-types` is where it lives.
|
|
688
|
+
const reason = upd.status !== 200 ?
|
|
689
|
+
`${upd.message || upd.status}` :
|
|
690
|
+
await cat.awaitOperation(upd.result, `updating ${aspectLabel}`);
|
|
691
|
+
if (upd.status === 403) {
|
|
692
|
+
// The type exists, so a refusal to patch it leaves it as it is.
|
|
693
|
+
warnings.push(`no permission to update the existing ${aspectLabel} in project ` +
|
|
694
|
+
`'${home.project}' (${reason}); left it unchanged`);
|
|
695
|
+
}
|
|
696
|
+
else if (reason) {
|
|
697
|
+
const resource = customAspectTypeName(type.id, dest);
|
|
698
|
+
warnings.push(`left the existing ${aspectLabel} unchanged (${reason}). It is ` +
|
|
699
|
+
`${resource}, and Dataplex will not patch it into the template ` +
|
|
700
|
+
`this build states -- a template field that was removed or ` +
|
|
701
|
+
`renumbered is a backwards-incompatible change and is refused. A ` +
|
|
702
|
+
`push will fail wherever the two disagree. To move it forward, ` +
|
|
703
|
+
`delete it and re-run this command: gcloud dataplex aspect-types ` +
|
|
704
|
+
`delete ${type.id} --project=${home.project} ` +
|
|
705
|
+
`--location=${home.location}. Deleting takes the aspects of every ` +
|
|
706
|
+
`entry already published with this type, so re-push those models ` +
|
|
707
|
+
`afterwards.`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else if (aspect.status !== 200) {
|
|
711
|
+
const refused = denial(aspectLabel, aspect.status, aspect.message);
|
|
712
|
+
if (refused)
|
|
713
|
+
return refused;
|
|
714
|
+
return {
|
|
715
|
+
error: `creating ${aspectLabel}: ${aspect.message || aspect.status}`
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
// The entry type below names this aspect type in its required aspects, and
|
|
720
|
+
// the server rejects that while the create is still in flight, so the wait
|
|
721
|
+
// is load-bearing rather than tidiness.
|
|
722
|
+
const failed = await cat.awaitOperation(aspect.result, `creating ${aspectLabel}`);
|
|
723
|
+
if (failed)
|
|
724
|
+
return { error: failed };
|
|
725
|
+
}
|
|
726
|
+
// The entry type requires the aspect type above, so it is created second. An
|
|
727
|
+
// existing one is left alone rather than patched: unlike the aspect type it
|
|
728
|
+
// carries no template that grows over time, and its required-aspect list is
|
|
729
|
+
// what an entry already published depends on.
|
|
730
|
+
const entryLabel = `entry type '${type.id}'`;
|
|
731
|
+
const entryType = await cat.createEntryType(home.project, home.location, type.id, {
|
|
732
|
+
...type.entryType,
|
|
733
|
+
requiredAspects: [{ type: customAspectTypeName(type.id, dest) }],
|
|
734
|
+
});
|
|
735
|
+
const collected = warnings.length ? { warnings } : {};
|
|
736
|
+
if (entryType.status === 409)
|
|
737
|
+
return collected;
|
|
738
|
+
const refused = denial(entryLabel, entryType.status, entryType.message);
|
|
739
|
+
if (refused)
|
|
740
|
+
return { ...refused, ...collected };
|
|
741
|
+
if (entryType.status !== 200) {
|
|
742
|
+
return {
|
|
743
|
+
...collected,
|
|
744
|
+
error: `creating ${entryLabel}: ${entryType.message || entryType.status}`
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
// A push may follow immediately, and an entry naming a type that is still
|
|
748
|
+
// being created is rejected the same way, so wait here too.
|
|
749
|
+
const failed = await cat.awaitOperation(entryType.result, `creating ${entryLabel}`);
|
|
750
|
+
return failed ? { ...collected, error: failed } : collected;
|
|
751
|
+
}
|