@izara_project/izara-core-generate-service-code 1.0.97 → 1.0.99

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 (56) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +45 -8
  3. package/src/compile/loadSchemas.js +26 -6
  4. package/src/emit/application/coreFunction/emitCoreFunctionArtifacts.js +15 -1
  5. package/src/emit/application/coreFunction/templates/InitialSetupConfig.ejs +8 -4
  6. package/src/emit/application/crud/templates/CreateEndpoint_Main.ejs +4 -4
  7. package/src/emit/application/crud/templates/CreateLogicalLib.ejs +60 -8
  8. package/src/emit/application/crud/templates/DeleteEndpoint_Main.ejs +4 -4
  9. package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +2 -2
  10. package/src/emit/application/crud/templates/FlowMain_Wbs.ejs +2 -2
  11. package/src/emit/application/crud/templates/UpdateEndpoint_Main.ejs +4 -4
  12. package/src/emit/application/custom/customFlowS3.js +4 -4
  13. package/src/emit/application/custom/templates/FlowEndpoint_Main.ejs +2 -2
  14. package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +5 -2
  15. package/src/emit/application/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +2 -2
  16. package/src/emit/application/rbac/emitDynamicRbacFlowSchemas.js +4 -1
  17. package/src/emit/application/rbac/emitRbacApplicationArtifacts.js +76 -1
  18. package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +2 -0
  19. package/src/emit/application/rbac/templates/ReservedUsageTtlExpired_HdrDss.ejs +176 -0
  20. package/src/emit/application/relationship/templates/ChangeRelationship_Main.ejs +3 -2
  21. package/src/emit/application/relationship/templates/CreateRelationship_Main.ejs +3 -2
  22. package/src/emit/application/relationship/templates/DeleteRelationship_Main.ejs +3 -2
  23. package/src/emit/application/relationship/templates/GetRelationship_Main.ejs +3 -2
  24. package/src/emit/application/relationship/templates/MoveRelationship_Main.ejs +3 -2
  25. package/src/emit/application/relationship/templates/UpdateRelationship_Main.ejs +3 -2
  26. package/src/emit/application/system/templates/RegisterComplete_Main.ejs +6 -2
  27. package/src/emit/application/system/templates/Register_Main.ejs +6 -2
  28. package/src/emit/application/system/templates/WebSocketComplete_Main.ejs +3 -3
  29. package/src/emit/infrastructure/functions/functionYamlArtifact.js +28 -0
  30. package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +21 -0
  31. package/src/emit/infrastructure/resources/iamResourceArtifacts.js +46 -6
  32. package/src/emit/infrastructure/resources/templates/GeneratedLambdaRoleServiceConfig.ejs +5 -3
  33. package/src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js +75 -22
  34. package/src/emit/schemas/ledger/emitLedgerSchemas.js +33 -11
  35. package/src/emit/schemas/ledger/templates/LedgerObjectSchema.ejs +3 -0
  36. package/src/emit/schemas/ledger/templates/LedgerRelationshipSchema.ejs +1 -0
  37. package/src/emit/schemas/propertyValue/emitPropertyValueSchemas.js +329 -0
  38. package/src/generate.js +10 -73
  39. package/src/output/mergeHooks.js +169 -15
  40. package/src/plan/domainRegistry.js +31 -5
  41. package/task.md +76 -0
  42. package/test/integration/graphDrivenFlowSchema.test.js +58 -0
  43. package/test/integration/publicApi.test.js +54 -1
  44. package/test/unit/application/createTargetRoleHandler.test.js +70 -1
  45. package/test/unit/application/crudApplicationArtifacts.test.js +4 -0
  46. package/test/unit/application/customFlowApplicationArtifacts.test.js +9 -0
  47. package/test/unit/application/relationshipApplicationArtifacts.test.js +30 -0
  48. package/test/unit/infrastructure/resourceArtifacts.test.js +53 -0
  49. package/test/unit/output/mergeHooks.test.js +405 -0
  50. package/test/unit/schemas/attributeTreeSchemas.test.js +41 -63
  51. package/test/unit/schemas/attributeTreeSingleService.test.js +175 -0
  52. package/test/unit/schemas/ledgerSchemas.test.js +33 -7
  53. package/test/unit/schemas/propertyValueSchemas.test.js +265 -0
  54. package/test/unit/templates/hookTagsValidation.test.js +83 -0
  55. package/src/emit/schemas/attributeTree/emitAttributeTreeTargetSchemas.js +0 -153
  56. package/src/emit/schemas/attributeTree/resolveTargetServicePath.js +0 -111
