Discussion π£
@Term() and @Term_*() decorators in CTO files accept arguments of type String, Number, Boolean, and TypeReference (via the Concerto decorator argument system).
The round-trip CTO β vocabulary β decorator command set β CTO loses non-string argument types: numbers and booleans become strings, and type references become the literal string "undefined". String @Term values round-trip correctly.
Questions:
- What types are allowed for
@Term and @Term_* (String, Number, Boolean, TypeReference) per the language design?
- What is the original intent behind
@Term and @Term_*?
- Concerto decorators support
DecoratorTypeReference β should that be supported in @Term and @Term_*, and what should it look like in vocabulary YAML?
Reproduction
Script: reproduce-term-roundtrip.js
npm install @accordproject/concerto-core @accordproject/concerto-cto @accordproject/concerto-vocabulary
node reproduce-term-roundtrip.js
The script:
- Parses a decorated CTO model and prints the AST.
- Extracts vocabulary YAML via
DecoratorManager.extractVocabularies.
- Builds a decorator command set via
VocabularyManager.generateDecoratorCommands.
- Applies commands to a plain model and prints regenerated CTO.
- Asserts that decorator command arguments preserve
DecoratorString, DecoratorNumber, DecoratorBoolean, and DecoratorTypeReference types.
Result: 2 passed, 5 failed (see Test results below).
Example
CTO model (input)
namespace com.example@1.0.0
concept Dimension {
o Integer unit
}
@Term("Products")
@Term_sortOrder(1)
@Term_featured(false)
concept Product {
@Term("Product Name")
@Term_rank(2)
@Term_visible(true)
o String name
@Term(Dimension)
@Term_sizeType(Dimension)
o Integer size
}
AST (CTO β AST)
Parsing preserves correct argument types in the AST:
{
"$class": "concerto.metamodel@1.0.0.Models",
"models": [
{
"$class": "concerto.metamodel@1.0.0.Model",
"decorators": [],
"namespace": "com.example@1.0.0",
"imports": [],
"declarations": [
{
"$class": "concerto.metamodel@1.0.0.ConceptDeclaration",
"name": "Dimension",
"isAbstract": false,
"properties": [
{
"$class": "concerto.metamodel@1.0.0.IntegerProperty",
"name": "unit",
"isArray": false,
"isOptional": false
}
]
},
{
"$class": "concerto.metamodel@1.0.0.ConceptDeclaration",
"name": "Product",
"isAbstract": false,
"properties": [
{
"$class": "concerto.metamodel@1.0.0.StringProperty",
"name": "name",
"isArray": false,
"isOptional": false,
"decorators": [
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "Product Name"
}
]
},
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_rank",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorNumber",
"value": 2
}
]
},
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_visible",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorBoolean",
"value": true
}
]
}
]
},
{
"$class": "concerto.metamodel@1.0.0.IntegerProperty",
"name": "size",
"isArray": false,
"isOptional": false,
"decorators": [
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorTypeReference",
"type": {
"$class": "concerto.metamodel@1.0.0.TypeIdentifier",
"name": "Dimension"
},
"isArray": false
}
]
},
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_sizeType",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorTypeReference",
"type": {
"$class": "concerto.metamodel@1.0.0.TypeIdentifier",
"name": "Dimension"
},
"isArray": false
}
]
}
]
}
],
"decorators": [
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "Products"
}
]
},
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_sortOrder",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorNumber",
"value": 1
}
]
},
{
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_featured",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorBoolean",
"value": false
}
]
}
]
}
]
}
]
}
Vocabulary YAML (AST β vocabulary)
Extracted vocabulary loses type information for non-string @Term / @Term_* arguments (undefined placeholders):
locale: en
namespace: com.example@1.0.0
declarations:
- Product: Products
sortOrder: 1
featured: false
properties:
- name: Product Name
rank: 2
visible: true
- size: undefined
sizeType: undefined
Decorator command set (vocabulary β DCS)
generateDecoratorCommands coerces all arguments to DecoratorString (numbers/booleans as JSON-ish values, type refs as "undefined"):
{
"$class": "org.accordproject.decoratorcommands@0.4.0.DecoratorCommandSet",
"name": "terms-en",
"version": "1.0.0",
"commands": [
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "Products"
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_sortOrder",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": 1
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_featured",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": false
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product",
"property": "name"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "Product Name"
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product",
"property": "name"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_rank",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": 2
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product",
"property": "name"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_visible",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": true
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product",
"property": "size"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "undefined"
}
]
}
},
{
"$class": "org.accordproject.decoratorcommands@0.4.0.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget",
"namespace": "com.example@1.0.0",
"declaration": "Product",
"property": "size"
},
"decorator": {
"$class": "concerto.metamodel@1.0.0.Decorator",
"name": "Term_sizeType",
"arguments": [
{
"$class": "concerto.metamodel@1.0.0.DecoratorString",
"value": "undefined"
}
]
}
}
]
}
Regenerated CTO (DCS β CTO)
Applying the command set to a plain model produces quoted string arguments for everything that was not a string label:
namespace com.example@1.0.0
concept Dimension {
o Integer unit
}
@Term("Products")
@Term_sortOrder("1")
@Term_featured("false")
concept Product {
@Term("Product Name")
@Term_rank("2")
@Term_visible("true")
o String name
@Term("undefined")
@Term_sizeType("undefined")
o Integer size
}
Test results
Assertions from reproduce-term-roundtrip.js on the generated decorator command set:
PASS @Term("Products") on Product β DecoratorString
PASS @Term("Product Name") on Product.name β DecoratorString
FAIL @Term(Dimension) on Product.size β DecoratorTypeReference
$class expected: concerto.metamodel@1.0.0.DecoratorTypeReference
actual: concerto.metamodel@1.0.0.DecoratorString
type.name expected: Dimension
actual: undefined
type.namespace expected: com.example@1.0.0
actual: undefined
FAIL @Term_sortOrder(1) on Product β DecoratorNumber
$class expected: concerto.metamodel@1.0.0.DecoratorNumber
actual: concerto.metamodel@1.0.0.DecoratorString
FAIL @Term_rank(2) on Product.name β DecoratorNumber
$class expected: concerto.metamodel@1.0.0.DecoratorNumber
actual: concerto.metamodel@1.0.0.DecoratorString
FAIL @Term_featured(false) on Product β DecoratorBoolean
$class expected: concerto.metamodel@1.0.0.DecoratorBoolean
actual: concerto.metamodel@1.0.0.DecoratorString
FAIL @Term_visible(true) on Product.name β DecoratorBoolean
$class expected: concerto.metamodel@1.0.0.DecoratorBoolean
actual: concerto.metamodel@1.0.0.DecoratorString
2 passed, 5 failed
SCRIPT
/* eslint-disable require-jsdoc */
/**
*
* Setup:
* npm install @accordproject/concerto-core @accordproject/concerto-cto @accordproject/concerto-vocabulary
*
* Run:
* node reproduce-term-roundtrip.js
*/
const { ModelManager, DecoratorManager } = require('@accordproject/concerto-core');
const { Printer } = require('@accordproject/concerto-cto');
const { VocabularyManager } = require('@accordproject/concerto-vocabulary');
// // model with non-string @Term_* decorator arguments
const DECORATED_MODEL_CTO = `
namespace com.example@1.0.0
concept Dimension {
o Integer unit
}
@Term("Products")
@Term_sortOrder(1)
@Term_featured(false)
concept Product {
@Term("Product Name")
@Term_rank(2)
@Term_visible(true)
o String name
@Term(Dimension)
@Term_sizeType(Dimension)
o Integer size
}
`;
const PLAIN_MODEL_CTO = `
namespace com.example@1.0.0
concept Dimension {
o Integer unit
}
concept Product {
o String name
o Integer size
}
`;
// CONSTANTS
const META = 'concerto.metamodel@1.0.0';
let passed = 0;
let failed = 0;
// HELPER FUNCTIONS
/**
* Assert decorator scalar argument type/value.
* @param {string} label test label
* @param {object|undefined} command decorator command
* @param {string} expectedClass expected argument class
* @param {string|number|boolean} expectedValue expected argument value
* @returns {void}
*/
function check(label, command, expectedClass, expectedValue) {
if (!command) {
console.log(` SKIP ${label} β command not found`);
return;
}
const arg = command.decorator.arguments[0];
const classOk = arg.$class === expectedClass;
const valueOk = arg.value === expectedValue;
if (classOk && valueOk) {
console.log(` PASS ${label}`);
passed++;
} else {
console.log(` FAIL ${label}`);
if (!classOk) {
console.log(` $class expected: ${expectedClass}`);
console.log(` actual: ${arg.$class}`);
}
if (!valueOk) {
console.log(` value expected: ${JSON.stringify(expectedValue)} (${typeof expectedValue})`);
console.log(` actual: ${JSON.stringify(arg.value)} (${typeof arg.value})`);
}
failed++;
}
}
/**
* Assert decorator type-reference argument shape.
* @param {string} label test label
* @param {object|undefined} command decorator command
* @param {string} expectedName expected type name
* @param {string} expectedNamespace expected type namespace
* @returns {void}
*/
function checkTypeReference(label, command, expectedName, expectedNamespace) {
if (!command) {
console.log(` SKIP ${label} β command not found`);
return;
}
const arg = command.decorator.arguments[0];
const classOk = arg.$class === `${META}.DecoratorTypeReference`;
const typeName = arg.type && arg.type.name;
const typeNamespace = arg.type && arg.type.namespace;
const nameOk = typeName === expectedName;
const namespaceOk = typeNamespace === expectedNamespace;
if (classOk && nameOk && namespaceOk) {
console.log(` PASS ${label}`);
passed++;
} else {
console.log(` FAIL ${label}`);
if (!classOk) {
console.log(` $class expected: ${META}.DecoratorTypeReference`);
console.log(` actual: ${arg.$class}`);
}
if (!nameOk) {
console.log(` type.name expected: ${expectedName}`);
console.log(` actual: ${typeName}`);
}
if (!namespaceOk) {
console.log(` type.namespace expected: ${expectedNamespace}`);
console.log(` actual: ${typeNamespace}`);
}
failed++;
}
}
const find = (cmds, name, declaration, property) =>
cmds.find(c =>
c.decorator.name === name &&
(!declaration || c.target.declaration === declaration) &&
(!property || c.target.property === property)
);
function main() {
// 1. LOAD THE MODEL
const mm = new ModelManager();
mm.addCTOModel(DECORATED_MODEL_CTO);
// 2. DISPLAY THE AST
const ast = mm.getAst();
console.log('=== AST (CTO β AST) ===');
console.log(JSON.stringify(ast, null, 2));
console.log('=== ===');
// 3. EXTRACT THE VOCABULARIES
const { vocabularies: vocab } = DecoratorManager.extractVocabularies(mm, { locale: 'en' });
// 4. DISPLAY THE VOCABULARIES
console.log('\n=== Vocabulary YAML ===');
console.log(JSON.stringify(vocab, null, 2));
console.log('\n=== Vocabulary YAML ===');
// 5. EXTRACT THE DECORATOR COMMANDS SET
const vocabManager = new VocabularyManager();
vocabManager.addVocabulary(vocab[0]);
const decoratorCommandSet = vocabManager.generateDecoratorCommands(mm, 'en');
// 6. DISPLAY THE DECORATOR COMMANDS SET
console.log('\n=== Decorator Command Set ===');
console.log(JSON.stringify(decoratorCommandSet, null, 2));
console.log('\n=== Decorator Command Set ===');
// 7. APPLY THE DECORATOR COMMANDS SET TO THE PLAIN MODEL and display the CTO
const pmm = new ModelManager();
pmm.addCTOModel(PLAIN_MODEL_CTO);
const pmmWithDecorators = DecoratorManager.decorateModels(pmm, decoratorCommandSet);
const pmmAst = pmmWithDecorators.getModelFile('com.example@1.0.0').getAst();
console.log('\n=== Plain Model with Decorators ===');
console.log(Printer.toCTO(pmmAst));
console.log('\n=== Plain Model with Decorators ===');
const cmds = decoratorCommandSet.commands;
console.log('\n=== TEST RESULTS ===');
// 8. CHECKS
// Primary @Term label β should always be DecoratorString
check(
'@Term("Products") on Product β DecoratorString',
find(cmds, 'Term', 'Product'),
`${META}.DecoratorString`, 'Products'
);
check(
'@Term("Product Name") on Product.name β DecoratorString',
find(cmds, 'Term', 'Product', 'name'),
`${META}.DecoratorString`, 'Product Name'
);
checkTypeReference(
'@Term(Dimension) on Product.size β DecoratorTypeReference',
find(cmds, 'Term', 'Product', 'size'),
'Dimension',
'com.example@1.0.0'
);
// @Term_* with integer value β should be DecoratorNumber
check(
'@Term_sortOrder(1) on Product β DecoratorNumber',
find(cmds, 'Term_sortOrder', 'Product'),
`${META}.DecoratorNumber`, 1
);
check(
'@Term_rank(2) on Product.name β DecoratorNumber',
find(cmds, 'Term_rank', 'Product', 'name'),
`${META}.DecoratorNumber`, 2
);
// @Term_* with boolean value β should be DecoratorBoolean
check(
'@Term_featured(false) on Product β DecoratorBoolean',
find(cmds, 'Term_featured', 'Product'),
`${META}.DecoratorBoolean`, false
);
check(
'@Term_visible(true) on Product.name β DecoratorBoolean',
find(cmds, 'Term_visible', 'Product', 'name'),
`${META}.DecoratorBoolean`, true
);
console.log('\n=== TEST RESULTS ===');
console.log('\n=== TEST SUMMARY ===');
console.log(`\n ${passed} passed, ${failed} failed`);
console.log('\n=== TEST SUMMARY ===');
}
main();
Summary
| Stage |
String @Term |
Number @Term_* |
Boolean @Term_* |
Type ref @Term / @Term_* |
| CTO β AST |
OK |
OK |
OK |
OK |
| AST β vocabulary |
OK |
OK (numeric YAML) |
OK (boolean YAML) |
Lost (undefined) |
| vocabulary β DCS |
OK |
Coerced to DecoratorString |
Coerced to DecoratorString |
Coerced to "undefined" string |
| DCS β CTO |
OK |
Quoted strings |
Quoted strings |
@Term("undefined") |
Parsing (CTO β AST) is correct; the breakage is in vocabulary extraction and decorator command generation (and therefore round-trip back to CTO).
Discussion π£
@Term()and@Term_*()decorators in CTO files accept arguments of typeString,Number,Boolean, andTypeReference(via the Concerto decorator argument system).The round-trip CTO β vocabulary β decorator command set β CTO loses non-string argument types: numbers and booleans become strings, and type references become the literal string
"undefined". String@Termvalues round-trip correctly.Questions:
@Termand@Term_*(String,Number,Boolean,TypeReference) per the language design?@Termand@Term_*?DecoratorTypeReferenceβ should that be supported in@Termand@Term_*, and what should it look like in vocabulary YAML?Reproduction
Script:
reproduce-term-roundtrip.jsThe script:
DecoratorManager.extractVocabularies.VocabularyManager.generateDecoratorCommands.DecoratorString,DecoratorNumber,DecoratorBoolean, andDecoratorTypeReferencetypes.Result: 2 passed, 5 failed (see Test results below).
Example
CTO model (input)
AST (CTO β AST)
Parsing preserves correct argument types in the AST:
{ "$class": "concerto.metamodel@1.0.0.Models", "models": [ { "$class": "concerto.metamodel@1.0.0.Model", "decorators": [], "namespace": "com.example@1.0.0", "imports": [], "declarations": [ { "$class": "concerto.metamodel@1.0.0.ConceptDeclaration", "name": "Dimension", "isAbstract": false, "properties": [ { "$class": "concerto.metamodel@1.0.0.IntegerProperty", "name": "unit", "isArray": false, "isOptional": false } ] }, { "$class": "concerto.metamodel@1.0.0.ConceptDeclaration", "name": "Product", "isAbstract": false, "properties": [ { "$class": "concerto.metamodel@1.0.0.StringProperty", "name": "name", "isArray": false, "isOptional": false, "decorators": [ { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "Product Name" } ] }, { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_rank", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorNumber", "value": 2 } ] }, { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_visible", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorBoolean", "value": true } ] } ] }, { "$class": "concerto.metamodel@1.0.0.IntegerProperty", "name": "size", "isArray": false, "isOptional": false, "decorators": [ { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorTypeReference", "type": { "$class": "concerto.metamodel@1.0.0.TypeIdentifier", "name": "Dimension" }, "isArray": false } ] }, { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_sizeType", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorTypeReference", "type": { "$class": "concerto.metamodel@1.0.0.TypeIdentifier", "name": "Dimension" }, "isArray": false } ] } ] } ], "decorators": [ { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "Products" } ] }, { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_sortOrder", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorNumber", "value": 1 } ] }, { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_featured", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorBoolean", "value": false } ] } ] } ] } ] }Vocabulary YAML (AST β vocabulary)
Extracted vocabulary loses type information for non-string
@Term/@Term_*arguments (undefinedplaceholders):Decorator command set (vocabulary β DCS)
generateDecoratorCommandscoerces all arguments toDecoratorString(numbers/booleans as JSON-ish values, type refs as"undefined"):{ "$class": "org.accordproject.decoratorcommands@0.4.0.DecoratorCommandSet", "name": "terms-en", "version": "1.0.0", "commands": [ { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "Products" } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_sortOrder", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": 1 } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_featured", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": false } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product", "property": "name" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "Product Name" } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product", "property": "name" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_rank", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": 2 } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product", "property": "name" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_visible", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": true } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product", "property": "size" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "undefined" } ] } }, { "$class": "org.accordproject.decoratorcommands@0.4.0.Command", "type": "UPSERT", "target": { "$class": "org.accordproject.decoratorcommands@0.4.0.CommandTarget", "namespace": "com.example@1.0.0", "declaration": "Product", "property": "size" }, "decorator": { "$class": "concerto.metamodel@1.0.0.Decorator", "name": "Term_sizeType", "arguments": [ { "$class": "concerto.metamodel@1.0.0.DecoratorString", "value": "undefined" } ] } } ] }Regenerated CTO (DCS β CTO)
Applying the command set to a plain model produces quoted string arguments for everything that was not a string label:
Test results
Assertions from
reproduce-term-roundtrip.json the generated decorator command set:SCRIPT
Summary
@Term@Term_*@Term_*@Term/@Term_*undefined)"undefined"string@Term("undefined")Parsing (CTO β AST) is correct; the breakage is in vocabulary extraction and decorator command generation (and therefore round-trip back to CTO).