scene-capability-engine 3.6.45 → 3.6.47

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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -0
  3. package/README.zh.md +1 -0
  4. package/docs/agent-runtime/symbol-evidence.schema.json +1 -1
  5. package/docs/command-reference.md +8 -0
  6. package/docs/interactive-customization/dialogue-governance-policy-baseline.json +4 -1
  7. package/docs/interactive-customization/embedded-assistant-authorization-dialogue-rules.md +5 -0
  8. package/docs/releases/README.md +2 -0
  9. package/docs/releases/v3.6.46.md +23 -0
  10. package/docs/releases/v3.6.47.md +23 -0
  11. package/docs/sce-business-mode-map.md +2 -1
  12. package/docs/sce-capability-matrix-e2e-example.md +2 -1
  13. package/docs/security-governance-default-baseline.md +2 -0
  14. package/docs/starter-kit/README.md +3 -0
  15. package/docs/zh/releases/README.md +2 -0
  16. package/docs/zh/releases/v3.6.46.md +23 -0
  17. package/docs/zh/releases/v3.6.47.md +23 -0
  18. package/lib/workspace/takeover-baseline.js +293 -1
  19. package/package.json +6 -2
  20. package/scripts/auto-strategy-router.js +231 -0
  21. package/scripts/capability-mapping-report.js +339 -0
  22. package/scripts/check-branding-consistency.js +140 -0
  23. package/scripts/check-sce-tracking.js +54 -0
  24. package/scripts/check-skip-allowlist.js +94 -0
  25. package/scripts/clarification-first-audit.js +322 -0
  26. package/scripts/errorbook-registry-health-gate.js +172 -0
  27. package/scripts/errorbook-release-gate.js +132 -0
  28. package/scripts/failure-attribution-repair.js +317 -0
  29. package/scripts/git-managed-gate.js +464 -0
  30. package/scripts/interactive-approval-event-projection.js +400 -0
  31. package/scripts/interactive-approval-workflow.js +829 -0
  32. package/scripts/interactive-authorization-tier-evaluate.js +413 -0
  33. package/scripts/interactive-change-plan-gate.js +225 -0
  34. package/scripts/interactive-context-bridge.js +617 -0
  35. package/scripts/interactive-customization-loop.js +1690 -0
  36. package/scripts/interactive-dialogue-governance.js +873 -0
  37. package/scripts/interactive-feedback-log.js +253 -0
  38. package/scripts/interactive-flow-smoke.js +238 -0
  39. package/scripts/interactive-flow.js +1059 -0
  40. package/scripts/interactive-governance-report.js +1112 -0
  41. package/scripts/interactive-intent-build.js +707 -0
  42. package/scripts/interactive-loop-smoke.js +215 -0
  43. package/scripts/interactive-moqui-adapter.js +304 -0
  44. package/scripts/interactive-plan-build.js +426 -0
  45. package/scripts/interactive-runtime-policy-evaluate.js +495 -0
  46. package/scripts/interactive-work-order-build.js +552 -0
  47. package/scripts/matrix-regression-gate.js +167 -0
  48. package/scripts/moqui-core-regression-suite.js +397 -0
  49. package/scripts/moqui-lexicon-audit.js +651 -0
  50. package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
  51. package/scripts/moqui-matrix-remediation-queue.js +852 -0
  52. package/scripts/moqui-metadata-extract.js +1340 -0
  53. package/scripts/moqui-rebuild-gate.js +167 -0
  54. package/scripts/moqui-release-summary.js +729 -0
  55. package/scripts/moqui-standard-rebuild.js +1370 -0
  56. package/scripts/moqui-template-baseline-report.js +682 -0
  57. package/scripts/npm-package-runtime-asset-check.js +221 -0
  58. package/scripts/problem-closure-gate.js +441 -0
  59. package/scripts/release-asset-integrity-check.js +216 -0
  60. package/scripts/release-asset-nonempty-normalize.js +166 -0
  61. package/scripts/release-drift-evaluate.js +223 -0
  62. package/scripts/release-drift-signals.js +255 -0
  63. package/scripts/release-governance-snapshot-export.js +132 -0
  64. package/scripts/release-ops-weekly-summary.js +934 -0
  65. package/scripts/release-risk-remediation-bundle.js +315 -0
  66. package/scripts/release-weekly-ops-gate.js +423 -0
  67. package/scripts/state-migration-reconciliation-gate.js +110 -0
  68. package/scripts/state-storage-tiering-audit.js +337 -0
  69. package/scripts/steering-content-audit.js +393 -0
  70. package/scripts/symbol-evidence-locate.js +370 -0
  71. package/template/.sce/README.md +1 -0
  72. package/template/.sce/steering/CORE_PRINCIPLES.md +25 -0