package/src/generate.js CHANGED
@@ -1,4 +1,3 @@
1
- import path from 'node:path';
2
1
  import { buildGenerationPlan } from './plan/buildGenerationPlan.js';
3
2
  import { resolveEmissionGroups } from './plan/domainRegistry.js';
4
3
  import { commitArtifacts } from './output/commitArtifacts.js';
@@ -8,10 +7,6 @@ import {
8
7
  resolveExtendedObjectSchemas,
9
8
  resolveGraphServiceTags
10
9
  } from './compile/loadSchemas.js';
11
- import {
12
- resolveTargetServicePath,
13
- emitAttributeTreeTargetSchemas
14
- } from './emit/schemas/attributeTree/emitAttributeTreeSchemas.js';
15
10
 
16
11
  export async function generate(rootPath, options = {}) {
17
12
  const startedAt = Date.now();
@@ -29,9 +24,17 @@ export async function generate(rootPath, options = {}) {
29
24
  errors
30
25
  ] = await Promise.all([
31
26
  loadLocalSchemaDirectory(project, 'ObjectSchemas', schemaLoadOptions),
32
- loadLocalSchemaDirectory(project, 'RelationshipSchemas', schemaLoadOptions),
27
+ loadLocalSchemaDirectory(
28
+ project,
29
+ 'RelationshipSchemas',
30
+ schemaLoadOptions
31
+ ),
33
32
  loadLocalSchemaDirectory(project, 'FlowSchemas', schemaLoadOptions),
34
- loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions),
33
+ loadLocalSchemaDirectory(
34
+ project,
35
+ 'NotificationSchemas',
36
+ schemaLoadOptions
37
+ ),
35
38
  loadLocalSchemaDirectory(project, 'ErrorSchemas', schemaLoadOptions)
36
39
  ]);
37
40
  const schemaBucketName =
@@ -101,14 +104,6 @@ export async function generate(rootPath, options = {}) {
101
104
  )
102
105
  );
103
106
  }
104
- const targetFiles = await _emitTargetServiceAttributeTreeSchemas({
105
- project,
106
- objects,
107
- logger: options.logger
108
- });
109
- for (const targetFile of targetFiles) {
110
- files.push(targetFile);
111
- }
112
107
  const durationMs = Date.now() - startedAt;
113
108
  console.log(
114
109
  `[INFO] [Generate] STATUS=FINISHED | ` +
@@ -126,61 +121,3 @@ export async function generate(rootPath, options = {}) {
126
121
  }
127
122
  }
128
123
 
129
- async function _emitTargetServiceAttributeTreeSchemas({
130
- project,
131
- objects,
132
- logger
133
- }) {
134
- if (!Array.isArray(objects)) {
135
- return [];
136
- }
137
- const committedFiles = [];
138
- for (const object of objects) {
139
- if (!Array.isArray(object?.addOnDataStructure)) {
140
- continue;
141
- }
142
- for (const addOn of object.addOnDataStructure) {
143
- if (addOn?.type === 'attributeTree' && addOn?.attributeTreeTag) {
144
- const attributeTag = addOn.attributeTreeTag;
145
- const targetServiceRoot = resolveTargetServicePath({
146
- currentServiceRoot: project.rootPath,
147
- attributeTag
148
- });
149
- if (targetServiceRoot) {
150
- const artifacts = emitAttributeTreeTargetSchemas({
151
- attributeTag,
152
- targetServiceRoot
153
- });
154
- const relativeArtifacts = artifacts.map((artifact) => {
155
- return {
156
- path: path.relative(targetServiceRoot, artifact.path),
157
- content: artifact.content
158
- };
159
- });
160
- const targetPlan = buildGenerationPlan(
161
- { outputPath: targetServiceRoot },
162
- relativeArtifacts,
163
- {
164
- managedScopes: {
165
- 'attribute-tree-target-schemas': relativeArtifacts.map(
166
- (a) => a.path
167
- )
168
- }
169
- }
170
- );
171
- const targetFiles = await commitArtifacts(targetPlan);
172
- for (const targetFile of targetFiles) {
173
- committedFiles.push(targetFile);
174
- }
175
- if (logger && typeof logger.info === 'function') {
176
- logger.info(
177
- `[INFO] [AttributeTree] Target schemas emitted for ` +
178
- `tag=${attributeTag} to ${targetServiceRoot}`
179
- );
180
- }
181
- }
182
- }
183
- }
184
- }
185
- return committedFiles;
186
- }
@@ -24,7 +24,10 @@ async function mergeHook(outputRoot, artifact) {
24
24
  }
