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