@@ -0,0 +1,873 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+
7
+ const DEFAULT_POLICY = 'docs/interactive-customization/dialogue-governance-policy-baseline.json';
8
+ const DEFAULT_AUTHORIZATION_DIALOGUE_POLICY = 'docs/interactive-customization/authorization-dialogue-policy-baseline.json';
9
+ const DEFAULT_OUT = '.sce/reports/interactive-dialogue-governance.json';
10
+ const DEFAULT_PROFILE = 'business-user';
11
+ const DIALOGUE_PROFILES = new Set(['business-user', 'system-maintainer']);
12
+ const EXECUTION_MODES = new Set(['suggestion', 'apply']);
13
+ const RUNTIME_ENVIRONMENTS = new Set(['dev', 'staging', 'prod']);
14
+ const UI_MODES = new Set(['user-app', 'ops-console']);
15
+
16
+ const BUILTIN_POLICY = {
17
+ version: '1.0.0',
18
+ mode: 'business-safe-assistant',
19
+ default_profile: DEFAULT_PROFILE,
20
+ length_policy: {
21
+ min_chars: 12,
22
+ max_chars: 1200,
23
+ min_significant_tokens: 4
24
+ },
25
+ deny_patterns: [
26
+ {
27
+ id: 'credential-exfiltration',
28
+ pattern: '\\b(export|dump|reveal|show)\\b[^.\\n]{0,80}\\b(password|secret|token|credential)\\b',
29
+ reason: 'request attempts to expose credentials or secrets'
30
+ },
31
+ {
32
+ id: 'approval-bypass',
33
+ pattern: '\\b(skip|bypass|disable)\\b[^.\\n]{0,80}\\b(approval|review|audit|permission)\\b',
34
+ reason: 'request attempts to bypass approval or governance flow'
35
+ }
36
+ ],
37
+ clarify_patterns: [
38
+ {
39
+ id: 'ambiguous-improve',
40
+ pattern: '\\b(improve|optimize|fix)\\b',
41
+ reason: 'goal is improvement-oriented but missing measurable target'
42
+ }
43
+ ],
44
+ response_rules: [
45
+ 'Use short business language and avoid deep technical jargon.',
46
+ 'Always restate objective, scope, and expected impact before any action recommendation.',
47
+ 'When risk or permission is involved, explicitly tell user what approval is required.',
48
+ 'If requirement is ambiguous, ask at most two focused clarification questions.',
49
+ 'If business scene, module, page, entity, or constraints are missing, clarify scope first; never replace missing understanding with blanket disable.',
50
+ 'Never propose credential export, approval bypass, or secret leakage.'
51
+ ],
52
+ clarification_templates: [
53
+ 'What business outcome should improve first (speed, accuracy, cost, compliance)?',
54
+ 'Which page or module should be changed first, and what should stay unchanged?',
55
+ 'Which entity or business rule is affected, and what constraint must remain unchanged?'
56
+ ],
57
+ profiles: {
58
+ 'business-user': {
59
+ mode: 'business-safe-assistant',
60
+ response_rules: [
61
+ 'Prefer business outcomes and measurable impact language over implementation details.'
62
+ ]
63
+ },
64
+ 'system-maintainer': {
65
+ mode: 'maintenance-safe-assistant',
66
+ length_policy: {
67
+ min_chars: 8,
68
+ max_chars: 1600,
69
+ min_significant_tokens: 3
70
+ },
71
+ deny_patterns: [
72
+ {
73
+ id: 'prod-change-without-ticket',
74
+ pattern: '\\b(prod|production)\\b[^.\\n]{0,120}\\b(without ticket|no ticket|skip ticket)\\b',
75
+ reason: 'production maintenance request is missing approved change ticket'
76
+ },
77
+ {
78
+ id: 'maintenance-no-rollback',
79
+ pattern: '\\b(hotfix|patch|change|deploy)\\b[^.\\n]{0,120}\\b(without rollback|no rollback)\\b',
80
+ reason: 'maintenance request lacks rollback safeguard'
81
+ }
82
+ ],
83
+ response_rules: [
84
+ 'For maintenance requests, require change ticket, rollback plan, and approval role before execution.',
85
+ 'When business context is incomplete, ask for the affected module/page/entity before deny or write restriction decisions.',
86
+ 'If request targets production, require staged validation evidence first.'
87
+ ],
88
+ clarification_templates: [
89
+ 'What is the approved change ticket id and rollback plan reference?',
90
+ 'Which environment should run first (dev/staging/prod), and who is the approver role?'
91
+ ]
92
+ }
93
+ }
94
+ };
95
+
96
+ const BUILTIN_AUTHORIZATION_DIALOGUE_POLICY = {
97
+ version: '1.0.0',
98
+ default_profile: DEFAULT_PROFILE,
99
+ prompt_templates: {
100
+ scope_confirmation: 'Confirm target module/page and business boundary before execution.',
101
+ impact_confirmation: 'Confirm expected business impact and out-of-scope boundaries.',
102
+ rollback_confirmation: 'Confirm rollback reference is prepared before apply.',
103
+ ticket_reference: 'Provide approved change ticket id.',
104
+ password_step_up: 'Complete one-time password authorization before apply.',
105
+ role_policy: 'Provide actor role and approver role according to role policy.',
106
+ role_separation: 'Confirm operator role and approver role are different.',
107
+ manual_review_ack: 'Acknowledge manual review is required before production apply.'
108
+ },
109
+ profiles: {
110
+ 'business-user': {
111
+ allow_execution_modes: ['suggestion'],
112
+ base_required_steps: ['scope_confirmation']
113
+ },
114
+ 'system-maintainer': {
115
+ allow_execution_modes: ['suggestion', 'apply'],
116
+ base_required_steps: ['scope_confirmation', 'impact_confirmation', 'rollback_confirmation']
117
+ }
118
+ },
119
+ environments: {
120
+ dev: {
121
+ require_ticket: false,
122
+ require_password_for_apply: false,
123
+ require_role_policy: false,
124
+ require_distinct_actor_roles: false,
125
+ require_manual_review_ack: false
126
+ },
127
+ staging: {
128
+ require_ticket: true,
129
+ require_password_for_apply: true,
130
+ require_role_policy: false,
131
+ require_distinct_actor_roles: false,
132
+ require_manual_review_ack: false
133
+ },
134
+ prod: {
135
+ require_ticket: true,
136
+ require_password_for_apply: true,
137
+ require_role_policy: true,
138
+ require_distinct_actor_roles: true,
139
+ require_manual_review_ack: true
140
+ }
141
+ }
142
+ };
143
+
144
+ function parseArgs(argv) {
145
+ const options = {
146
+ goal: null,
147
+ goalFile: null,
148
+ context: null,
149
+ policy: DEFAULT_POLICY,
150
+ profile: DEFAULT_PROFILE,
151
+ uiMode: null,
152
+ executionMode: 'suggestion',
153
+ runtimeEnvironment: 'staging',
154
+ authorizationDialoguePolicy: DEFAULT_AUTHORIZATION_DIALOGUE_POLICY,
155
+ out: DEFAULT_OUT,
156
+ json: false,
157
+ failOnDeny: false
158
+ };
159
+
160
+ for (let index = 0; index < argv.length; index += 1) {
161
+ const token = argv[index];
162
+ const next = argv[index + 1];
163
+ if (token === '--goal' && next) {
164
+ options.goal = next;
165
+ index += 1;
166
+ } else if (token === '--goal-file' && next) {
167
+ options.goalFile = next;
168
+ index += 1;
169
+ } else if (token === '--context' && next) {
170
+ options.context = next;
171
+ index += 1;
172
+ } else if (token === '--policy' && next) {
173
+ options.policy = next;
174
+ index += 1;
175
+ } else if (token === '--profile' && next) {
176
+ options.profile = next;
177
+ index += 1;
178
+ } else if (token === '--ui-mode' && next) {
179
+ options.uiMode = next;
180
+ index += 1;
181
+ } else if (token === '--execution-mode' && next) {
182
+ options.executionMode = next;
183
+ index += 1;
184
+ } else if (token === '--runtime-environment' && next) {
185
+ options.runtimeEnvironment = next;
186
+ index += 1;
187
+ } else if (token === '--authorization-dialogue-policy' && next) {
188
+ options.authorizationDialoguePolicy = next;
189
+ index += 1;
190
+ } else if (token === '--out' && next) {
191
+ options.out = next;
192
+ index += 1;
193
+ } else if (token === '--fail-on-deny') {
194
+ options.failOnDeny = true;
195
+ } else if (token === '--json') {
196
+ options.json = true;
197
+ } else if (token === '--help' || token === '-h') {
198
+ printHelpAndExit(0);
199
+ }
200
+ }
201
+
202
+ if (!options.goal && !options.goalFile) {
203
+ throw new Error('either --goal or --goal-file is required.');
204
+ }
205
+ options.profile = normalizeText(options.profile || DEFAULT_PROFILE).toLowerCase() || DEFAULT_PROFILE;
206
+ if (!DIALOGUE_PROFILES.has(options.profile)) {
207
+ throw new Error(`--profile must be one of: ${Array.from(DIALOGUE_PROFILES).join(', ')}`);
208
+ }
209
+ options.uiMode = normalizeText(
210
+ options.uiMode || (options.profile === 'system-maintainer' ? 'ops-console' : 'user-app')
211
+ ).toLowerCase();
212
+ if (!UI_MODES.has(options.uiMode)) {
213
+ throw new Error(`--ui-mode must be one of: ${Array.from(UI_MODES).join(', ')}`);
214
+ }
215
+ options.executionMode = normalizeText(options.executionMode || 'suggestion').toLowerCase() || 'suggestion';
216
+ if (!EXECUTION_MODES.has(options.executionMode)) {
217
+ throw new Error(`--execution-mode must be one of: ${Array.from(EXECUTION_MODES).join(', ')}`);
218
+ }
219
+ options.runtimeEnvironment = normalizeText(options.runtimeEnvironment || 'staging').toLowerCase() || 'staging';
220
+ if (!RUNTIME_ENVIRONMENTS.has(options.runtimeEnvironment)) {
221
+ throw new Error(`--runtime-environment must be one of: ${Array.from(RUNTIME_ENVIRONMENTS).join(', ')}`);
222
+ }
223
+ return options;
224
+ }
225
+
226
+ function printHelpAndExit(code) {
227
+ const lines = [
228
+ 'Usage: node scripts/interactive-dialogue-governance.js (--goal <text> | --goal-file <path>) [options]',
229
+ '',
230
+ 'Options:',
231
+ ' --goal <text> User goal text',
232
+ ' --goal-file <path> File containing user goal text',
233
+ ' --context <path> Optional page context JSON file',
234
+ ` --policy <path> Dialogue governance policy JSON (default: ${DEFAULT_POLICY})`,
235
+ ` --profile <name> Dialogue profile (business-user|system-maintainer, default: ${DEFAULT_PROFILE})`,
236
+ ' --ui-mode <name> user-app|ops-console (default by profile)',
237
+ ' --execution-mode <mode> suggestion|apply (default: suggestion)',
238
+ ' --runtime-environment <name> dev|staging|prod (default: staging)',
239
+ ` --authorization-dialogue-policy <path> Authorization dialogue policy JSON (default: ${DEFAULT_AUTHORIZATION_DIALOGUE_POLICY})`,
240
+ ` --out <path> Governance report JSON output path (default: ${DEFAULT_OUT})`,
241
+ ' --fail-on-deny Exit code 2 when dialogue decision is deny',
242
+ ' --json Print payload JSON',
243
+ ' -h, --help Show this help'
244
+ ];
245
+ console.log(lines.join('\n'));
246
+ process.exit(code);
247
+ }
248
+
249
+ function resolvePath(cwd, value) {
250
+ return path.isAbsolute(value) ? value : path.resolve(cwd, value);
251
+ }
252
+
253
+ function normalizeText(value) {
254
+ return `${value || ''}`.trim().replace(/\s+/g, ' ');
255
+ }
256
+
257
+ async function readGoal(options, cwd) {
258
+ if (options.goal) {
259
+ return normalizeText(options.goal);
260
+ }
261
+ const goalPath = resolvePath(cwd, options.goalFile);
262
+ if (!(await fs.pathExists(goalPath))) {
263
+ throw new Error(`goal file not found: ${goalPath}`);
264
+ }
265
+ const content = await fs.readFile(goalPath, 'utf8');
266
+ const goal = normalizeText(content);
267
+ if (!goal) {
268
+ throw new Error('goal text is empty.');
269
+ }
270
+ return goal;
271
+ }
272
+
273
+ async function readJsonFile(filePath, label) {
274
+ if (!(await fs.pathExists(filePath))) {
275
+ throw new Error(`${label} not found: ${filePath}`);
276
+ }
277
+ const raw = await fs.readFile(filePath, 'utf8');
278
+ try {
279
+ return JSON.parse(raw);
280
+ } catch (error) {
281
+ throw new Error(`invalid JSON in ${label}: ${error.message}`);
282
+ }
283
+ }
284
+
285
+ function normalizeRuleList(value) {
286
+ if (!Array.isArray(value)) {
287
+ return [];
288
+ }
289
+ return value
290
+ .filter(item => item && typeof item === 'object')
291
+ .map(item => ({
292
+ id: normalizeText(item.id || 'rule'),
293
+ pattern: normalizeText(item.pattern),
294
+ reason: normalizeText(item.reason || 'policy rule matched')
295
+ }))
296
+ .filter(item => item.pattern);
297
+ }
298
+
299
+ function normalizeStringList(value) {
300
+ if (!Array.isArray(value)) {
301
+ return [];
302
+ }
303
+ return value
304
+ .map(item => normalizeText(item))
305
+ .filter(Boolean);
306
+ }
307
+
308
+ function normalizeNumber(value, fallback) {
309
+ const numeric = Number(value);
310
+ return Number.isFinite(numeric) ? numeric : fallback;
311
+ }
312
+
313
+ function normalizeOptionalNumber(value) {
314
+ const numeric = Number(value);
315
+ return Number.isFinite(numeric) ? numeric : null;
316
+ }
317
+
318
+ function normalizeProfileConfig(rawProfile) {
319
+ const profile = rawProfile && typeof rawProfile === 'object' ? rawProfile : {};
320
+ const lengthPolicy = profile.length_policy && typeof profile.length_policy === 'object'
321
+ ? profile.length_policy
322
+ : {};
323
+ return {
324
+ mode: normalizeText(profile.mode || ''),
325
+ length_policy: {
326
+ min_chars: normalizeOptionalNumber(lengthPolicy.min_chars),
327
+ max_chars: normalizeOptionalNumber(lengthPolicy.max_chars),
328
+ min_significant_tokens: normalizeOptionalNumber(lengthPolicy.min_significant_tokens)
329
+ },
330
+ deny_patterns: normalizeRuleList(profile.deny_patterns),
331
+ clarify_patterns: normalizeRuleList(profile.clarify_patterns),
332
+ response_rules: normalizeStringList(profile.response_rules),
333
+ clarification_templates: normalizeStringList(profile.clarification_templates)
334
+ };
335
+ }
336
+
337
+ function normalizeProfileMap(rawProfiles, fallbackProfiles = {}) {
338
+ const profiles = rawProfiles && typeof rawProfiles === 'object' ? rawProfiles : {};
339
+ const normalized = {};
340
+ for (const [profileName, profileConfig] of Object.entries(fallbackProfiles)) {
341
+ const key = normalizeText(profileName).toLowerCase();
342
+ if (!key) {
343
+ continue;
344
+ }
345
+ normalized[key] = normalizeProfileConfig(profileConfig);
346
+ }
347
+ for (const [profileName, profileConfig] of Object.entries(profiles)) {
348
+ const key = normalizeText(profileName).toLowerCase();
349
+ if (!key) {
350
+ continue;
351
+ }
352
+ normalized[key] = normalizeProfileConfig(profileConfig);
353
+ }
354
+ return normalized;
355
+ }
356
+
357
+ function uniqueStrings(values) {
358
+ return Array.from(new Set(Array.isArray(values) ? values : []));
359
+ }
360
+
361
+ function normalizeStringTokenList(values, fallback = []) {
362
+ const normalized = uniqueStrings(
363
+ (Array.isArray(values) ? values : fallback)
364
+ .map(item => normalizeText(item).toLowerCase())
365
+ .filter(Boolean)
366
+ );
367
+ return normalized;
368
+ }
369
+
370
+ function normalizePolicy(rawPolicy) {
371
+ const policy = rawPolicy && typeof rawPolicy === 'object' ? rawPolicy : {};
372
+ const lengthPolicy = policy.length_policy && typeof policy.length_policy === 'object'
373
+ ? policy.length_policy
374
+ : {};
375
+ const denyPatterns = Array.isArray(policy.deny_patterns) ? policy.deny_patterns : [];
376
+ const clarifyPatterns = Array.isArray(policy.clarify_patterns) ? policy.clarify_patterns : [];
377
+ const responseRules = Array.isArray(policy.response_rules) ? policy.response_rules : [];
378
+ const clarificationTemplates = Array.isArray(policy.clarification_templates)
379
+ ? policy.clarification_templates
380
+ : [];
381
+ const profiles = normalizeProfileMap(policy.profiles, BUILTIN_POLICY.profiles);
382
+ const defaultProfile = normalizeText(policy.default_profile || BUILTIN_POLICY.default_profile).toLowerCase() || DEFAULT_PROFILE;
383
+ if (!profiles[defaultProfile]) {
384
+ profiles[DEFAULT_PROFILE] = normalizeProfileConfig(BUILTIN_POLICY.profiles[DEFAULT_PROFILE]);
385
+ }
386
+ return {
387
+ version: normalizeText(policy.version || BUILTIN_POLICY.version),
388
+ mode: normalizeText(policy.mode || BUILTIN_POLICY.mode),
389
+ default_profile: profiles[defaultProfile] ? defaultProfile : DEFAULT_PROFILE,
390
+ length_policy: {
391
+ min_chars: normalizeNumber(lengthPolicy.min_chars, BUILTIN_POLICY.length_policy.min_chars),
392
+ max_chars: normalizeNumber(lengthPolicy.max_chars, BUILTIN_POLICY.length_policy.max_chars),
393
+ min_significant_tokens: normalizeNumber(
394
+ lengthPolicy.min_significant_tokens,
395
+ BUILTIN_POLICY.length_policy.min_significant_tokens
396
+ )
397
+ },
398
+ deny_patterns: normalizeRuleList(denyPatterns.length > 0 ? denyPatterns : BUILTIN_POLICY.deny_patterns),
399
+ clarify_patterns: normalizeRuleList(clarifyPatterns.length > 0 ? clarifyPatterns : BUILTIN_POLICY.clarify_patterns),
400
+ response_rules: normalizeStringList(responseRules.length > 0 ? responseRules : BUILTIN_POLICY.response_rules),
401
+ clarification_templates: normalizeStringList(
402
+ clarificationTemplates.length > 0
403
+ ? clarificationTemplates
404
+ : BUILTIN_POLICY.clarification_templates
405
+ ),
406
+ profiles
407
+ };
408
+ }
409
+
410
+ function mergePolicyWithProfile(policy, profile) {
411
+ const base = policy && typeof policy === 'object' ? policy : normalizePolicy(BUILTIN_POLICY);
412
+ const profileConfig = profile && typeof profile === 'object' ? profile : {};
413
+ const profileLength = profileConfig.length_policy && typeof profileConfig.length_policy === 'object'
414
+ ? profileConfig.length_policy
415
+ : {};
416
+ return {
417
+ ...base,
418
+ mode: normalizeText(profileConfig.mode || base.mode),
419
+ length_policy: {
420
+ min_chars: Number.isFinite(profileLength.min_chars) ? profileLength.min_chars : base.length_policy.min_chars,
421
+ max_chars: Number.isFinite(profileLength.max_chars) ? profileLength.max_chars : base.length_policy.max_chars,
422
+ min_significant_tokens: Number.isFinite(profileLength.min_significant_tokens)
423
+ ? profileLength.min_significant_tokens
424
+ : base.length_policy.min_significant_tokens
425
+ },
426
+ deny_patterns: [...base.deny_patterns, ...(Array.isArray(profileConfig.deny_patterns) ? profileConfig.deny_patterns : [])],
427
+ clarify_patterns: [...base.clarify_patterns, ...(Array.isArray(profileConfig.clarify_patterns) ? profileConfig.clarify_patterns : [])],
428
+ response_rules: uniqueStrings([...base.response_rules, ...(Array.isArray(profileConfig.response_rules) ? profileConfig.response_rules : [])]),
429
+ clarification_templates: uniqueStrings([
430
+ ...base.clarification_templates,
431
+ ...(Array.isArray(profileConfig.clarification_templates) ? profileConfig.clarification_templates : [])
432
+ ])
433
+ };
434
+ }
435
+
436
+ function resolvePolicyProfile(policy, requestedProfile) {
437
+ const normalizedPolicy = policy && typeof policy === 'object' ? policy : normalizePolicy(BUILTIN_POLICY);
438
+ const profileMap = normalizedPolicy.profiles && typeof normalizedPolicy.profiles === 'object'
439
+ ? normalizedPolicy.profiles
440
+ : {};
441
+ const selectedProfile = normalizeText(requestedProfile || normalizedPolicy.default_profile || DEFAULT_PROFILE).toLowerCase() || DEFAULT_PROFILE;
442
+ const profileConfig = profileMap[selectedProfile];
443
+ if (!profileConfig) {
444
+ const available = Object.keys(profileMap);
445
+ throw new Error(`dialogue profile not found: ${selectedProfile}${available.length > 0 ? ` (available: ${available.join(', ')})` : ''}`);
446
+ }
447
+ return {
448
+ requested_profile: selectedProfile,
449
+ active_profile: selectedProfile,
450
+ policy: mergePolicyWithProfile(normalizedPolicy, profileConfig)
451
+ };
452
+ }
453
+
454
+ async function loadPolicy(policyPath, cwd) {
455
+ const resolved = resolvePath(cwd, policyPath || DEFAULT_POLICY);
456
+ const exists = await fs.pathExists(resolved);
457
+ if (!exists) {
458
+ return {
459
+ source: 'builtin-default',
460
+ from_file: false,
461
+ policy: normalizePolicy(BUILTIN_POLICY)
462
+ };
463
+ }
464
+ const rawPolicy = await readJsonFile(resolved, 'dialogue policy');
465
+ return {
466
+ source: path.relative(cwd, resolved) || '.',
467
+ from_file: true,
468
+ policy: normalizePolicy(rawPolicy)
469
+ };
470
+ }
471
+
472
+ function normalizePromptTemplateMap(input, fallback) {
473
+ const raw = input && typeof input === 'object' ? input : {};
474
+ const result = {};
475
+ for (const [key, value] of Object.entries(fallback || {})) {
476
+ result[key] = normalizeText(value);
477
+ }
478
+ for (const [key, value] of Object.entries(raw)) {
479
+ const normalizedKey = normalizeText(key);
480
+ const normalizedValue = normalizeText(value);
481
+ if (!normalizedKey || !normalizedValue) {
482
+ continue;
483
+ }
484
+ result[normalizedKey] = normalizedValue;
485
+ }
486
+ return result;
487
+ }
488
+
489
+ function normalizeAuthorizationDialogueProfile(input, fallback) {
490
+ const profile = input && typeof input === 'object' ? input : {};
491
+ const base = fallback && typeof fallback === 'object' ? fallback : {};
492
+ return {
493
+ allow_execution_modes: normalizeStringTokenList(
494
+ profile.allow_execution_modes,
495
+ base.allow_execution_modes || ['suggestion']
496
+ ),
497
+ base_required_steps: normalizeStringTokenList(
498
+ profile.base_required_steps,
499
+ base.base_required_steps || ['scope_confirmation']
500
+ )
501
+ };
502
+ }
503
+
504
+ function normalizeAuthorizationDialogueEnvironment(input, fallback) {
505
+ const env = input && typeof input === 'object' ? input : {};
506
+ const base = fallback && typeof fallback === 'object' ? fallback : {};
507
+ const pickBool = (field, defaultValue = false) => {
508
+ if (env[field] === true) {
509
+ return true;
510
+ }
511
+ if (env[field] === false) {
512
+ return false;
513
+ }
514
+ return defaultValue === true;
515
+ };
516
+ return {
517
+ require_ticket: pickBool('require_ticket', base.require_ticket),
518
+ require_password_for_apply: pickBool('require_password_for_apply', base.require_password_for_apply),
519
+ require_role_policy: pickBool('require_role_policy', base.require_role_policy),
520
+ require_distinct_actor_roles: pickBool('require_distinct_actor_roles', base.require_distinct_actor_roles),
521
+ require_manual_review_ack: pickBool('require_manual_review_ack', base.require_manual_review_ack)
522
+ };
523
+ }
524
+
525
+ function normalizeAuthorizationDialoguePolicy(rawPolicy) {
526
+ const policy = rawPolicy && typeof rawPolicy === 'object' ? rawPolicy : {};
527
+ const fallback = BUILTIN_AUTHORIZATION_DIALOGUE_POLICY;
528
+ const profilesInput = policy.profiles && typeof policy.profiles === 'object' ? policy.profiles : {};
529
+ const environmentsInput = policy.environments && typeof policy.environments === 'object' ? policy.environments : {};
530
+
531
+ const profiles = {};
532
+ for (const [profileName, config] of Object.entries(fallback.profiles)) {
533
+ profiles[profileName] = normalizeAuthorizationDialogueProfile(config, config);
534
+ }
535
+ for (const [profileName, config] of Object.entries(profilesInput)) {
536
+ const key = normalizeText(profileName).toLowerCase();
537
+ if (!key) {
538
+ continue;
539
+ }
540
+ profiles[key] = normalizeAuthorizationDialogueProfile(config, profiles[key] || fallback.profiles[DEFAULT_PROFILE]);
541
+ }
542
+
543
+ const environments = {};
544
+ for (const [envName, config] of Object.entries(fallback.environments)) {
545
+ environments[envName] = normalizeAuthorizationDialogueEnvironment(config, config);
546
+ }
547
+ for (const [envName, config] of Object.entries(environmentsInput)) {
548
+ const key = normalizeText(envName).toLowerCase();
549
+ if (!key) {
550
+ continue;
551
+ }
552
+ environments[key] = normalizeAuthorizationDialogueEnvironment(config, environments[key] || fallback.environments.staging);
553
+ }
554
+
555
+ const defaultProfile = normalizeText(policy.default_profile || fallback.default_profile).toLowerCase() || DEFAULT_PROFILE;
556
+
557
+ return {
558
+ version: normalizeText(policy.version || fallback.version),
559
+ default_profile: profiles[defaultProfile] ? defaultProfile : DEFAULT_PROFILE,
560
+ prompt_templates: normalizePromptTemplateMap(policy.prompt_templates, fallback.prompt_templates),
561
+ profiles,
562
+ environments
563
+ };
564
+ }
565
+
566
+ async function loadAuthorizationDialoguePolicy(policyPath, cwd) {
567
+ const resolved = resolvePath(cwd, policyPath || DEFAULT_AUTHORIZATION_DIALOGUE_POLICY);
568
+ const exists = await fs.pathExists(resolved);
569
+ if (!exists) {
570
+ return {
571
+ source: 'builtin-default',
572
+ from_file: false,
573
+ policy: normalizeAuthorizationDialoguePolicy(BUILTIN_AUTHORIZATION_DIALOGUE_POLICY)
574
+ };
575
+ }
576
+ const rawPolicy = await readJsonFile(resolved, 'authorization dialogue policy');
577
+ return {
578
+ source: path.relative(cwd, resolved) || '.',
579
+ from_file: true,
580
+ policy: normalizeAuthorizationDialoguePolicy(rawPolicy)
581
+ };
582
+ }
583
+
584
+ function evaluateAuthorizationDialogue(options, policyRuntime) {
585
+ const policy = policyRuntime && policyRuntime.policy && typeof policyRuntime.policy === 'object'
586
+ ? policyRuntime.policy
587
+ : normalizeAuthorizationDialoguePolicy(BUILTIN_AUTHORIZATION_DIALOGUE_POLICY);
588
+ const activeProfile = options.profile || policy.default_profile || DEFAULT_PROFILE;
589
+ const profile = policy.profiles[activeProfile] || policy.profiles[policy.default_profile] || policy.profiles[DEFAULT_PROFILE];
590
+ const environment = policy.environments[options.runtimeEnvironment] || policy.environments.staging || {};
591
+
592
+ const requiredSteps = Array.isArray(profile.base_required_steps) ? [...profile.base_required_steps] : [];
593
+ const requiredInputs = [];
594
+ const reasons = [];
595
+ let decision = 'allow';
596
+
597
+ const allowedModes = Array.isArray(profile.allow_execution_modes)
598
+ ? profile.allow_execution_modes
599
+ : ['suggestion'];
600
+
601
+ if (!allowedModes.includes(options.executionMode)) {
602
+ decision = 'deny';
603
+ reasons.push(`profile "${activeProfile}" does not allow execution mode "${options.executionMode}"`);
604
+ }
605
+ if (options.uiMode === 'user-app' && options.executionMode === 'apply') {
606
+ decision = 'deny';
607
+ reasons.push('ui mode "user-app" is suggestion-only for embedded business-user surfaces');
608
+ }
609
+
610
+ if (options.executionMode === 'apply') {
611
+ requiredSteps.push('impact_confirmation', 'rollback_confirmation');
612
+ if (environment.require_ticket) {
613
+ requiredSteps.push('ticket_reference');
614
+ requiredInputs.push('change_ticket_id');
615
+ }
616
+ if (environment.require_password_for_apply) {
617
+ requiredSteps.push('password_step_up');
618
+ requiredInputs.push('one_time_password');
619
+ }
620
+ if (environment.require_role_policy) {
621
+ requiredSteps.push('role_policy');
622
+ requiredInputs.push('actor_role', 'approver_role');
623
+ }
624
+ if (environment.require_distinct_actor_roles) {
625
+ requiredSteps.push('role_separation');
626
+ requiredInputs.push('operator_role', 'approver_role');
627
+ }
628
+ if (environment.require_manual_review_ack) {
629
+ requiredSteps.push('manual_review_ack');
630
+ if (decision !== 'deny') {
631
+ decision = 'review-required';
632
+ }
633
+ reasons.push(`environment "${options.runtimeEnvironment}" requires manual review acknowledgement before apply`);
634
+ }
635
+ }
636
+
637
+ const uniqueSteps = normalizeStringTokenList(requiredSteps, []);
638
+ const uniqueInputs = normalizeStringTokenList(requiredInputs, []);
639
+ const prompts = uniqueSteps
640
+ .map(step => policy.prompt_templates[step])
641
+ .filter(Boolean);
642
+
643
+ return {
644
+ decision,
645
+ execute_permitted: options.executionMode === 'apply' && decision === 'allow',
646
+ required_confirmation_steps: uniqueSteps,
647
+ required_inputs: uniqueInputs,
648
+ ui_prompts: prompts,
649
+ reasons: uniqueStrings(reasons),
650
+ context: {
651
+ ui_mode: options.uiMode,
652
+ execution_mode: options.executionMode,
653
+ runtime_environment: options.runtimeEnvironment,
654
+ dialogue_profile: activeProfile
655
+ },
656
+ policy: {
657
+ source: policyRuntime.source,
658
+ from_file: policyRuntime.from_file,
659
+ version: policy.version
660
+ }
661
+ };
662
+ }
663
+
664
+ function safeRegExp(value) {
665
+ try {
666
+ return new RegExp(value, 'i');
667
+ } catch (_error) {
668
+ return null;
669
+ }
670
+ }
671
+
672
+ function evaluatePatternRules(goal, rules) {
673
+ const hits = [];
674
+ for (const rule of rules) {
675
+ const regex = safeRegExp(rule.pattern);
676
+ if (!regex) {
677
+ continue;
678
+ }
679
+ if (regex.test(goal)) {
680
+ hits.push({
681
+ id: rule.id || 'rule',
682
+ reason: rule.reason || 'policy rule matched'
683
+ });
684
+ }
685
+ }
686
+ return hits;
687
+ }
688
+
689
+ function pickClarificationQuestions(policy, context = {}) {
690
+ const questions = [];
691
+ const templates = Array.isArray(policy.clarification_templates) ? policy.clarification_templates : [];
692
+ if (!context.module && !context.page && !context.entity) {
693
+ questions.push('Which module/page/entity is affected first?');
694
+ } else {
695
+ if (!context.module) {
696
+ questions.push('Which module should be changed first?');
697
+ }
698
+ if (!context.page) {
699
+ questions.push('Which page or screen is currently problematic?');
700
+ }
701
+ if (!context.entity) {
702
+ questions.push('Which entity or business rule is affected?');
703
+ }
704
+ }
705
+ for (const template of templates) {
706
+ if (questions.length >= 2) {
707
+ break;
708
+ }
709
+ if (!questions.includes(template)) {
710
+ questions.push(template);
711
+ }
712
+ }
713
+ return questions.slice(0, 2);
714
+ }
715
+
716
+ function contextHasBusinessScope(context = {}) {
717
+ const payload = context && typeof context === 'object' ? context : {};
718
+ return ['module', 'page', 'entity', 'scene_id', 'workflow_node']
719
+ .some((key) => normalizeText(payload[key]));
720
+ }
721
+
722
+ function goalMentionsBusinessScope(goal = '') {
723
+ const normalizedGoal = normalizeText(goal).toLowerCase();
724
+ if (!normalizedGoal) {
725
+ return false;
726
+ }
727
+ return /\b(page|screen|module|entity|workflow|scene)\b/.test(normalizedGoal);
728
+ }
729
+
730
+ function evaluateDialogue(goal, context = {}, policy) {
731
+ const normalizedGoal = normalizeText(goal);
732
+ const tokens = normalizedGoal.split(/\s+/).filter(Boolean);
733
+ const denyHits = evaluatePatternRules(normalizedGoal, policy.deny_patterns);
734
+ const clarifyHits = evaluatePatternRules(normalizedGoal, policy.clarify_patterns);
735
+ const missingBusinessScope = !contextHasBusinessScope(context) && !goalMentionsBusinessScope(normalizedGoal);
736
+
737
+ const reasons = [];
738
+ if (normalizedGoal.length < policy.length_policy.min_chars) {
739
+ reasons.push(`goal is too short (< ${policy.length_policy.min_chars} chars)`);
740
+ }
741
+ if (normalizedGoal.length > policy.length_policy.max_chars) {
742
+ reasons.push(`goal is too long (> ${policy.length_policy.max_chars} chars)`);
743
+ }
744
+ if (tokens.length < policy.length_policy.min_significant_tokens) {
745
+ reasons.push(`goal has too few significant tokens (< ${policy.length_policy.min_significant_tokens})`);
746
+ }
747
+ if (missingBusinessScope) {
748
+ reasons.push('business scene/module/page/entity context is missing; clarify scope before fallback or execution');
749
+ }
750
+ reasons.push(...denyHits.map(item => item.reason));
751
+ reasons.push(...clarifyHits.map(item => item.reason));
752
+
753
+ let decision = 'allow';
754
+ if (denyHits.length > 0) {
755
+ decision = 'deny';
756
+ } else if (
757
+ missingBusinessScope ||
758
+ clarifyHits.length > 0 ||
759
+ normalizedGoal.length < policy.length_policy.min_chars ||
760
+ tokens.length < policy.length_policy.min_significant_tokens
761
+ ) {
762
+ decision = 'clarify';
763
+ }
764
+
765
+ return {
766
+ decision,
767
+ reasons: Array.from(new Set(reasons)),
768
+ business_scope_clarification_required: missingBusinessScope,
769
+ deny_hits: denyHits,
770
+ clarify_hits: clarifyHits,
771
+ response_rules: Array.isArray(policy.response_rules) ? policy.response_rules : [],
772
+ clarification_questions: decision === 'clarify' ? pickClarificationQuestions(policy, context) : []
773
+ };
774
+ }
775
+
776
+ function toContextRef(context) {
777
+ const payload = context && typeof context === 'object' ? context : {};
778
+ return {
779
+ product: normalizeText(payload.product || payload.app || ''),
780
+ module: normalizeText(payload.module || ''),
781
+ page: normalizeText(payload.page || ''),
782
+ entity: normalizeText(payload.entity || ''),
783
+ scene_id: normalizeText(payload.scene_id || '')
784
+ };
785
+ }
786
+
787
+ async function main() {
788
+ const options = parseArgs(process.argv.slice(2));
789
+ const cwd = process.cwd();
790
+ const goal = await readGoal(options, cwd);
791
+ const context = options.context
792
+ ? await readJsonFile(resolvePath(cwd, options.context), 'context')
793
+ : {};
794
+ const policyRuntime = await loadPolicy(options.policy, cwd);
795
+ const authorizationDialoguePolicyRuntime = await loadAuthorizationDialoguePolicy(
796
+ options.authorizationDialoguePolicy,
797
+ cwd
798
+ );
799
+ const profileRuntime = resolvePolicyProfile(policyRuntime.policy, options.profile);
800
+ const evaluation = evaluateDialogue(goal, context, profileRuntime.policy);
801
+ const authorizationDialogue = evaluateAuthorizationDialogue(options, authorizationDialoguePolicyRuntime);
802
+ const outPath = resolvePath(cwd, options.out || DEFAULT_OUT);
803
+
804
+ const payload = {
805
+ mode: 'interactive-dialogue-governance',
806
+ generated_at: new Date().toISOString(),
807
+ policy: {
808
+ source: policyRuntime.source,
809
+ from_file: policyRuntime.from_file,
810
+ version: policyRuntime.policy.version,
811
+ mode: profileRuntime.policy.mode,
812
+ default_profile: policyRuntime.policy.default_profile || DEFAULT_PROFILE,
813
+ requested_profile: profileRuntime.requested_profile,
814
+ active_profile: profileRuntime.active_profile,
815
+ ui_mode: options.uiMode
816
+ },
817
+ input: {
818
+ goal,
819
+ ui_mode: options.uiMode,
820
+ execution_mode: options.executionMode,
821
+ runtime_environment: options.runtimeEnvironment,
822
+ context: options.context ? (path.relative(cwd, resolvePath(cwd, options.context)) || '.') : null,
823
+ context_ref: toContextRef(context)
824
+ },
825
+ authorization_dialogue: authorizationDialogue,
826
+ ...evaluation,
827
+ output: {
828
+ report: path.relative(cwd, outPath) || '.'
829
+ }
830
+ };
831
+
832
+ await fs.ensureDir(path.dirname(outPath));
833
+ await fs.writeJson(outPath, payload, { spaces: 2 });
834
+
835
+ if (options.json) {
836
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
837
+ } else {
838
+ process.stdout.write(`Interactive dialogue governance: ${payload.decision}\n`);
839
+ process.stdout.write(`- Report: ${payload.output.report}\n`);
840
+ }
841
+
842
+ if (options.failOnDeny && payload.decision === 'deny') {
843
+ process.exitCode = 2;
844
+ }
845
+ }
846
+
847
+ if (require.main === module) {
848
+ main().catch((error) => {
849
+ console.error(`Interactive dialogue governance failed: ${error.message}`);
850
+ process.exit(1);
851
+ });
852
+ }
853
+
854
+ module.exports = {
855
+ DEFAULT_POLICY,
856
+ DEFAULT_AUTHORIZATION_DIALOGUE_POLICY,
857
+ DEFAULT_OUT,
858
+ BUILTIN_POLICY,
859
+ BUILTIN_AUTHORIZATION_DIALOGUE_POLICY,
860
+ parseArgs,
861
+ resolvePath,
862
+ normalizePolicy,
863
+ normalizeAuthorizationDialoguePolicy,
864
+ normalizeProfileConfig,
865
+ normalizeProfileMap,
866
+ mergePolicyWithProfile,
867
+ resolvePolicyProfile,
868
+ loadPolicy,
869
+ loadAuthorizationDialoguePolicy,
870
+ evaluateAuthorizationDialogue,
871
+ evaluateDialogue,
872
+ main
873
+ };