25
25
  }
26
26
 
27
- if (sourceContent !== null && (artifact.writeWhenFileExist === false || artifact.writeWhenFileExists === false)) {
27
+ const skipWrite =
28
+ artifact.writeWhenFileExist === false ||
29
+ artifact.writeWhenFileExists === false;
30
+ if (sourceContent !== null && skipWrite) {
28
31
  return sourceContent;
29
32
  }
30
33
 
@@ -37,32 +40,183 @@ async function mergeHook(outputRoot, artifact) {
37
40
  return artifact.content;
38
41
  }
39
42
 
43
+ _validateHookIntegrity(artifact.path, sourceContent);
44
+ _warnOrphanedHooks(
45
+ artifact.path,
46
+ sourceContent,
47
+ artifact.content,
48
+ matchers
49
+ );
50
+
51
+ const tagOccurrences = new Map();
40
52
  return artifact.content.replace(
41
53
  matchers.anyTag,
42
54
  (_match, p1, _body1, p2) => {
43
- const tagName = p1 || p2;
44
- const hookBlock = sourceContent.match(matchers.tagPattern(tagName))?.[0];
45
- return hookBlock ?? _match;
55
+ const tagName = _extractTagName(p1, p2);
56
+ if (!tagName) {
57
+ return _match;
58
+ }
59
+ let count = 0;
60
+ if (tagOccurrences.has(tagName)) {
61
+ count = tagOccurrences.get(tagName);
62
+ }
63
+ tagOccurrences.set(tagName, count + 1);
64
+
65
+ const matches = sourceContent.match(matchers.tagPattern(tagName));
66
+ if (!matches || count >= matches.length) {
67
+ return _match;
68
+ }
69
+ const hookBlock = matches[count];
70
+ if (hookBlock !== null && hookBlock !== undefined) {
71
+ return _normalizeHookBlock(hookBlock, tagName);
72
+ }
73
+ return _match;
46
74
  }
47
75
  );
48
76
  }
49
77
 
78
+ function _validateHookIntegrity(filePath, content) {
79
+ const lower = filePath.toLowerCase();
80
+ let openCount = 0;
81
+ let closeCount = 0;
82
+
83
+ if (
84
+ lower.endsWith('.js') ||
85
+ lower.endsWith('.mjs') ||
86
+ lower.endsWith('.cjs')
87
+ ) {
88
+ const openP1 = content.match(/\/\/\s*\(<\s*[\w.-]+/g);
89
+ const closeP1 = content.match(/\/\/\s*\(<\/\s*[\w.-]+/g);
90
+ const openP2 = content.match(/\/\/\s*<#\s*[\w.-]+/g);
91
+ const closeP2 = content.match(/\/\/\s*<#\/\s*[\w.-]+/g);
92
+
93
+ if (openP1) {
94
+ openCount += openP1.length;
95
+ }
96
+ if (closeP1) {
97
+ closeCount += closeP1.length;
98
+ }
99
+ if (openP2) {
100
+ openCount += openP2.length;
101
+ }
102
+ if (closeP2) {
103
+ closeCount += closeP2.length;
104
+ }
105
+ } else if (lower.endsWith('.yml') || lower.endsWith('.yaml')) {
106
+ const openYml = content.match(/#\s*<#\s*[\w.-]+/g);
107
+ const closeYml = content.match(/#\s*<#\/\s*[\w.-]+/g);
108
+
109
+ if (openYml) {
110
+ openCount += openYml.length;
111
+ }
112
+ if (closeYml) {
113
+ closeCount += closeYml.length;
114
+ }
115
+ }
116
+
117
+ if (openCount !== closeCount) {
118
+ throw new Error(
119
+ `[mergeHooks] Malformed hook tags in '${filePath}': found ` +
120
+ `${openCount} open tag(s) but ${closeCount} close tag(s). ` +
121
+ `Aborting to prevent code loss.`
122
+ );
123
+ }
124
+ }
125
+
126
+ function _warnOrphanedHooks(
127
+ filePath,
128
+ sourceContent,
129
+ artifactContent,
130
+ matchers
131
+ ) {
132
+ const anyTagRegex = new RegExp(matchers.anyTag.source, 'gm');
133
+ let match = anyTagRegex.exec(sourceContent);
134
+
135
+ while (match !== null) {
136
+ const tagName = _extractTagName(match[1], match[3]);
137
+ let body = '';
138
+ if (typeof match[2] === 'string') {
139
+ body = match[2];
140
+ } else if (typeof match[4] === 'string') {
141
+ body = match[4];
142
+ }
143
+
144
+ if (tagName && body.trim().length > 0) {
145
+ const templatePattern = matchers.tagPattern(tagName);
146
+ const existsInTemplate = templatePattern.test(artifactContent);
147
+ if (!existsInTemplate) {
148
+ console.warn(
149
+ `[WARN] [mergeHooks] Hook '${tagName}' in '${filePath}' ` +
150
+ `contains custom code but is no longer present in template.`
151
+ );
152
+ }
153
+ }
154
+ match = anyTagRegex.exec(sourceContent);
155
+ }
156
+ }
157
+
158
+ function _normalizeHookBlock(hookBlock, tagName) {
159
+ if (typeof hookBlock !== 'string' || hookBlock.length === 0) {
160
+ return hookBlock;
161
+ }
162
+ const esc = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
163
+ const openRegex = new RegExp(`(//\\s*\\(<\\s*${esc}\\s*)(?!>)\\)`);
164
+ const closeRegex = new RegExp(`(//\\s*\\(<\\/\\s*${esc}\\s*)(?!>)\\)`);
165
+ let normalized = hookBlock.replace(openRegex, '$1>)');
166
+ normalized = normalized.replace(closeRegex, '$1>)');
167
+ return normalized;
168
+ }
169
+
170
+ function _extractTagName(p1, p2) {
171
+ if (typeof p1 === 'string' && p1.length > 0) {
172
+ return p1;
173
+ }
174
+ if (typeof p2 === 'string' && p2.length > 0) {
175
+ return p2;
176
+ }
177
+ return '';
178
+ }
179
+
50
180
  function hookMatchers(filePath) {
51
- if (filePath.endsWith('.js')) {
181
+ const lower = filePath.toLowerCase();
182
+ if (
183
+ lower.endsWith('.js') ||
184
+ lower.endsWith('.mjs') ||
185
+ lower.endsWith('.cjs')
186
+ ) {
187
+ const jsP1 =
188
+ '\\/\\/\\s*\\(<\\s*([\\w.-]+)\\s*>?\\s*\\)([\\s\\S]*?)' +
189
+ '\\/\\/\\s*\\(<\\/\\s*\\1\\s*>?\\s*\\)';
190
+ const jsP2 =
191
+ '\\/\\/\\s*<#\\s*([\\w.-]+)\\s*#>([\\s\\S]*?)' +
192
+ '\\/\\/\\s*<#\\/\\s*\\3\\s*#>';
52
193
  return {
53
- anyTag: /(?:\/\/\(<(\w+)>\)([\s\S]*?)\/\/\(<\/\1>\)|\/\/\s*<#(\w+)#>([\s\S]*?)\/\/\s*<#\/\3#>)/gm,
54
- tagPattern: (name) =>
55
- new RegExp(
56
- `(?:\\/\\/\\(<${name}>\\)([\\s\\S]*?)\\/\\/\\(<\\/${name}>\\)|\\/\\/\\s*<#${name}#>([\\s\\S]*?)\\/\\/\\s*<#\\/${name}#>)`,
57
- 'gm'
58
- )
194
+ anyTag: new RegExp(`(?:${jsP1}|${jsP2})`, 'gm'),
195
+ tagPattern: (name) => {
196
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
197
+ const p1 =
198
+ `\\/\\/\\s*\\(<\\s*${esc}\\s*>?\\s*\\)([\\s\\S]*?)` +
199
+ `\\/\\/\\s*\\(<\\/\\s*${esc}\\s*>?\\s*\\)`;
200
+ const p2 =
201
+ `\\/\\/\\s*<#\\s*${esc}\\s*#>([\\s\\S]*?)` +
202
+ `\\/\\/\\s*<#\\/\\s*${esc}\\s*#>`;
203
+ return new RegExp(`(?:${p1}|${p2})`, 'gm');
204
+ }
59
205
  };
60
206
  }
61
- if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
207
+ if (lower.endsWith('.yml') || lower.endsWith('.yaml')) {
208
+ const ymlPattern =
209
+ '#\\s*<#\\s*([\\w.-]+)\\s*#>([\\s\\S]*?)' +
210
+ '#\\s*<#\\/\\s*\\1\\s*#>';
62
211
  return {
63
- anyTag: /#<#(\w+)#>([\s\S]*?)#<#\/\1#>/gm,
64
- tagPattern: (name) =>
65
- new RegExp(`#<#${name}#>([\\s\\S]*?)#<#\\/${name}#>`, 'gm')
212
+ anyTag: new RegExp(ymlPattern, 'gm'),
213
+ tagPattern: (name) => {
214
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
215
+ return new RegExp(
216
+ `#\\s*<#\\s*${esc}\\s*#>([\\s\\S]*?)#\\s*<#\\/\\s*${esc}\\s*#>`,
217
+ 'gm'
218
+ );
219
+ }
66
220
  };
67
221
  }
68
222
  return null;
@@ -58,6 +58,12 @@ import {
58
58
  ledgerObjectSchemaManagedPaths,
59
59
  ledgerRelationshipSchemaManagedPaths
60
60
  } from '../emit/schemas/ledger/emitLedgerSchemas.js';
61
+ import {
62
+ emitPropertyValueObjectSchemas,
63
+ emitPropertyValueRelationshipSchemas,
64
+ propertyValueObjectSchemaManagedPaths,
65
+ propertyValueRelationshipSchemaManagedPaths
66
+ } from '../emit/schemas/propertyValue/emitPropertyValueSchemas.js';
61
67
  import {
62
68
  emitErrorSchemas,
63
69
  errorSchemaManagedPaths
@@ -85,7 +91,9 @@ export async function resolveEmissionGroups(context) {
85
91
  resource,
86
92
  coreFunction,
87
93
  systemText,
88
- errorSchemas
94
+ errorSchemas,
95
+ ledgerObjectArtifacts,
96
+ ledgerRelationshipArtifacts
89
97
  ] = await Promise.all([
90
98
  emitCrudApplicationArtifacts({
91
99
  objects,
@@ -125,6 +133,14 @@ export async function resolveEmissionGroups(context) {
125
133
  project,
126
134
  errors,
127
135
  objectGraphServiceTags
136
+ }),
137
+ emitLedgerObjectSchemas({
138
+ objects,
139
+ serviceTag: project.serviceTag
140
+ }),
141
+ emitLedgerRelationshipSchemas({
142
+ objects,
143
+ serviceTag: project.serviceTag
128
144
  })
129
145
  ]);
130
146
 
@@ -204,15 +220,25 @@ export async function resolveEmissionGroups(context) {
204
220
  {
205
221
  scope: 'ledger-object-schemas',
206
222
  managedPaths: ledgerObjectSchemaManagedPaths(objects),
207
- artifacts: emitLedgerObjectSchemas({
223
+ artifacts: ledgerObjectArtifacts
224
+ },
225
+ {
226
+ scope: 'ledger-relationship-schemas',
227
+ managedPaths: ledgerRelationshipSchemaManagedPaths(objects),
228
+ artifacts: ledgerRelationshipArtifacts
229
+ },
230
+ {
231
+ scope: 'property-value-object-schemas',
232
+ managedPaths: propertyValueObjectSchemaManagedPaths(objects),
233
+ artifacts: emitPropertyValueObjectSchemas({
208
234
  objects,
209
235
  serviceTag: project.serviceTag
210
236
  })
211
237
  },
212
238
  {
213
- scope: 'ledger-relationship-schemas',
214
- managedPaths: ledgerRelationshipSchemaManagedPaths(objects),
215
- artifacts: emitLedgerRelationshipSchemas({
239
+ scope: 'property-value-relationship-schemas',
240
+ managedPaths: propertyValueRelationshipSchemaManagedPaths(objects),
241
+ artifacts: emitPropertyValueRelationshipSchemas({
216
242
  objects,
217
243
  serviceTag: project.serviceTag
218
244
  })
package/task.md ADDED
@@ -0,0 +1,76 @@
1
+ # Task Checklist: Attribute Tree Single-Service Code Generation Consolidation
2
+
3
+ > **Canonical Architecture Plan**: [[10.Systems/GenerateServiceCode/30-Generation/Task Roadmap - Attribute Tree Per-Repo Generation Refactoring|Task Roadmap in Obsidian]]
4
+ > **Selected Design**: **Single Main Service (Self-Repo Consolidation)** + **Style 1 (`generatedCode/<domain>/`)**
5
+ > **Repository**: `IzaraCore-SupportServices-GenerateServiceCode-Seagame`
6
+
7
+ ---
8
+
9
+ ## 🎯 Target Architecture: Single Unified Service (Self-Repo)
10
+
11
+ All schemas for an `attributeTree` (Tree nodes, Standard Value Handler node, and cascading PropertyValue nodes) are emitted directly inside the **Main Service** workspace:
12
+
13
+ ```text
14
+ app/src/schemas/
15
+ ├── ObjectSchemas/
16
+ │ ├── sellOffer.js <-- Source ObjectSchema
17
+ │ └── generatedCode/
18
+ │ ├── attributeTree/
19
+ │ │ ├── sellOfferTermLink.js <-- Link Node
20
+ │ │ ├── sellOfferTermValue.js <-- Value Node
21
+ │ │ ├── sellOfferTermLabel.js <-- Label Node
22
+ │ │ └── sellOfferTermValueStandard.js <-- Handler Node (Self-Repo)
23
+ │ │
24
+ │ └── propertyValue/ <-- Cascading from ValueStandard
25
+ │ ├── sellOfferTermValueStandardPropertyLabel.js
26
+ │ └── sellOfferTermValueStandardPropertyValue.js
27
+ │
28
+ └── RelationshipSchemas/
29
+ └── generatedCode/
30
+ ├── attributeTree/
31
+ │ ├── hasSellOfferTermLink.js
32
+ │ ├── disabledSellOfferTermLink.js
33
+ │ ├── isSellOfferTerm.js
34
+ │ └── isSellOfferTermLabel.js
35
+ │
36
+ └── propertyValue/
37
+ ├── hasSellOfferTermValueStandardPropertyValue.js
38
+ ├── disabledSellOfferTermValueStandardPropertyValue.js
39
+ └── isSellOfferTermValueStandardPropertyLabel.js
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 📋 Implementation Checklist
45
+
46
+ - [x] **TASK-01: Recursive Schema Loading & Directives Loader**
47
+ - **File**: `src/compile/loadSchemas.js`
48
+ - **Acceptance Criteria**: `loadLocalSchemaDirectory(project, 'ObjectSchemas')` discovers and validates schemas nested inside `generatedCode/**`.
49
+
50
+ - [x] **TASK-02: Consolidate ValueStandard into `emitAttributeTreeSchemas.js`**
51
+ - **File**: `src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js`
52
+ - **Action**:
53
+ 1. Emit `${tag}ValueStandard.js` directly within `emitAttributeTreeObjectSchemas`.
54
+ 2. Add `${tag}ValueStandard.js` to `attributeTreeObjectSchemaManagedPaths`.
55
+ 3. Include preservation hook `//(<addOnDataStructure${tag}ValueStandard>)` with commented `propertyValue` addOn template.
56
+ - **Acceptance Criteria**: Single-service attributeTree generates all 4 ObjectSchemas (`Link`, `Value`, `Label`, `ValueStandard`).
57
+
58
+ - [x] **TASK-03: Remove Target Directive Scope & Dead Code**
59
+ - **Files**:
60
+ - `src/plan/domainRegistry.js` (Removed `attribute-tree-target-value-standard` scope & unused `directives` param)
61
+ - `src/emit/schemas/attributeTree/emitAttributeTreeTargetValueStandard.js` (Deleted dead file)
62
+ - `src/compile/loadSchemas.js` (Removed obsolete `loadGenerateDirectives`)
63
+ - `src/generate.js` (Removed `loadGenerateDirectives` call & `directives` passing)
64
+ - `test/unit/schemas/attributeTreeSingleService.test.js` (Removed obsolete directive loader tests)
65
+ - **Acceptance Criteria**: Clean scope tracking under `attribute-tree-object-schemas` with zero dead directive loading.
66
+
67
+ - [x] **TASK-04: Single-Service & Cascading Integration Verification**
68
+ - **File**: `test/unit/schemas/attributeTreeSingleService.test.js`
69
+ - **Action**: Verifies single-service generation and cascading generation to `propertyValue` within the same repository.
70
+ - **Acceptance Criteria**: 100% passing tests with zero cross-repo write or external directive needed.
71
+
72
+ - [x] **TASK-05: Synchronize Vault & Documentation**
73
+ - **Vault Files**:
74
+ - `10.Systems/GenerateServiceCode/20-Schema/Dynamic Schema.md`
75
+ - `40.Vault/Operations/timeline-log.md`
76
+ - `40.Vault/Memory/active-context.md`
@@ -544,6 +544,64 @@ test('validateFlowSchema passes for s3 event with valid bucketName', () => {
544
544
  );
545
545
  });
546
546
 
547
+ test('validateFlowSchema passes for s3 event with explicit S3 flowSteps', () => {
548
+ assert.equal(
549
+ validateFlowSchema({
550
+ flowTag: 'uploadFlow',
551
+ event: ['s3'],
552
+ bucketName: 'MyUploadBucket',
553
+ flowSteps: [
554
+ { stepName: 'CreateObject', event: ['wbs'] },
555
+ { stepName: 'ReservedLimit', event: ['sqs'] },
556
+ { stepName: 'CreatePreSignUrl', event: ['sqs'] },
557
+ { stepName: 'ProcessAfterUpload', event: ['s3'] },
558
+ { stepName: 'ConfirmAfterUpload', event: ['sqs'] }
559
+ ]
560
+ }),
561
+ true
562
+ );
563
+ });
564
+
565
+ test('validateFlowSchema passes for s3 event without flowSteps', () => {
566
+ assert.equal(
567
+ validateFlowSchema({
568
+ flowTag: 'uploadFlow',
569
+ event: ['s3'],
570
+ bucketName: 'MyUploadBucket'
571
+ }),
572
+ true
573
+ );
574
+ });
575
+
576
+ test('validateFlowSchema rejects s3 event with invalid entry stepName', () => {
577
+ assert.throws(
578
+ () =>
579
+ validateFlowSchema({
580
+ flowTag: 'uploadFlow',
581
+ event: ['s3'],
582
+ bucketName: 'MyUploadBucket',
583
+ flowSteps: [{ stepName: 'InvalidEntry' }]
584
+ }),
585
+ /Entry step flowSteps\[0\] for s3 flow must have stepName 'CreateObject' or 'Start'/
586
+ );
587
+ });
588
+
589
+ test('validateFlowSchema rejects s3 event with invalid non-entry stepName', () => {
590
+ assert.throws(
591
+ () =>
592
+ validateFlowSchema({
593
+ flowTag: 'uploadFlow',
594
+ event: ['s3'],
595
+ bucketName: 'MyUploadBucket',
596
+ flowSteps: [
597
+ { stepName: 'CreateObject' },
598
+ { stepName: 'InvalidStep' }
599
+ ]
600
+ }),
601
+ /is invalid for s3 flow/
602
+ );
603
+ });
604
+
547
605
  test('validateFlowSchema rejects event with storedCache or triggerCache as invalid event', () => {
548
606
  assert.throws(
549
607
  () =>
@@ -208,7 +208,7 @@ test('generate commits the system WebSocket completion path for own-topic flows'
208
208
  assert.match(webSocketComplete, /\(\<createIdentifiersFlow\>\)/);
209
209
  assert.match(webSocketComplete, /\(\<beforePostMessage\>\)/);
210
210
  assert.match(webSocketComplete, /postToConnection\(_izContext,/);
211
- const webSocketHandler = await readFile(
211
+ const webSocketHandler = await readFile(
212
212
  join(
213
213
  rootPath,
214
214
  'app/src/generatedCode/SystemFlowSchemas/WebSocketMain/source/WebSocketComplete_HdrSqs.js'
@@ -217,6 +217,49 @@ test('generate commits the system WebSocket completion path for own-topic flows'
217
217
  );
218
218
  assert.match(webSocketHandler, /record\.body\.TopicArn/);
219
219
  assert.match(webSocketHandler, /addCallingFlowToPassOnProperties/);
220
+
221
+ const registerComplete = await readFile(
222
+ join(
223
+ rootPath,
224
+ 'app/src/generatedCode/SystemFlowSchemas/Register/source/RegisterComplete_Main.js'
225
+ ),
226
+ 'utf8'
227
+ );
228
+ assert.match(registerComplete, /getFlowSchema\.getFlowSchemaS3WithCache/);
229
+ assert.doesNotMatch(registerComplete, /getObjectSchema\.getFlowSchemaS3WithCache/);
230
+
231
+ const registerMain = await readFile(
232
+ join(
233
+ rootPath,
234
+ 'app/src/generatedCode/SystemFlowSchemas/Register/source/Register_Main.js'
235
+ ),
236
+ 'utf8'
237
+ );
238
+ assert.match(registerMain, /getFlowSchema\.getFlowSchemaS3WithCache/);
239
+ assert.doesNotMatch(registerMain, /getObjectSchema\.getFlowSchemaS3WithCache/);
240
+
241
+ assert.match(webSocketComplete, /getFlowSchema\.getFlowSchemaS3WithCache/);
242
+ assert.doesNotMatch(webSocketComplete, /getObjectSchema\.getFlowSchemaS3WithCache/);
243
+
244
+ const trackHdrWbs = await readFile(
245
+ join(
246
+ rootPath,
247
+ 'app/src/generatedCode/FlowSchemas/Track/source/Track_HdrWbs.js'
248
+ ),
249
+ 'utf8'
250
+ );
251
+ assert.match(trackHdrWbs, /getFlowSchema\.getFlowSchemaS3WithCache/);
252
+ assert.doesNotMatch(trackHdrWbs, /getObjectSchema\.getFlowSchemaS3WithCache/);
253
+
254
+ const trackWbsMain = await readFile(
255
+ join(
256
+ rootPath,
257
+ 'app/src/generatedCode/FlowSchemas/Track/source/Track_WbsMain.js'
258
+ ),
259
+ 'utf8'
260
+ );
261
+ assert.match(trackWbsMain, /getFlowSchema\.getFlowSchemaS3WithCache/);
262
+ assert.doesNotMatch(trackWbsMain, /getObjectSchema\.getFlowSchemaS3WithCache/);
220
263
  });
221
264
 
222
265
  test('generate commits the trigger-cache suite with its queues when triggerFlowTypes is set', async () => {
@@ -245,6 +288,16 @@ test('generate commits the trigger-cache suite with its queues when triggerFlowT
245
288
  ),
246
289
  /CartCheckTriggerFlowsHdrSqs/
247
290
  );
291
+
292
+ const cartCheckTrigger = await readFile(
293
+ join(
294
+ rootPath,
295
+ 'app/src/generatedCode/FlowSchemas/Cart/source/CartCheckTriggerFlows_Main.js'
296
+ ),
297
+ 'utf8'
298
+ );
299
+ assert.match(cartCheckTrigger, /getFlowSchema\.getFlowSchemaS3WithCache/);
300
+ assert.doesNotMatch(cartCheckTrigger, /getObjectSchema\.getFlowSchemaS3WithCache/);
248
301
  });
249
302
 
250
303
  test('generate removes the system WebSocket path when the last own-topic flow is removed', async () => {