@unson/brainbase-mcp 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/contracts/foundation/graph-history.sql +634 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/contracts/judgment-value-proof/schema.json +43 -1
- package/dist/canonical-task-service.js +16 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-objectives.d.ts +11 -0
- package/dist/company-os-objectives.js +84 -47
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -0
- package/dist/foundation-graph-http.d.ts +43 -0
- package/dist/foundation-graph-http.js +243 -0
- package/dist/foundation-graph-write.d.ts +90 -0
- package/dist/foundation-graph-write.js +447 -0
- package/dist/foundation-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -0
- package/dist/graph-corrections.d.ts +157 -0
- package/dist/graph-corrections.js +620 -0
- package/dist/graph-foundation-reader.d.ts +60 -0
- package/dist/graph-foundation-reader.js +494 -0
- package/dist/graph-retrieval.d.ts +6 -1
- package/dist/graph-revision-reader.d.ts +80 -0
- package/dist/graph-revision-reader.js +246 -0
- package/dist/graph-web-http.d.ts +26 -0
- package/dist/graph-web-http.js +215 -0
- package/dist/graph-web.d.ts +364 -0
- package/dist/graph-web.js +639 -0
- package/dist/judgment-problem-snapshot.d.ts +30 -2
- package/dist/judgment-problem-snapshot.js +302 -6
- package/dist/judgment-value-proof-review.d.ts +166 -0
- package/dist/judgment-value-proof-review.js +437 -0
- package/dist/judgment-value-proof.d.ts +28 -1
- package/dist/judgment-value-proof.js +53 -8
- package/dist/knowledge-continuation.d.ts +127 -0
- package/dist/knowledge-continuation.js +446 -0
- package/dist/knowledge-lookup.d.ts +178 -0
- package/dist/knowledge-lookup.js +758 -0
- package/dist/local-web-fetch-bridge.d.ts +5 -0
- package/dist/local-web-fetch-bridge.js +37 -0
- package/dist/local-web-host.d.ts +109 -0
- package/dist/local-web-host.js +688 -0
- package/dist/local-web-security.d.ts +25 -0
- package/dist/local-web-security.js +95 -0
- package/dist/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +12 -0
- package/dist/personal-knowledge.js +29 -0
- package/dist/personal-memory-handover.d.ts +108 -0
- package/dist/personal-memory-handover.js +369 -0
- package/dist/philosophy-revision-reader.d.ts +85 -0
- package/dist/philosophy-revision-reader.js +241 -0
- package/dist/portable-graph.d.ts +24 -0
- package/dist/portable-graph.js +77 -0
- package/dist/server.d.ts +7 -2
- package/dist/server.js +9 -6
- package/dist/value-proof-review-http.d.ts +25 -0
- package/dist/value-proof-review-http.js +185 -0
- package/dist/world-model.d.ts +21 -3
- package/dist/world-model.js +94 -12
- package/package.json +94 -6
- package/ui/brainbase-tokens.css +56 -0
- package/ui/graph-own-share.js +367 -0
- package/ui/graph-projects-view.css +23 -0
- package/ui/graph-projects-view.js +730 -0
- package/ui/graph-registry-view.css +101 -0
- package/ui/graph-registry-view.js +804 -0
- package/ui/graph-view-shared.css +522 -0
- package/ui/graph-view-shared.js +1227 -0
- package/ui/judgment-view.css +119 -45
- package/ui/local-web-shell.css +396 -0
- package/ui/local-web-shell.js +378 -0
- package/ui/objective-editor-http-port.js +154 -0
- package/ui/objective-editor.css +180 -74
- package/ui/objective-editor.js +490 -43
- package/ui/outcome-knowledge.css +50 -14
- package/ui/outcome-knowledge.js +15 -1
- package/ui/value-proof-review.css +504 -0
- package/ui/value-proof-review.js +1104 -0
- package/ui/workspace-kit.css +485 -0
- package/ui/workspace-kit.js +229 -0
- package/ui/world-model-view.css +188 -0
- package/ui/world-model-view.js +486 -0
- package/ui/icons/mana/LICENSE.tabler.txt +0 -21
- package/ui/icons/mana/affiliate.svg +0 -20
- package/ui/icons/mana/brand-github.svg +0 -15
- package/ui/icons/mana/brand-google-drive.svg +0 -17
- package/ui/icons/mana/brand-slack.svg +0 -18
- package/ui/icons/mana/cpu.svg +0 -24
- package/ui/outcome-mana.css +0 -255
- package/ui/outcome-mana.js +0 -2325
package/ui/outcome-mana.js
DELETED
|
@@ -1,2325 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mana outcome-delegation UI.
|
|
3
|
-
*
|
|
4
|
-
* This module deliberately has no knowledge of fetch, tenant tokens, or the
|
|
5
|
-
* Mana runtime URL. The host injects the same-origin `api` and `apiMutation`
|
|
6
|
-
* functions used by the organization shell. The BFF remains responsible for
|
|
7
|
-
* deriving tenant, actor, and project access from the authenticated session.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const OUTCOME_DELEGATION_CONTRACT_VERSION = 'mana.outcome-delegation.v1';
|
|
11
|
-
export const MANA_STORY_IDS = Object.freeze([
|
|
12
|
-
'story-brainbase-outcome-mana-contract',
|
|
13
|
-
'story-brainbase-outcome-mana-authority',
|
|
14
|
-
'story-brainbase-outcome-mana-triggers',
|
|
15
|
-
'story-brainbase-outcome-mana-deliver-outcome',
|
|
16
|
-
'story-brainbase-outcome-mana-safe-test',
|
|
17
|
-
'story-brainbase-outcome-mana-run-control',
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
const UNKNOWN = '未確認';
|
|
21
|
-
const EMPTY = 'empty';
|
|
22
|
-
const STOPPED_RUN_RESTART_UNAVAILABLE = '停止した実行は再開できません。';
|
|
23
|
-
|
|
24
|
-
const STATUS_LABELS = Object.freeze({
|
|
25
|
-
loading: '読み込み中',
|
|
26
|
-
idle: '待機中',
|
|
27
|
-
ready: '取得済み',
|
|
28
|
-
draft: '下書き',
|
|
29
|
-
active: '有効',
|
|
30
|
-
stopped: '停止',
|
|
31
|
-
misconfigured: '設定不備',
|
|
32
|
-
retired: '廃止',
|
|
33
|
-
queued: '待機中',
|
|
34
|
-
pending: '待機中',
|
|
35
|
-
running: '実行中',
|
|
36
|
-
waiting_approval: '承認待ち',
|
|
37
|
-
failed: '失敗',
|
|
38
|
-
completion_unverified: '完了未確認',
|
|
39
|
-
configuration_snapshot_missing: '設定記録不足',
|
|
40
|
-
completed_verified: '成果検証済み',
|
|
41
|
-
completed: '完了',
|
|
42
|
-
not_executed: '未実行',
|
|
43
|
-
unexecuted: '未実行',
|
|
44
|
-
executed: '実行済み・完了未確認',
|
|
45
|
-
verified: '検証済み',
|
|
46
|
-
saved_unverified: '保存済み・確認待ち',
|
|
47
|
-
stale: '再試験が必要',
|
|
48
|
-
permission_denied: '権限不足',
|
|
49
|
-
error_retryable: '再試行可能なエラー',
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const AUTHORITY_LABELS = Object.freeze({
|
|
53
|
-
auto: '自動許可',
|
|
54
|
-
approval: '承認が必要',
|
|
55
|
-
deny: '禁止',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const TRIGGER_LABELS = Object.freeze({
|
|
59
|
-
manual: '手動',
|
|
60
|
-
schedule: '定期実行',
|
|
61
|
-
document: '文書追加',
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const RUN_STATUSES = new Set([
|
|
65
|
-
'queued', 'running', 'waiting_approval', 'failed', 'stopped',
|
|
66
|
-
'completion_unverified', 'completed_verified',
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
const STAGE_LABELS = Object.freeze({
|
|
70
|
-
preflight: '事前確認',
|
|
71
|
-
knowledge_retrieval: '知識取得',
|
|
72
|
-
input_retrieval: '入力取得',
|
|
73
|
-
generate: '成果生成',
|
|
74
|
-
effect_execution: '外部操作',
|
|
75
|
-
artifact_save: '成果物保存',
|
|
76
|
-
artifact_readback: '保存結果確認',
|
|
77
|
-
completion_verification: '完了条件確認',
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// The runtime still exposes the same contract, authority, trigger, test, and
|
|
81
|
-
// run resources. The UI presents those resources as one operational workflow
|
|
82
|
-
// so people can see the current step without expanding five long cards at once.
|
|
83
|
-
const WORKFLOW_STAGES = Object.freeze([
|
|
84
|
-
{ id: 'contract', label: '成果契約', description: '成果・根拠・保存先・完了条件' },
|
|
85
|
-
{ id: 'authority', label: '権限', description: '操作・対象資源・主体ごとの許可' },
|
|
86
|
-
{ id: 'triggers', label: '起動条件', description: '起動方式と明示的な有効化' },
|
|
87
|
-
{ id: 'safe_test', label: '安全試験', description: '隔離実行と完了条件の検証' },
|
|
88
|
-
{ id: 'runs', label: '実行履歴', description: '起動理由・段階・成果物・再開地点' },
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
const READBACK_STATES = new Set(['verified', 'active', 'completed_verified', 'readback_verified']);
|
|
92
|
-
|
|
93
|
-
// These are the operational surfaces of the Mana workspace. Codex and
|
|
94
|
-
// Claude Code are intentionally not listed here: their own applications are
|
|
95
|
-
// the source of truth for their plug-in connections. Mana needs the shared
|
|
96
|
-
// inventory because its runtime authority is only visible from this module.
|
|
97
|
-
const MANA_VIEWS = Object.freeze([
|
|
98
|
-
{ id: 'overview', label: '委任一覧', description: '委任ごとの状態と概要' },
|
|
99
|
-
{ id: 'connections', label: '接続状態', description: '組織全体の接続と許可リソース' },
|
|
100
|
-
{ id: 'runs', label: '実行履歴', description: '実行工程とreadback' },
|
|
101
|
-
{ id: 'settings', label: 'Mana設定', description: 'runtime既定値と監査' },
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
const CONNECTOR_DEFINITIONS = Object.freeze([
|
|
105
|
-
{ id: 'github', label: 'GitHub', description: 'コード・ドキュメントの参照', icon: '/icons/mana/brand-github.svg' },
|
|
106
|
-
{ id: 'drive', label: 'Google Drive', description: '資料・成果物の保存', icon: '/icons/mana/brand-google-drive.svg' },
|
|
107
|
-
{ id: 'slack', label: 'Slack', description: '通知・承認・文書イベント', icon: '/icons/mana/brand-slack.svg' },
|
|
108
|
-
{ id: 'brainbase', label: 'Brainbase Graph', description: '知識・判断の参照', icon: '/icons/mana/affiliate.svg' },
|
|
109
|
-
{ id: 'mana', label: 'Mana runtime', description: '委任の実行基盤', icon: '/icons/mana/cpu.svg' },
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
const CONNECTOR_FOUNDATION_KINDS = Object.freeze({
|
|
113
|
-
github: 'github',
|
|
114
|
-
drive: 'drive',
|
|
115
|
-
slack: 'slack',
|
|
116
|
-
brainbase: 'knowledge',
|
|
117
|
-
mana: 'mana',
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
function hasOwn(value, key) {
|
|
121
|
-
return Boolean(value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function objectValue(value) {
|
|
125
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function resolveInjected(value) {
|
|
129
|
-
return typeof value === 'function' ? value() : value;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function firstPresent(...values) {
|
|
133
|
-
return values.find((value) => value !== null && value !== undefined && String(value).trim() !== '');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function present(value, fallback = UNKNOWN) {
|
|
137
|
-
if (Array.isArray(value)) {
|
|
138
|
-
const items = value.filter((item) => item !== null && item !== undefined && String(item).trim() !== '');
|
|
139
|
-
return items.length ? items.join(', ') : fallback;
|
|
140
|
-
}
|
|
141
|
-
return value === null || value === undefined || String(value).trim() === '' ? fallback : String(value);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function stringValue(value, fallback = '') {
|
|
145
|
-
return value === null || value === undefined ? fallback : String(value);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function integerValue(value, fallback = null) {
|
|
149
|
-
return Number.isInteger(value) ? value : Number.isInteger(Number(value)) ? Number(value) : fallback;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function projectValue(input) {
|
|
153
|
-
const project = objectValue(resolveInjected(input));
|
|
154
|
-
return {
|
|
155
|
-
// Keep the canonical project payload intact. The UI only reads a small
|
|
156
|
-
// subset today, but foundation values/resources are needed to render the
|
|
157
|
-
// connection inventory without guessing a connection is healthy.
|
|
158
|
-
...project,
|
|
159
|
-
code: firstPresent(project.code, project.project_code, project.projectCode) ?? null,
|
|
160
|
-
name: firstPresent(project.name, project.display_name, project.displayName) ?? null,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function sessionValue(input) {
|
|
165
|
-
const session = objectValue(resolveInjected(input));
|
|
166
|
-
const permissions = objectValue(session.permissions);
|
|
167
|
-
return {
|
|
168
|
-
role: firstPresent(session.role, session.project_role, permissions.role) ?? null,
|
|
169
|
-
actorId: firstPresent(session.actor_id, session.actorId, session.person_id, session.personId, session.slack_user_id) ?? null,
|
|
170
|
-
displayName: firstPresent(session.display_name, session.displayName, session.name) ?? null,
|
|
171
|
-
outcomePermission: firstPresent(session.outcome_permission, permissions.outcomes, permissions.outcome_delegation) ?? null,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function normalizeStatus(value) {
|
|
176
|
-
const status = stringValue(value).trim().toLowerCase().replace(/[ -]+/g, '_');
|
|
177
|
-
return status || null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export function normalizeConnector(value, fallbackId = '') {
|
|
181
|
-
const raw = value;
|
|
182
|
-
const source = objectValue(value);
|
|
183
|
-
const resources = Array.isArray(source.resources)
|
|
184
|
-
? source.resources
|
|
185
|
-
: Array.isArray(source.allowedResources)
|
|
186
|
-
? source.allowedResources
|
|
187
|
-
: Array.isArray(source.allowed_resources) ? source.allowed_resources : [];
|
|
188
|
-
return {
|
|
189
|
-
raw,
|
|
190
|
-
id: stringValue(firstPresent(source.id, source.connectorId, source.connector_id, fallbackId), fallbackId),
|
|
191
|
-
label: stringValue(firstPresent(source.label, source.name, source.provider, fallbackId), fallbackId),
|
|
192
|
-
description: stringValue(source.description, ''),
|
|
193
|
-
// A registered resource may have no status. Keep that distinction so
|
|
194
|
-
// the screen can show 未確認 instead of inferring a live connection.
|
|
195
|
-
status: normalizeStatus(firstPresent(source.status, source.state, source.connectionState, source.connection_state)),
|
|
196
|
-
account: firstPresent(source.account, source.accountId, source.account_id, source.identifier, source.url) ?? null,
|
|
197
|
-
resourceCount: integerValue(firstPresent(source.resourceCount, source.resource_count), resources.length || null),
|
|
198
|
-
resources,
|
|
199
|
-
checkedAt: firstPresent(source.checkedAt, source.checked_at, source.updatedAt, source.updated_at) ?? null,
|
|
200
|
-
reason: firstPresent(source.reason, source.error, source.connectionReason, source.connection_reason) ?? null,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function normalizeApprovalTarget(value, fallbackContractVersion = null) {
|
|
205
|
-
const source = objectValue(value);
|
|
206
|
-
const effect = objectValue(source.effect);
|
|
207
|
-
const payload = effect.payload;
|
|
208
|
-
const payloadObject = objectValue(payload);
|
|
209
|
-
const operation = firstPresent(source.operation, effect.operation);
|
|
210
|
-
const resource = firstPresent(source.resource, effect.resource);
|
|
211
|
-
const destination = firstPresent(
|
|
212
|
-
source.destination, source.external_destination, source.externalDestination,
|
|
213
|
-
payloadObject.destination, payloadObject.external_destination, payloadObject.externalDestination,
|
|
214
|
-
resource,
|
|
215
|
-
);
|
|
216
|
-
const diff = firstPresent(
|
|
217
|
-
source.diff, source.change_summary, source.changeSummary, source.changes,
|
|
218
|
-
payloadObject.diff, payloadObject.change_summary, payloadObject.changeSummary, payloadObject.changes,
|
|
219
|
-
payload,
|
|
220
|
-
);
|
|
221
|
-
const target = {
|
|
222
|
-
approvalId: stringValue(firstPresent(source.approvalId, source.approval_id), ''),
|
|
223
|
-
operation: stringValue(operation, ''),
|
|
224
|
-
resource: stringValue(resource, ''),
|
|
225
|
-
payloadHash: stringValue(firstPresent(source.payloadHash, source.payload_hash), ''),
|
|
226
|
-
destination: destination ?? null,
|
|
227
|
-
contractVersion: integerValue(firstPresent(source.contractVersion, source.contract_version, fallbackContractVersion), null),
|
|
228
|
-
expiresAt: firstPresent(source.expiresAt, source.expires_at) ?? null,
|
|
229
|
-
diff: diff ?? null,
|
|
230
|
-
};
|
|
231
|
-
return {
|
|
232
|
-
...target,
|
|
233
|
-
complete: Boolean(target.approvalId && target.operation && target.resource && target.payloadHash
|
|
234
|
-
&& target.destination && target.contractVersion !== null && target.expiresAt && target.diff),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function approvalDisplayValue(value) {
|
|
239
|
-
if (value === null || value === undefined || value === '') return UNKNOWN;
|
|
240
|
-
if (typeof value === 'object') {
|
|
241
|
-
try { return JSON.stringify(value); } catch { return UNKNOWN; }
|
|
242
|
-
}
|
|
243
|
-
return String(value);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function statusLabel(value) {
|
|
247
|
-
const status = normalizeStatus(value);
|
|
248
|
-
return STATUS_LABELS[status] ?? (status ? `状態: ${status}` : UNKNOWN);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function authorityLabel(value) {
|
|
252
|
-
return AUTHORITY_LABELS[normalizeStatus(value)] ?? UNKNOWN;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function triggerLabel(value) {
|
|
256
|
-
const source = objectValue(value);
|
|
257
|
-
const normalized = normalizeStatus(typeof value === 'object' ? firstPresent(source.type, source.triggerType, source.trigger_type) : value);
|
|
258
|
-
return TRIGGER_LABELS[normalized] ?? UNKNOWN;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function runReasonLabel(reason, trigger) {
|
|
262
|
-
const normalized = normalizeStatus(reason);
|
|
263
|
-
if (TRIGGER_LABELS[normalized]) return TRIGGER_LABELS[normalized];
|
|
264
|
-
if (typeof reason === 'string' && reason.trim() && !/^[a-z0-9_]+$/i.test(reason.trim())) return reason.trim();
|
|
265
|
-
return triggerLabel(trigger);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function dateLabel(value) {
|
|
269
|
-
if (!value) return UNKNOWN;
|
|
270
|
-
const date = new Date(value);
|
|
271
|
-
if (Number.isNaN(date.getTime())) return UNKNOWN;
|
|
272
|
-
return new Intl.DateTimeFormat('ja-JP', {
|
|
273
|
-
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
|
274
|
-
}).format(date);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function idempotencyKey(prefix) {
|
|
278
|
-
const generated = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
279
|
-
return `${prefix}-${generated}`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function normalizeError(value) {
|
|
283
|
-
const source = value instanceof Error ? value : objectValue(value);
|
|
284
|
-
const nested = objectValue(source.error);
|
|
285
|
-
const status = integerValue(source.status ?? source.statusCode ?? source.response?.status, null);
|
|
286
|
-
const code = stringValue(firstPresent(source.code, nested.code, typeof source.error === 'string' ? source.error : null, status ? `http_${status}` : null), 'unknown_error');
|
|
287
|
-
const message = stringValue(firstPresent(source.message, nested.message), code);
|
|
288
|
-
const lower = `${code} ${message}`.toLowerCase();
|
|
289
|
-
const permission = status === 401 || status === 403 || /permission|forbidden|not_allowed|scope_mismatch|unauthori/.test(lower);
|
|
290
|
-
const conflict = status === 409 || /conflict|version|stale/.test(lower);
|
|
291
|
-
return {
|
|
292
|
-
code,
|
|
293
|
-
message,
|
|
294
|
-
status,
|
|
295
|
-
kind: permission ? 'permission_denied' : conflict ? 'conflict' : 'error_retryable',
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function collectionFrom(payload, keys = ['items', 'records', 'contracts', 'delegations', 'runs']) {
|
|
300
|
-
if (Array.isArray(payload)) return payload;
|
|
301
|
-
const object = objectValue(payload);
|
|
302
|
-
for (const key of keys) if (Array.isArray(object[key])) return object[key];
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function explicitEmpty(payload, keys) {
|
|
307
|
-
if (Array.isArray(payload)) return payload.length === 0;
|
|
308
|
-
const object = objectValue(payload);
|
|
309
|
-
if (object.state === EMPTY || object.state === 'empty') return true;
|
|
310
|
-
const collection = collectionFrom(payload, keys);
|
|
311
|
-
return Array.isArray(collection) && collection.length === 0;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function isReadbackVerified(payload) {
|
|
315
|
-
const object = objectValue(payload);
|
|
316
|
-
const readback = objectValue(object.readback ?? object.verification);
|
|
317
|
-
const contract = objectValue(object.contract ?? object.delegation);
|
|
318
|
-
const run = objectValue(object.run ?? object.testRun);
|
|
319
|
-
return object.verified === true || object.readback_verified === true || readback.verified === true
|
|
320
|
-
|| READBACK_STATES.has(normalizeStatus(object.state)) || READBACK_STATES.has(normalizeStatus(readback.state))
|
|
321
|
-
|| READBACK_STATES.has(normalizeStatus(contract.status)) || READBACK_STATES.has(normalizeStatus(run.status));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function contractMatchesExpected(contract, { id, version = null, fields = null, status = null } = {}) {
|
|
325
|
-
if (!contract?.contractId || contract.contractId !== id) return false;
|
|
326
|
-
if (version !== null && contract.version !== version) return false;
|
|
327
|
-
if (status && contract.status !== status) return false;
|
|
328
|
-
if (!fields) return true;
|
|
329
|
-
const expected = normalizeContract(fields);
|
|
330
|
-
const source = objectValue(fields);
|
|
331
|
-
const comparisons = [
|
|
332
|
-
['project', 'project_code', 'projectCode'], ['outcome'], ['scope'],
|
|
333
|
-
['inputRefs', 'input_refs'], ['knowledgeRefs', 'knowledge_refs'], ['judgmentRefs', 'judgment_refs'],
|
|
334
|
-
['artifactDestination', 'artifact_destination'], ['completionCriteria', 'completion_criteria'],
|
|
335
|
-
['owner'], ['limits'], ['trigger'],
|
|
336
|
-
];
|
|
337
|
-
return comparisons.every((keys) => {
|
|
338
|
-
if (!keys.some((key) => hasOwn(source, key))) return true;
|
|
339
|
-
const normalizedKey = keys[0];
|
|
340
|
-
return JSON.stringify(contract[normalizedKey]) === JSON.stringify(expected[normalizedKey]);
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function normalizeReference(value) {
|
|
345
|
-
const reference = objectValue(value);
|
|
346
|
-
return {
|
|
347
|
-
id: stringValue(firstPresent(reference.id, reference.referenceId, reference.reference_id)),
|
|
348
|
-
version: reference.version === null ? null : stringValue(firstPresent(reference.version, reference.revision), ''),
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function normalizeCriterion(value, index = 0) {
|
|
353
|
-
const criterion = objectValue(value);
|
|
354
|
-
return {
|
|
355
|
-
id: stringValue(firstPresent(criterion.id, criterion.criterionId), `criterion-${index + 1}`),
|
|
356
|
-
description: stringValue(firstPresent(criterion.description, criterion.label), ''),
|
|
357
|
-
kind: stringValue(firstPresent(criterion.kind), 'content_includes'),
|
|
358
|
-
expected: criterion.expected,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export function normalizeTrigger(value) {
|
|
363
|
-
const trigger = objectValue(value);
|
|
364
|
-
const type = normalizeStatus(firstPresent(trigger.type, trigger.trigger_type));
|
|
365
|
-
return {
|
|
366
|
-
type: ['manual', 'schedule', 'document'].includes(type) ? type : null,
|
|
367
|
-
scheduleId: stringValue(firstPresent(trigger.scheduleId, trigger.schedule_id), ''),
|
|
368
|
-
subscriptionId: stringValue(firstPresent(trigger.subscriptionId, trigger.subscription_id), ''),
|
|
369
|
-
provider: stringValue(trigger.provider, ''),
|
|
370
|
-
state: normalizeStatus(firstPresent(trigger.state, trigger.status, trigger.connection_state)) ?? null,
|
|
371
|
-
reason: stringValue(firstPresent(trigger.reason, trigger.error, trigger.connection_reason), ''),
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function normalizeContract(value) {
|
|
376
|
-
const raw = objectValue(value);
|
|
377
|
-
const source = objectValue(raw.contract ?? raw.delegation ?? raw);
|
|
378
|
-
const destination = objectValue(source.artifactDestination ?? source.artifact_destination);
|
|
379
|
-
const owner = objectValue(source.owner);
|
|
380
|
-
const limits = objectValue(source.limits);
|
|
381
|
-
const references = (key, snakeKey) => {
|
|
382
|
-
const refs = source[key] ?? source[snakeKey];
|
|
383
|
-
return Array.isArray(refs) ? refs.map(normalizeReference) : [];
|
|
384
|
-
};
|
|
385
|
-
const criteria = source.completionCriteria ?? source.completion_criteria;
|
|
386
|
-
return {
|
|
387
|
-
raw: value,
|
|
388
|
-
schemaVersion: stringValue(firstPresent(source.schemaVersion, source.schema_version), OUTCOME_DELEGATION_CONTRACT_VERSION),
|
|
389
|
-
contractId: stringValue(firstPresent(source.contractId, source.contract_id, source.id), ''),
|
|
390
|
-
version: integerValue(source.version, null),
|
|
391
|
-
tenantId: null,
|
|
392
|
-
project: stringValue(firstPresent(source.project, source.project_code, source.projectCode), ''),
|
|
393
|
-
status: normalizeStatus(source.status),
|
|
394
|
-
nextAction: stringValue(firstPresent(source.nextAction, source.next_action), ''),
|
|
395
|
-
outcome: stringValue(source.outcome, ''),
|
|
396
|
-
scope: stringValue(source.scope, ''),
|
|
397
|
-
inputRefs: references('inputRefs', 'input_refs'),
|
|
398
|
-
knowledgeRefs: references('knowledgeRefs', 'knowledge_refs'),
|
|
399
|
-
judgmentRefs: references('judgmentRefs', 'judgment_refs'),
|
|
400
|
-
artifactDestination: {
|
|
401
|
-
adapterId: stringValue(firstPresent(destination.adapterId, destination.adapter_id), ''),
|
|
402
|
-
location: stringValue(destination.location, ''),
|
|
403
|
-
environment: ['production', 'isolated'].includes(stringValue(destination.environment)) ? destination.environment : null,
|
|
404
|
-
},
|
|
405
|
-
completionCriteria: Array.isArray(criteria) ? criteria.map(normalizeCriterion) : [],
|
|
406
|
-
owner: { actorId: stringValue(firstPresent(owner.actorId, owner.actor_id), '') },
|
|
407
|
-
trigger: normalizeTrigger(source.trigger),
|
|
408
|
-
limits: {
|
|
409
|
-
maxAttempts: integerValue(limits.maxAttempts ?? limits.max_attempts, null),
|
|
410
|
-
timeoutMs: integerValue(limits.timeoutMs ?? limits.timeout_ms, null),
|
|
411
|
-
budgetUnits: integerValue(limits.budgetUnits ?? limits.budget_units, null),
|
|
412
|
-
},
|
|
413
|
-
createdBy: stringValue(firstPresent(source.createdBy, source.created_by), ''),
|
|
414
|
-
createdAt: source.createdAt ?? source.created_at ?? null,
|
|
415
|
-
updatedAt: source.updatedAt ?? source.updated_at ?? null,
|
|
416
|
-
supersedesVersion: integerValue(source.supersedesVersion ?? source.supersedes_version, null),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export function normalizeContracts(payload) {
|
|
421
|
-
const records = collectionFrom(payload, ['items', 'records', 'contracts', 'delegations']);
|
|
422
|
-
if (!records) return { state: 'unknown', records: [], raw: payload };
|
|
423
|
-
return { state: records.length ? 'ready' : 'empty', records: records.map(normalizeContract), raw: payload };
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export function normalizeAuthority(value) {
|
|
427
|
-
const source = objectValue(value);
|
|
428
|
-
const entries = collectionFrom(source, ['matrix', 'rules', 'items', 'entries', 'authorities']);
|
|
429
|
-
if (!entries) return { state: 'unknown', status: 'unknown', matrix: [], raw: value };
|
|
430
|
-
const status = entries.length ? 'ready' : 'empty';
|
|
431
|
-
return {
|
|
432
|
-
state: status,
|
|
433
|
-
status,
|
|
434
|
-
matrix: entries.map((entry, index) => {
|
|
435
|
-
const item = objectValue(entry);
|
|
436
|
-
return {
|
|
437
|
-
id: stringValue(firstPresent(item.id, item.operationId, item.operation_id), `operation-${index + 1}`),
|
|
438
|
-
operation: stringValue(firstPresent(item.operation, item.action, item.name), UNKNOWN),
|
|
439
|
-
target: stringValue(firstPresent(item.target, item.resource, item.resource_id), UNKNOWN),
|
|
440
|
-
project: stringValue(firstPresent(item.project, item.project_code, item.projectCode), UNKNOWN),
|
|
441
|
-
actor: stringValue(firstPresent(item.actor, item.actor_id, item.actorId), UNKNOWN),
|
|
442
|
-
policy: ['auto', 'approval', 'deny'].includes(normalizeStatus(item.policy ?? item.mode ?? item.effect)) ? normalizeStatus(item.policy ?? item.mode ?? item.effect) : null,
|
|
443
|
-
reason: stringValue(firstPresent(item.reason, item.explanation), ''),
|
|
444
|
-
state: normalizeStatus(item.state),
|
|
445
|
-
};
|
|
446
|
-
}),
|
|
447
|
-
raw: value,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export function normalizeRun(value) {
|
|
452
|
-
const raw = objectValue(value);
|
|
453
|
-
const source = objectValue(raw.run ?? raw);
|
|
454
|
-
const completion = objectValue(source.completion ?? source.completion_result ?? source.completionResult);
|
|
455
|
-
const artifacts = source.artifacts ?? source.artifact_refs ?? source.artifactRefs;
|
|
456
|
-
const evidence = source.evidence ?? source.references ?? [];
|
|
457
|
-
const stages = Array.isArray(source.stages) ? source.stages : [];
|
|
458
|
-
const status = normalizeStatus(source.status ?? source.state);
|
|
459
|
-
const safeTestSource = objectValue(source.safeTest ?? source.safe_test ?? raw.safeTest ?? raw.safe_test);
|
|
460
|
-
const configHash = stringValue(firstPresent(
|
|
461
|
-
source.configHash, source.config_hash,
|
|
462
|
-
safeTestSource.configHash, safeTestSource.config_hash,
|
|
463
|
-
), '');
|
|
464
|
-
const triggerSource = source.trigger;
|
|
465
|
-
const trigger = typeof triggerSource === 'string'
|
|
466
|
-
? triggerSource
|
|
467
|
-
: firstPresent(objectValue(triggerSource).type, objectValue(triggerSource).triggerType, objectValue(triggerSource).trigger_type, source.triggerType, source.trigger_type, source.triggerReason, source.trigger_reason, source.reason) ?? null;
|
|
468
|
-
const artifactReadback = source.artifactReadback ?? source.artifact_readback ?? source.readback;
|
|
469
|
-
const judgment = source.judgment ?? source.judgment_result ?? source.judgmentResult;
|
|
470
|
-
const judgmentReason = firstPresent(
|
|
471
|
-
source.judgmentReason, source.judgment_reason,
|
|
472
|
-
objectValue(judgment).reason, source.reason_detail,
|
|
473
|
-
) ?? null;
|
|
474
|
-
return {
|
|
475
|
-
raw: value,
|
|
476
|
-
id: stringValue(firstPresent(source.runId, source.run_id, source.id), ''),
|
|
477
|
-
contractId: stringValue(firstPresent(source.contractId, source.contract_id), ''),
|
|
478
|
-
contractVersion: integerValue(source.contractVersion ?? source.contract_version, null),
|
|
479
|
-
mode: normalizeStatus(source.mode),
|
|
480
|
-
status: RUN_STATUSES.has(status) ? status : status ?? null,
|
|
481
|
-
safeTest: {
|
|
482
|
-
configSnapshot: safeTestSource.configSnapshot ?? safeTestSource.config_snapshot ?? null,
|
|
483
|
-
configHash: stringValue(firstPresent(safeTestSource.configHash, safeTestSource.config_hash), ''),
|
|
484
|
-
currentConfigHash: stringValue(firstPresent(safeTestSource.currentConfigHash, safeTestSource.current_config_hash), ''),
|
|
485
|
-
retestRequired: safeTestSource.retestRequired === true || safeTestSource.retest_required === true,
|
|
486
|
-
retestReason: stringValue(firstPresent(safeTestSource.retestReason, safeTestSource.retest_reason), ''),
|
|
487
|
-
},
|
|
488
|
-
configHash,
|
|
489
|
-
trigger,
|
|
490
|
-
reason: stringValue(firstPresent(source.triggerReason, source.trigger_reason, source.reason), ''),
|
|
491
|
-
artifactReadback: artifactReadback === undefined ? null : artifactReadback,
|
|
492
|
-
judgment: judgment === undefined ? null : judgment,
|
|
493
|
-
judgmentReason,
|
|
494
|
-
stage: stringValue(firstPresent(source.stage, source.step, source.phase), ''),
|
|
495
|
-
stages: stages.map((value) => {
|
|
496
|
-
const stage = objectValue(value);
|
|
497
|
-
return {
|
|
498
|
-
id: stringValue(firstPresent(stage.id, stage.stageId, stage.stage_id), ''),
|
|
499
|
-
status: normalizeStatus(stage.status ?? stage.state),
|
|
500
|
-
attempt: integerValue(stage.attempt, null),
|
|
501
|
-
startedAt: stage.startedAt ?? stage.started_at ?? null,
|
|
502
|
-
completedAt: stage.completedAt ?? stage.completed_at ?? null,
|
|
503
|
-
errorCode: stringValue(firstPresent(stage.errorCode, stage.error_code), ''),
|
|
504
|
-
receipt: stage.receipt,
|
|
505
|
-
};
|
|
506
|
-
}),
|
|
507
|
-
resumePoint: stringValue(firstPresent(source.resumePoint, source.resume_point, source.restart_from), ''),
|
|
508
|
-
evidence: Array.isArray(evidence) ? evidence : [],
|
|
509
|
-
artifacts: Array.isArray(artifacts) ? artifacts : [],
|
|
510
|
-
completion: {
|
|
511
|
-
state: normalizeStatus(firstPresent(completion.state, completion.status, source.completion_state)),
|
|
512
|
-
criteria: Array.isArray(completion.criteria) ? completion.criteria : [],
|
|
513
|
-
reason: stringValue(firstPresent(completion.reason, completion.error), ''),
|
|
514
|
-
},
|
|
515
|
-
pendingApproval: normalizeApprovalTarget(source.pendingApproval ?? source.pending_approval, integerValue(source.contractVersion ?? source.contract_version, null)),
|
|
516
|
-
startedAt: source.startedAt ?? source.started_at ?? source.createdAt ?? source.created_at ?? null,
|
|
517
|
-
updatedAt: source.updatedAt ?? source.updated_at ?? null,
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function safeTestStatus(value, contract) {
|
|
522
|
-
const run = normalizeRun(value);
|
|
523
|
-
const current = objectValue(contract);
|
|
524
|
-
const resultState = completionState(value);
|
|
525
|
-
if (resultState === 'unexecuted') return 'unexecuted';
|
|
526
|
-
if (resultState === 'failed') return 'failed';
|
|
527
|
-
if (resultState !== 'completed_verified') return 'completion_unverified';
|
|
528
|
-
if (run.mode !== 'safe_test' || !run.contractId || run.contractId !== current.contractId) return 'unknown';
|
|
529
|
-
const { configHash, currentConfigHash, retestRequired, retestReason } = run.safeTest;
|
|
530
|
-
if (retestReason === 'configuration_snapshot_missing') return 'configuration_snapshot_missing';
|
|
531
|
-
if (retestRequired || (configHash && currentConfigHash && configHash !== currentConfigHash)) return 'stale';
|
|
532
|
-
if (!configHash || !currentConfigHash) return 'configuration_snapshot_missing';
|
|
533
|
-
return configHash === currentConfigHash ? 'verified' : 'stale';
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
export function normalizeRuns(payload) {
|
|
537
|
-
const records = collectionFrom(payload, ['runs', 'items', 'records']);
|
|
538
|
-
if (!records) return { state: 'unknown', records: [], raw: payload };
|
|
539
|
-
return { state: records.length ? 'ready' : 'empty', records: records.map(normalizeRun), raw: payload };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
export function completionState(value) {
|
|
543
|
-
const source = objectValue(value);
|
|
544
|
-
const run = objectValue(source.run ?? source.testRun ?? source);
|
|
545
|
-
const state = normalizeStatus(firstPresent(source.completion_state, source.completionState, run.completion_state, run.completionState, run.status, source.status));
|
|
546
|
-
const executed = source.executed === true || source.execution_state === 'executed' || Boolean(firstPresent(source.run_id, source.runId, run.id, run.run_id))
|
|
547
|
-
|| ['queued', 'running', 'waiting_approval', 'failed', 'stopped', 'completion_unverified', 'completed_verified'].includes(state);
|
|
548
|
-
const verified = source.completion_verified === true || source.completionVerified === true
|
|
549
|
-
|| state === 'completed_verified' || objectValue(source.completion).state === 'verified' || objectValue(run.completion).state === 'verified';
|
|
550
|
-
if (!executed) return 'unexecuted';
|
|
551
|
-
if (verified) return 'completed_verified';
|
|
552
|
-
return state === 'failed' ? 'failed' : state === 'stopped' ? 'stopped' : 'completion_unverified';
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function criterionLines(criteria) {
|
|
556
|
-
return (Array.isArray(criteria) ? criteria : []).map((criterion) => {
|
|
557
|
-
const value = normalizeCriterion(criterion);
|
|
558
|
-
const expected = value.expected === undefined ? '' : `\t${String(value.expected)}`;
|
|
559
|
-
return `${value.id}\t${value.description}\t${value.kind}${expected}`;
|
|
560
|
-
}).join('\n');
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function parseReferenceLines(value) {
|
|
564
|
-
return stringValue(value).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
565
|
-
const [id, version = ''] = line.split(/\s*(?:@|\t|\|)\s*/, 2);
|
|
566
|
-
return { id: id.trim(), version: version.trim() || null };
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function parseCriteriaLines(value) {
|
|
571
|
-
const text = stringValue(value).trim();
|
|
572
|
-
if (!text) return [];
|
|
573
|
-
try {
|
|
574
|
-
const parsed = JSON.parse(text);
|
|
575
|
-
if (Array.isArray(parsed)) return parsed.map(normalizeCriterion);
|
|
576
|
-
} catch {
|
|
577
|
-
// The line format keeps the form usable for people who do not want to edit JSON.
|
|
578
|
-
}
|
|
579
|
-
return text.split(/\r?\n/).map((line, index) => {
|
|
580
|
-
const [id, description, kind = 'content_includes', expected = ''] = line.split(/\s*(?:\t|\|)\s*/);
|
|
581
|
-
return normalizeCriterion({ id: id?.trim() || `criterion-${index + 1}`, description: description?.trim() || line.trim(), kind: kind?.trim() || 'content_includes', expected: expected?.trim() || undefined }, index);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function contractDraftFromForm(fields) {
|
|
586
|
-
const criterionText = fields.completionCriteria.value;
|
|
587
|
-
const body = {
|
|
588
|
-
outcome: fields.outcome.value,
|
|
589
|
-
scope: fields.scope.value,
|
|
590
|
-
inputRefs: parseReferenceLines(fields.inputRefs.value),
|
|
591
|
-
knowledgeRefs: parseReferenceLines(fields.knowledgeRefs.value),
|
|
592
|
-
judgmentRefs: parseReferenceLines(fields.judgmentRefs.value),
|
|
593
|
-
artifactDestination: {
|
|
594
|
-
adapterId: fields.adapterId.value,
|
|
595
|
-
location: fields.location.value,
|
|
596
|
-
environment: fields.environment.value,
|
|
597
|
-
},
|
|
598
|
-
completionCriteria: parseCriteriaLines(criterionText),
|
|
599
|
-
owner: { actorId: fields.ownerActorId.value },
|
|
600
|
-
trigger: { type: 'manual' },
|
|
601
|
-
limits: {
|
|
602
|
-
maxAttempts: Number(fields.maxAttempts.value),
|
|
603
|
-
timeoutMs: Number(fields.timeoutMs.value),
|
|
604
|
-
budgetUnits: Number(fields.budgetUnits.value),
|
|
605
|
-
},
|
|
606
|
-
};
|
|
607
|
-
return body;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function createElement(document, tag, options = {}) {
|
|
611
|
-
const element = document.createElement(tag);
|
|
612
|
-
if (options.className) element.className = options.className;
|
|
613
|
-
if (options.text !== undefined) element.textContent = String(options.text);
|
|
614
|
-
for (const [name, value] of Object.entries(options.attrs ?? {})) {
|
|
615
|
-
if (value === undefined || value === null) continue;
|
|
616
|
-
element.setAttribute(name, String(value));
|
|
617
|
-
}
|
|
618
|
-
return element;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function clearElement(element) {
|
|
622
|
-
if (!element) return;
|
|
623
|
-
if (typeof element.replaceChildren === 'function') element.replaceChildren();
|
|
624
|
-
else if (Array.isArray(element.children)) element.children.length = 0;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function listen(element, type, handler) {
|
|
628
|
-
if (element && typeof element.addEventListener === 'function') element.addEventListener(type, handler);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function focusElement(element) {
|
|
632
|
-
if (element && typeof element.focus === 'function') element.focus();
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function button(document, text, handler, options = {}) {
|
|
636
|
-
const item = createElement(document, 'button', {
|
|
637
|
-
className: options.className ?? 'outcome-mana-button',
|
|
638
|
-
text,
|
|
639
|
-
attrs: { type: 'button', ...(options.attrs ?? {}) },
|
|
640
|
-
});
|
|
641
|
-
if (options.disabled) item.disabled = true;
|
|
642
|
-
listen(item, 'click', (event) => { event?.preventDefault?.(); handler?.(item, event); });
|
|
643
|
-
return item;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function group(document, tag, options = {}, children = []) {
|
|
647
|
-
const element = createElement(document, tag, options);
|
|
648
|
-
element.append(...children.filter(Boolean));
|
|
649
|
-
return element;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function connectorIdentity(document, connector, options = {}) {
|
|
653
|
-
const identity = createElement(document, options.tag ?? 'span', {
|
|
654
|
-
className: `outcome-mana-connector-identity outcome-mana-connector-${connector.id}${options.large ? ' is-large' : ''}`,
|
|
655
|
-
attrs: options.attrs,
|
|
656
|
-
});
|
|
657
|
-
identity.append(
|
|
658
|
-
group(document, 'span', { className: 'outcome-mana-connector-icon-frame', attrs: { 'aria-hidden': 'true' } }, [
|
|
659
|
-
createElement(document, 'img', {
|
|
660
|
-
className: 'outcome-mana-connector-icon',
|
|
661
|
-
attrs: { src: connector.icon, alt: '', width: options.large ? '24' : '20', height: options.large ? '24' : '20' },
|
|
662
|
-
}),
|
|
663
|
-
]),
|
|
664
|
-
group(document, 'span', { className: 'outcome-mana-connector-copy' }, [
|
|
665
|
-
createElement(document, options.large ? 'h3' : 'strong', { text: connector.label }),
|
|
666
|
-
options.large ? createElement(document, 'small', { text: connector.description }) : null,
|
|
667
|
-
]),
|
|
668
|
-
);
|
|
669
|
-
return identity;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function statusBadge(document, status, label = statusLabel(status)) {
|
|
673
|
-
const normalized = normalizeStatus(status) ?? 'unknown';
|
|
674
|
-
return createElement(document, 'span', {
|
|
675
|
-
className: `outcome-mana-badge outcome-mana-badge-${normalized}`,
|
|
676
|
-
text: label,
|
|
677
|
-
attrs: { 'data-state': normalized },
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function stateMessage(document, status, options = {}) {
|
|
682
|
-
const state = status ?? 'unknown';
|
|
683
|
-
if (state === 'loading') {
|
|
684
|
-
const item = createElement(document, 'div', { className: 'outcome-mana-state outcome-mana-loading', attrs: { role: 'status', 'aria-live': 'polite' } });
|
|
685
|
-
item.append(createElement(document, 'span', { className: 'outcome-mana-skeleton', attrs: { 'aria-hidden': 'true' } }), createElement(document, 'span', { text: options.loadingText ?? '読み込んでいます…' }));
|
|
686
|
-
return item;
|
|
687
|
-
}
|
|
688
|
-
if (state === 'empty') {
|
|
689
|
-
const item = createElement(document, 'div', { className: 'outcome-mana-state outcome-mana-empty', attrs: { role: 'status' } });
|
|
690
|
-
item.append(createElement(document, 'strong', { text: options.emptyTitle ?? '登録された内容はありません' }), createElement(document, 'p', { text: options.emptyText ?? 'ここから登録できます。' }));
|
|
691
|
-
return item;
|
|
692
|
-
}
|
|
693
|
-
if (state === 'permission_denied') {
|
|
694
|
-
const item = createElement(document, 'div', { className: 'outcome-mana-state outcome-mana-permission', attrs: { role: 'alert' } });
|
|
695
|
-
item.append(createElement(document, 'strong', { text: '権限がありません' }), createElement(document, 'p', { text: options.permissionText ?? 'このプロジェクトの委任設定を確認する権限がありません。現在の権限を確認してください。' }));
|
|
696
|
-
return item;
|
|
697
|
-
}
|
|
698
|
-
if (state === 'error_retryable' || state === 'conflict' || state === 'unknown') {
|
|
699
|
-
const item = createElement(document, 'div', { className: `outcome-mana-state outcome-mana-error outcome-mana-error-${state}`, attrs: { role: 'alert' } });
|
|
700
|
-
item.append(createElement(document, 'strong', { text: state === 'unknown' ? '状態を確認できません' : state === 'conflict' ? '別の更新を確認しました' : '読み込みを確認できません' }));
|
|
701
|
-
if (options.message) item.append(createElement(document, 'p', { text: options.message }));
|
|
702
|
-
if (options.retry) item.append(button(document, '再試行', options.retry, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
703
|
-
return item;
|
|
704
|
-
}
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function definitionList(document, entries) {
|
|
709
|
-
const list = createElement(document, 'dl', { className: 'outcome-mana-definition' });
|
|
710
|
-
for (const [term, value] of entries) {
|
|
711
|
-
list.append(createElement(document, 'dt', { text: term }), createElement(document, 'dd', { text: present(value) }));
|
|
712
|
-
}
|
|
713
|
-
return list;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function field(document, labelText, type, name, value, options = {}) {
|
|
717
|
-
const label = createElement(document, 'label', { className: 'outcome-mana-field' });
|
|
718
|
-
label.append(createElement(document, 'span', { className: 'outcome-mana-label', text: labelText }));
|
|
719
|
-
const input = createElement(document, type === 'textarea' ? 'textarea' : type === 'select' ? 'select' : 'input', {
|
|
720
|
-
className: 'outcome-mana-input',
|
|
721
|
-
attrs: { name, id: options.id ?? `outcome-mana-${name}`, ...(options.attrs ?? {}) },
|
|
722
|
-
});
|
|
723
|
-
if (type === 'select') {
|
|
724
|
-
for (const option of options.options ?? []) input.append(createElement(document, 'option', { text: option.label, attrs: { value: option.value } }));
|
|
725
|
-
}
|
|
726
|
-
if (type === 'textarea') input.textContent = stringValue(value);
|
|
727
|
-
else input.value = stringValue(value);
|
|
728
|
-
if (options.helper) label.append(input, createElement(document, 'small', { className: 'outcome-mana-helper', text: options.helper }));
|
|
729
|
-
else label.append(input);
|
|
730
|
-
return { label, input };
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
function textOf(value) {
|
|
734
|
-
if (value === null || value === undefined) return UNKNOWN;
|
|
735
|
-
if (typeof value === 'string') return value;
|
|
736
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
737
|
-
const id = firstPresent(value.label, value.name, value.title, value.identifier, value.url, value.id);
|
|
738
|
-
if (id) return value.version ? `${id} @ ${value.version}` : id;
|
|
739
|
-
}
|
|
740
|
-
try { return JSON.stringify(value, null, 2); } catch { return UNKNOWN; }
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function evidenceList(document, title, values) {
|
|
744
|
-
const section = createElement(document, 'div', { className: 'outcome-mana-evidence' });
|
|
745
|
-
section.append(createElement(document, 'h4', { text: title }));
|
|
746
|
-
if (!Array.isArray(values) || !values.length) {
|
|
747
|
-
section.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: UNKNOWN }));
|
|
748
|
-
return section;
|
|
749
|
-
}
|
|
750
|
-
const list = createElement(document, 'ul');
|
|
751
|
-
values.forEach((value) => list.append(createElement(document, 'li', { text: textOf(value) })));
|
|
752
|
-
section.append(list);
|
|
753
|
-
return section;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
function stageList(document, stages) {
|
|
757
|
-
const section = createElement(document, 'div', { className: 'outcome-mana-evidence' });
|
|
758
|
-
section.append(createElement(document, 'h4', { text: '処理段階' }));
|
|
759
|
-
if (!Array.isArray(stages) || !stages.length) {
|
|
760
|
-
section.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: UNKNOWN }));
|
|
761
|
-
return section;
|
|
762
|
-
}
|
|
763
|
-
const rows = stages.map((stage) => {
|
|
764
|
-
const details = [statusLabel(stage.status)];
|
|
765
|
-
if (stage.attempt !== null) details.push(`試行 ${stage.attempt}`);
|
|
766
|
-
if (stage.errorCode) details.push(`エラー: ${stage.errorCode}`);
|
|
767
|
-
return [`${STAGE_LABELS[stage.id] ?? stage.id} (${stage.id || UNKNOWN})`, details.join(' / ')];
|
|
768
|
-
});
|
|
769
|
-
section.append(definitionList(document, rows));
|
|
770
|
-
return section;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function sectionHeading(document, title, status, description, id) {
|
|
774
|
-
const head = createElement(document, 'div', { className: 'outcome-mana-section-heading' });
|
|
775
|
-
const copy = createElement(document, 'div');
|
|
776
|
-
copy.append(createElement(document, 'h2', { text: title, attrs: id ? { id } : {} }), createElement(document, 'p', { className: 'outcome-mana-section-description', text: description }));
|
|
777
|
-
head.append(copy);
|
|
778
|
-
if (status) head.append(statusBadge(document, status));
|
|
779
|
-
return head;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function defaultPathMap(basePath, projectCode) {
|
|
783
|
-
const root = String(basePath || '/api/outcome-delegations').replace(/\/$/, '');
|
|
784
|
-
const query = projectCode ? `?project_code=${encodeURIComponent(projectCode)}` : '';
|
|
785
|
-
return {
|
|
786
|
-
settings: `${root}/settings${query}`,
|
|
787
|
-
contractList: `${root}${query}`,
|
|
788
|
-
triggerCatalog: (type) => {
|
|
789
|
-
const search = new URLSearchParams(projectCode ? { project_code: projectCode } : {});
|
|
790
|
-
if (type) search.set('type', type);
|
|
791
|
-
return `${root}/triggers${search.size ? `?${search}` : ''}`;
|
|
792
|
-
},
|
|
793
|
-
contractDetail: (contractId) => `${root}/${encodeURIComponent(contractId)}${query}`,
|
|
794
|
-
contractClone: (contractId) => `${root}/${encodeURIComponent(contractId)}/clone${query}`,
|
|
795
|
-
contractRetire: (contractId) => `${root}/${encodeURIComponent(contractId)}/retire${query}`,
|
|
796
|
-
authority: (contractId, params = {}) => {
|
|
797
|
-
const search = new URLSearchParams(projectCode ? { project_code: projectCode } : {});
|
|
798
|
-
if (Number.isInteger(params.contractVersion)) search.set('contractVersion', String(params.contractVersion));
|
|
799
|
-
return `${root}/${encodeURIComponent(contractId)}/authority${search.size ? `?${search}` : ''}`;
|
|
800
|
-
},
|
|
801
|
-
triggers: (contractId) => `${root}/${encodeURIComponent(contractId)}/triggers${query}`,
|
|
802
|
-
tests: (contractId) => `${root}/${encodeURIComponent(contractId)}/tests${query}`,
|
|
803
|
-
activation: (contractId) => `${root}/${encodeURIComponent(contractId)}/activation${query}`,
|
|
804
|
-
runList: (contractId) => `${root}/${encodeURIComponent(contractId)}/runs${query}`,
|
|
805
|
-
runDetail: (runId) => `${root}/runs/${encodeURIComponent(runId)}${query}`,
|
|
806
|
-
decisions: (runId) => `${root}/runs/${encodeURIComponent(runId)}/decisions${query}`,
|
|
807
|
-
resume: (runId) => `${root}/runs/${encodeURIComponent(runId)}/resume${query}`,
|
|
808
|
-
runStop: (runId) => `${root}/runs/${encodeURIComponent(runId)}/stop${query}`,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
export function createManaPaths(options = {}) {
|
|
813
|
-
return defaultPathMap(options.basePath ?? '/api/outcome-delegations', options.projectCode ?? options.project_code ?? null);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function resolvePath(pathMap, name, params) {
|
|
817
|
-
const value = pathMap[name];
|
|
818
|
-
return typeof value === 'function' ? value(params?.id ?? params?.contractId ?? params?.runId, params) : value;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function canManage(session) {
|
|
822
|
-
const permission = normalizeStatus(session.outcomePermission);
|
|
823
|
-
if (['deny', 'denied', 'false', 'none'].includes(permission) || session.outcomePermission === false) return false;
|
|
824
|
-
return true;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function contractFormValues(contract, draft) {
|
|
828
|
-
const source = draft ?? contract ?? normalizeContract({});
|
|
829
|
-
return {
|
|
830
|
-
outcome: source.outcome ?? '',
|
|
831
|
-
scope: source.scope ?? '',
|
|
832
|
-
inputRefs: (source.inputRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
833
|
-
knowledgeRefs: (source.knowledgeRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
834
|
-
judgmentRefs: (source.judgmentRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
835
|
-
adapterId: source.artifactDestination?.adapterId ?? '',
|
|
836
|
-
location: source.artifactDestination?.location ?? '',
|
|
837
|
-
environment: source.artifactDestination?.environment ?? 'isolated',
|
|
838
|
-
completionCriteria: criterionLines(source.completionCriteria),
|
|
839
|
-
ownerActorId: source.owner?.actorId ?? '',
|
|
840
|
-
maxAttempts: source.limits?.maxAttempts ?? '',
|
|
841
|
-
timeoutMs: source.limits?.timeoutMs ?? '',
|
|
842
|
-
budgetUnits: source.limits?.budgetUnits ?? '',
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function contractInput(contract, overrides = {}) {
|
|
847
|
-
return {
|
|
848
|
-
outcome: contract.outcome,
|
|
849
|
-
scope: contract.scope,
|
|
850
|
-
inputRefs: contract.inputRefs,
|
|
851
|
-
knowledgeRefs: contract.knowledgeRefs,
|
|
852
|
-
judgmentRefs: contract.judgmentRefs,
|
|
853
|
-
artifactDestination: contract.artifactDestination,
|
|
854
|
-
completionCriteria: contract.completionCriteria,
|
|
855
|
-
owner: contract.owner,
|
|
856
|
-
trigger: contract.trigger?.type ? {
|
|
857
|
-
type: contract.trigger.type,
|
|
858
|
-
...(contract.trigger.type === 'schedule' ? { scheduleId: contract.trigger.scheduleId } : {}),
|
|
859
|
-
...(contract.trigger.type === 'document' ? { subscriptionId: contract.trigger.subscriptionId, provider: contract.trigger.provider } : {}),
|
|
860
|
-
} : { type: 'manual' },
|
|
861
|
-
limits: contract.limits,
|
|
862
|
-
...overrides,
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function triggerFromContract(contract, draft) {
|
|
867
|
-
return normalizeTrigger(draft?.trigger ?? contract?.trigger);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
export function createManaOutcomeUI(options = {}) {
|
|
871
|
-
const document = options.document ?? globalThis.document;
|
|
872
|
-
if (!document) throw new Error('outcome_mana_document_unavailable');
|
|
873
|
-
const root = options.root ?? options.container ?? null;
|
|
874
|
-
const api = options.api;
|
|
875
|
-
const apiMutation = options.apiMutation;
|
|
876
|
-
const paths = options.paths ?? {};
|
|
877
|
-
const pathBuilder = typeof options.pathFor === 'function' ? options.pathFor : null;
|
|
878
|
-
const projectInput = options.project;
|
|
879
|
-
const sessionInput = options.session;
|
|
880
|
-
const state = {
|
|
881
|
-
// null means no user choice has been made yet. render() derives a useful
|
|
882
|
-
// first step from the state so existing deep links/readbacks land on the
|
|
883
|
-
// relevant workspace; clicks then remain the source of truth.
|
|
884
|
-
activeView: null,
|
|
885
|
-
delegationTab: 'settings',
|
|
886
|
-
activeStage: null,
|
|
887
|
-
contract: { status: 'loading', records: [], selectedId: null, detail: null, error: null, formDraft: null, pendingSave: null },
|
|
888
|
-
authority: { status: 'idle', matrix: [], raw: null, error: null, draft: null },
|
|
889
|
-
triggers: { status: 'idle', trigger: null, error: null, draft: null, catalogStatus: 'idle', schedules: [], subscriptions: [], catalogError: null },
|
|
890
|
-
safeTest: { status: 'idle', result: null, error: null, formDraft: null },
|
|
891
|
-
activation: { status: 'idle', error: null, message: '' },
|
|
892
|
-
runs: { status: 'idle', records: [], selectedId: null, detail: null, error: null },
|
|
893
|
-
runAction: { status: 'idle', error: null, draft: null },
|
|
894
|
-
connections: { status: 'unknown', records: [], selectedId: null, detail: null, error: null },
|
|
895
|
-
manaSettings: { status: 'unknown', data: null, error: null, draft: null, saveStatus: 'idle', saveError: null },
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
const viewIds = new Set([...MANA_VIEWS.map((view) => view.id), 'delegation_settings']);
|
|
899
|
-
|
|
900
|
-
function stageStatus(id) {
|
|
901
|
-
if (id === 'contract') return state.contract.status;
|
|
902
|
-
if (id === 'authority') return state.authority.status;
|
|
903
|
-
if (id === 'triggers') {
|
|
904
|
-
if (state.triggers.status !== 'idle') return state.triggers.status;
|
|
905
|
-
if (state.triggers.catalogStatus !== 'idle') return state.triggers.catalogStatus;
|
|
906
|
-
return currentContract()?.status ?? 'idle';
|
|
907
|
-
}
|
|
908
|
-
if (id === 'safe_test') {
|
|
909
|
-
if (state.safeTest.status === 'loading') return 'loading';
|
|
910
|
-
return state.safeTest.result ? safeTestStatus(state.safeTest.result, currentContract()) : state.safeTest.status;
|
|
911
|
-
}
|
|
912
|
-
if (id === 'runs') return state.runs.status;
|
|
913
|
-
return 'unknown';
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function derivedActiveStage() {
|
|
917
|
-
if (state.activeStage && WORKFLOW_STAGES.some((stage) => stage.id === state.activeStage)) return state.activeStage;
|
|
918
|
-
if (state.triggers.status !== 'idle' || state.triggers.catalogStatus !== 'idle') return 'triggers';
|
|
919
|
-
// The trigger workspace is where the final activation gates are shown.
|
|
920
|
-
// Keep it as the landing step when a caller has already loaded the two
|
|
921
|
-
// prerequisites but has not clicked a stage yet.
|
|
922
|
-
if (state.authority.status !== 'idle' && state.safeTest.status !== 'idle') return 'triggers';
|
|
923
|
-
if (state.runs.status !== 'idle') return 'runs';
|
|
924
|
-
if (state.safeTest.status !== 'idle') return 'safe_test';
|
|
925
|
-
if (state.authority.status !== 'idle') return 'authority';
|
|
926
|
-
return 'contract';
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
function setActiveStage(id) {
|
|
930
|
-
if (WORKFLOW_STAGES.some((stage) => stage.id === id)) {
|
|
931
|
-
state.activeStage = id;
|
|
932
|
-
state.delegationTab = 'settings';
|
|
933
|
-
// The five existing workflow stages are the detail workspace. Keeping
|
|
934
|
-
// this transition here preserves deep links and existing callers while
|
|
935
|
-
// the top-level navigation remains focused on the five new surfaces.
|
|
936
|
-
// Runs also has a top-level operational view. Keep that view selected
|
|
937
|
-
// while it refreshes or opens a run; explicit workflow-stage navigation
|
|
938
|
-
// still enters the delegation detail workspace.
|
|
939
|
-
if (!(id === 'runs' && state.activeView === 'runs')) state.activeView = 'delegation_settings';
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function setActiveView(id) {
|
|
944
|
-
if (viewIds.has(id)) state.activeView = id;
|
|
945
|
-
return state.activeView;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function derivedActiveView() {
|
|
949
|
-
if (state.activeView && viewIds.has(state.activeView)) return state.activeView;
|
|
950
|
-
// A detail-only readback is the existing deep-link shape. Render it in
|
|
951
|
-
// the settings workspace until the user explicitly returns to overview.
|
|
952
|
-
if (state.contract.detail && !state.contract.records.length) return 'delegation_settings';
|
|
953
|
-
return 'overview';
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function currentProject() { return projectValue(projectInput); }
|
|
957
|
-
function currentSession() { return sessionValue(sessionInput); }
|
|
958
|
-
function currentContract() {
|
|
959
|
-
return state.contract.detail ?? state.contract.records.find((record) => record.contractId === state.contract.selectedId) ?? null;
|
|
960
|
-
}
|
|
961
|
-
function contractId() { return currentContract()?.contractId || null; }
|
|
962
|
-
function currentPaths() {
|
|
963
|
-
const generated = defaultPathMap(options.basePath ?? '/api/outcome-delegations', currentProject().code);
|
|
964
|
-
return { ...generated, ...paths };
|
|
965
|
-
}
|
|
966
|
-
function path(name, params = {}) {
|
|
967
|
-
const context = { ...params, projectCode: currentProject().code, project: currentProject() };
|
|
968
|
-
if (pathBuilder) return pathBuilder(name, context);
|
|
969
|
-
return resolvePath(currentPaths(), name, context);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function connectorSource(project, id) {
|
|
973
|
-
const foundations = objectValue(project.foundation_values);
|
|
974
|
-
const resources = objectValue(project.resources);
|
|
975
|
-
const aliases = {
|
|
976
|
-
brainbase: ['brainbase', 'knowledge'],
|
|
977
|
-
drive: ['drive', 'google_drive', 'googleDrive'],
|
|
978
|
-
github: ['github', 'gitHub'],
|
|
979
|
-
slack: ['slack'],
|
|
980
|
-
mana: ['mana', 'mana_runtime', 'manaRuntime'],
|
|
981
|
-
};
|
|
982
|
-
for (const key of aliases[id] ?? [id]) {
|
|
983
|
-
if (hasOwn(foundations, key)) return foundations[key];
|
|
984
|
-
if (hasOwn(resources, key)) return resources[key];
|
|
985
|
-
if (hasOwn(project, key)) return project[key];
|
|
986
|
-
}
|
|
987
|
-
return null;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
function connectorRecordsFromProject() {
|
|
991
|
-
const project = currentProject();
|
|
992
|
-
return CONNECTOR_DEFINITIONS.map((definition) => {
|
|
993
|
-
const source = connectorSource(project, definition.id);
|
|
994
|
-
// GitHub foundations can be an array of repositories. Keep every item
|
|
995
|
-
// as a permitted resource while leaving the connection state unknown.
|
|
996
|
-
const value = Array.isArray(source)
|
|
997
|
-
? { resources: source }
|
|
998
|
-
: (source !== null && source !== undefined && typeof source !== 'object' ? { account: source } : source);
|
|
999
|
-
const normalized = normalizeConnector(value, definition.id);
|
|
1000
|
-
return {
|
|
1001
|
-
...normalized,
|
|
1002
|
-
label: definition.label,
|
|
1003
|
-
description: definition.description,
|
|
1004
|
-
source,
|
|
1005
|
-
};
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
function connectionStatusLabel(status) {
|
|
1010
|
-
const normalized = normalizeStatus(status);
|
|
1011
|
-
if (['connected', 'verified', 'readback_verified'].includes(normalized)) return '接続確認済み';
|
|
1012
|
-
if (['active', 'enabled', 'ready', 'available'].includes(normalized)) return '利用可能';
|
|
1013
|
-
if (['failed', 'error', 'unavailable'].includes(normalized)) return '確認失敗';
|
|
1014
|
-
if (['unregistered', 'not_registered'].includes(normalized)) return '未登録';
|
|
1015
|
-
if (['unconfigured', 'misconfigured'].includes(normalized)) return '確認設定なし';
|
|
1016
|
-
if (['permission_denied', 'forbidden', 'unauthorized'].includes(normalized)) return '権限不足';
|
|
1017
|
-
if (['timeout', 'timed_out'].includes(normalized)) return 'タイムアウト';
|
|
1018
|
-
if (['rejected', 'unconfirmed', 'readback_mismatch'].includes(normalized)) return '確認できません';
|
|
1019
|
-
return UNKNOWN;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
function connectionReasonLabel(reason) {
|
|
1023
|
-
const value = stringValue(reason).trim();
|
|
1024
|
-
const normalized = normalizeStatus(value);
|
|
1025
|
-
const labels = {
|
|
1026
|
-
connection_unregistered: '接続先が登録されていません',
|
|
1027
|
-
foundation_provider_check_unconfigured: '接続確認の設定が不足しています',
|
|
1028
|
-
foundation_provider_check_url_missing: '接続確認先のURLが設定されていません',
|
|
1029
|
-
provider_check_url_missing: '接続確認先のURLが設定されていません',
|
|
1030
|
-
foundation_provider_check_token_missing: '接続確認用の認証情報が設定されていません',
|
|
1031
|
-
provider_credentials_missing: '接続確認用の認証情報が設定されていません',
|
|
1032
|
-
provider_credentials_unavailable: '接続確認用の認証情報を読み取れません',
|
|
1033
|
-
provider_credentials_invalid: '接続確認用の認証情報が不正です',
|
|
1034
|
-
provider_permission_denied: '認証または権限が不足しています',
|
|
1035
|
-
permission_denied: '認証または権限が不足しています',
|
|
1036
|
-
provider_timeout: '接続先が時間内に応答しませんでした',
|
|
1037
|
-
upstream_timeout: '接続先が時間内に応答しませんでした',
|
|
1038
|
-
provider_request_failed: '接続先へ到達できませんでした',
|
|
1039
|
-
upstream_unavailable: '接続先へ到達できませんでした',
|
|
1040
|
-
provider_rejected: '接続先が確認要求を拒否しました',
|
|
1041
|
-
provider_readback_mismatch: '登録内容と確認結果が一致しません',
|
|
1042
|
-
local_preview_only: 'ローカルプレビューでは実接続を確認できません',
|
|
1043
|
-
};
|
|
1044
|
-
return labels[normalized] ?? (value || UNKNOWN);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
function resourceLabel(value) {
|
|
1048
|
-
const source = objectValue(value);
|
|
1049
|
-
return firstPresent(source.label, source.name, source.title, source.identifier, source.url, source.id) ?? textOf(value);
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
function permittedResources(contract) {
|
|
1053
|
-
const source = objectValue(contract);
|
|
1054
|
-
const raw = source.allowedResources ?? source.allowed_resources ?? source.resources ?? source.scopeResources ?? source.scope_resources;
|
|
1055
|
-
if (Array.isArray(raw)) return raw;
|
|
1056
|
-
if (raw && typeof raw === 'object') return Object.entries(raw).flatMap(([kind, items]) => (Array.isArray(items) ? items : [items]).map((item) => ({ kind, value: item })));
|
|
1057
|
-
const refs = [source.inputRefs, source.knowledgeRefs, source.judgmentRefs].flatMap((items) => Array.isArray(items) ? items : []);
|
|
1058
|
-
return refs;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
function normalizeConnectorPayload(payload) {
|
|
1062
|
-
const records = collectionFrom(payload, ['connectors', 'connections', 'items', 'records']);
|
|
1063
|
-
if (!records) return { state: 'unknown', records: [], raw: payload };
|
|
1064
|
-
return { state: records.length ? 'ready' : 'empty', records: records.map((item) => normalizeConnector(item)), raw: payload };
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function connectorFromFoundationCheck(definition, payload, error, attemptedAt) {
|
|
1068
|
-
const source = objectValue(payload ?? error?.payload);
|
|
1069
|
-
const verification = objectValue(source.verification);
|
|
1070
|
-
const registered = connectorRecordsFromProject().find((item) => item.id === definition.id) ?? {};
|
|
1071
|
-
const status = firstPresent(source.status, verification.state, error?.status === 403 ? 'permission_denied' : null, error ? 'failed' : null);
|
|
1072
|
-
const reason = firstPresent(verification.reason, source.reason, source.error?.code, source.error, error?.message);
|
|
1073
|
-
const resources = Array.isArray(source.resources) ? source.resources : registered.resources;
|
|
1074
|
-
return normalizeConnector({
|
|
1075
|
-
...registered,
|
|
1076
|
-
id: definition.id,
|
|
1077
|
-
label: definition.label,
|
|
1078
|
-
description: definition.description,
|
|
1079
|
-
status,
|
|
1080
|
-
account: firstPresent(source.account, source.identifier, registered.account),
|
|
1081
|
-
resources,
|
|
1082
|
-
checkedAt: firstPresent(verification.checked_at, verification.checkedAt, source.checked_at, source.checkedAt, attemptedAt),
|
|
1083
|
-
reason,
|
|
1084
|
-
}, definition.id);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
async function checkConnections() {
|
|
1088
|
-
const attemptedAt = new Date().toISOString();
|
|
1089
|
-
return Promise.all(CONNECTOR_DEFINITIONS.map(async (definition) => {
|
|
1090
|
-
const kind = CONNECTOR_FOUNDATION_KINDS[definition.id];
|
|
1091
|
-
const target = path('foundationCheck', { kind });
|
|
1092
|
-
if (!target || typeof apiMutation !== 'function') return connectorFromFoundationCheck(definition, {}, null, null);
|
|
1093
|
-
try {
|
|
1094
|
-
const payload = await apiMutation(target, {
|
|
1095
|
-
method: 'POST',
|
|
1096
|
-
key: `mana-foundation-${kind}-check-${attemptedAt}`,
|
|
1097
|
-
body: {},
|
|
1098
|
-
});
|
|
1099
|
-
return connectorFromFoundationCheck(definition, payload, null, attemptedAt);
|
|
1100
|
-
} catch (error) {
|
|
1101
|
-
return connectorFromFoundationCheck(definition, null, error, attemptedAt);
|
|
1102
|
-
}
|
|
1103
|
-
}));
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async function loadConnections() {
|
|
1107
|
-
const checkTarget = path('foundationCheck', { kind: 'github' });
|
|
1108
|
-
const target = path('connectors') ?? path('connections');
|
|
1109
|
-
state.connections.status = 'loading'; state.connections.error = null; render();
|
|
1110
|
-
try {
|
|
1111
|
-
if (checkTarget && typeof apiMutation === 'function') {
|
|
1112
|
-
const records = await checkConnections();
|
|
1113
|
-
state.connections = { ...state.connections, status: 'ready', records, error: null };
|
|
1114
|
-
} else if (!target) {
|
|
1115
|
-
const records = projectConnectors();
|
|
1116
|
-
state.connections = { ...state.connections, status: records.some((item) => item.source !== null && item.source !== undefined) ? 'ready' : 'unknown', records, error: null };
|
|
1117
|
-
} else {
|
|
1118
|
-
const normalized = normalizeConnectorPayload(await callApi(target));
|
|
1119
|
-
state.connections = { ...state.connections, ...normalized, status: normalized.state, error: null };
|
|
1120
|
-
}
|
|
1121
|
-
if (!state.connections.selectedId && state.connections.records.length) state.connections.selectedId = state.connections.records[0].id;
|
|
1122
|
-
state.connections.detail = state.connections.records.find((item) => item.id === state.connections.selectedId) ?? null;
|
|
1123
|
-
} catch (error) { setError(state.connections, error); }
|
|
1124
|
-
render(); notifyChange(); return state.connections;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
async function loadManaSettings() {
|
|
1128
|
-
const target = path('settings') ?? path('manaSettings');
|
|
1129
|
-
const mana = objectValue(currentProject().mana);
|
|
1130
|
-
const projected = objectValue(firstPresent(
|
|
1131
|
-
currentProject().manaSettings,
|
|
1132
|
-
currentProject().mana_settings,
|
|
1133
|
-
mana.settings,
|
|
1134
|
-
mana.runtimeSettings,
|
|
1135
|
-
mana.runtime_settings,
|
|
1136
|
-
));
|
|
1137
|
-
if (!target) {
|
|
1138
|
-
state.manaSettings = { ...state.manaSettings, status: Object.keys(projected).length ? 'ready' : 'unknown', data: Object.keys(projected).length ? projected : null, error: null };
|
|
1139
|
-
render(); notifyChange(); return state.manaSettings;
|
|
1140
|
-
}
|
|
1141
|
-
state.manaSettings.status = 'loading'; state.manaSettings.error = null; render();
|
|
1142
|
-
try {
|
|
1143
|
-
const payload = await callApi(target);
|
|
1144
|
-
const data = objectValue(payload.settings ?? payload.manaSettings ?? payload);
|
|
1145
|
-
state.manaSettings = { ...state.manaSettings, status: Object.keys(data).length ? 'ready' : 'unknown', data: Object.keys(data).length ? data : null, error: null };
|
|
1146
|
-
} catch (error) { setError(state.manaSettings, error); }
|
|
1147
|
-
render(); notifyChange(); return state.manaSettings;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function settingsSaveTarget() {
|
|
1151
|
-
// The read endpoint is intentionally not reused for writes. Hosts must
|
|
1152
|
-
// opt into a write route explicitly so an editable form cannot mutate a
|
|
1153
|
-
// runtime setting by accident.
|
|
1154
|
-
if (!hasOwn(paths, 'settingsSave') && !pathBuilder) return null;
|
|
1155
|
-
return path('settingsSave');
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
function settingSection(data, camelKey, snakeKey) {
|
|
1159
|
-
return objectValue(firstPresent(data[camelKey], data[snakeKey]));
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
function settingValue(section, ...keys) {
|
|
1163
|
-
return firstPresent(...keys.map((key) => section[key])) ?? '';
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function scalarSettingValue(value) {
|
|
1167
|
-
if (typeof value === 'string' || typeof value === 'number') return value;
|
|
1168
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return '';
|
|
1169
|
-
const nestedKeys = ['maxAttempts', 'max_attempts', 'max', 'limit', 'count', 'value'];
|
|
1170
|
-
const nestedKey = nestedKeys.find((key) => hasOwn(value, key));
|
|
1171
|
-
return nestedKey ? scalarSettingValue(value[nestedKey]) : '';
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
function updateAttemptSetting(current, nextValue) {
|
|
1175
|
-
if (current === undefined) return nextValue;
|
|
1176
|
-
if (typeof current === 'string' || typeof current === 'number') return nextValue;
|
|
1177
|
-
if (!current || typeof current !== 'object' || Array.isArray(current)) return current;
|
|
1178
|
-
const nestedKeys = ['maxAttempts', 'max_attempts', 'max', 'limit', 'count', 'value'];
|
|
1179
|
-
const nestedKey = nestedKeys.find((key) => hasOwn(current, key));
|
|
1180
|
-
if (!nestedKey) return current;
|
|
1181
|
-
return { ...current, [nestedKey]: updateAttemptSetting(current[nestedKey], nextValue) };
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
function settingsDraftFromFields(data, controls) {
|
|
1185
|
-
const source = objectValue(data);
|
|
1186
|
-
const draft = JSON.parse(JSON.stringify(source));
|
|
1187
|
-
const defaultsKey = hasOwn(source, 'runtime_defaults') ? 'runtime_defaults' : hasOwn(source, 'runtimeDefaults') ? 'runtimeDefaults' : 'defaults';
|
|
1188
|
-
const safetyKey = hasOwn(source, 'safety_limits') ? 'safety_limits' : hasOwn(source, 'safetyLimits') ? 'safetyLimits' : 'safety_limits';
|
|
1189
|
-
const notificationKey = hasOwn(source, 'notifications') ? 'notifications' : 'notifications';
|
|
1190
|
-
const auditKey = hasOwn(source, 'audit') ? 'audit' : 'audit';
|
|
1191
|
-
const defaults = objectValue(source[defaultsKey]);
|
|
1192
|
-
const safety = objectValue(source[safetyKey]);
|
|
1193
|
-
const attemptKeys = ['maxAttempts', 'max_attempts', 'attempts'];
|
|
1194
|
-
const safetyAttemptKey = attemptKeys.find((key) => hasOwn(safety, key));
|
|
1195
|
-
const defaultsAttemptKey = attemptKeys.find((key) => hasOwn(defaults, key));
|
|
1196
|
-
const attemptsSectionKey = safetyAttemptKey ? safetyKey : defaultsAttemptKey ? defaultsKey : safetyKey;
|
|
1197
|
-
const attemptsPropertyKey = safetyAttemptKey ?? defaultsAttemptKey ?? (attemptsSectionKey === safetyKey ? 'attempts' : 'maxAttempts');
|
|
1198
|
-
draft[defaultsKey] = { ...defaults, timeout: controls.timeout.value, timeoutMs: controls.timeoutMs.value, budgetUnits: controls.budgetUnits.value };
|
|
1199
|
-
if (attemptsSectionKey === defaultsKey) draft[defaultsKey][attemptsPropertyKey] = updateAttemptSetting(defaults[attemptsPropertyKey], controls.maxAttempts.value);
|
|
1200
|
-
if (attemptsSectionKey === safetyKey) draft[safetyKey] = { ...safety, [attemptsPropertyKey]: updateAttemptSetting(safety[attemptsPropertyKey], controls.maxAttempts.value) };
|
|
1201
|
-
draft[safetyKey] = { ...objectValue(draft[safetyKey] ?? safety), safeTestRequired: controls.safeTestRequired.checked, externalEffectApproval: controls.externalEffectApproval.checked };
|
|
1202
|
-
draft[notificationKey] = { ...objectValue(source[notificationKey]), channel: controls.channel.value, events: controls.events.value };
|
|
1203
|
-
draft[auditKey] = { ...objectValue(source[auditKey]), retention: controls.retention.value, destination: controls.auditDestination.value };
|
|
1204
|
-
return draft;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
async function saveManaSettings(draft) {
|
|
1208
|
-
const target = settingsSaveTarget();
|
|
1209
|
-
state.manaSettings.saveError = null;
|
|
1210
|
-
if (state.manaSettings.status !== 'ready' || !target) {
|
|
1211
|
-
state.manaSettings.draft = null;
|
|
1212
|
-
state.manaSettings.saveStatus = 'unknown';
|
|
1213
|
-
render(); notifyChange();
|
|
1214
|
-
return state.manaSettings;
|
|
1215
|
-
}
|
|
1216
|
-
state.manaSettings.draft = objectValue(draft);
|
|
1217
|
-
state.manaSettings.saveStatus = 'saving';
|
|
1218
|
-
render();
|
|
1219
|
-
try {
|
|
1220
|
-
const payload = await callMutation(target, { method: 'PUT', body: { settings: state.manaSettings.draft }, key: idempotencyKey('mana-settings-save') });
|
|
1221
|
-
const readback = objectValue(payload?.settings ?? payload?.manaSettings ?? payload);
|
|
1222
|
-
if (Object.keys(readback).length) {
|
|
1223
|
-
state.manaSettings = { ...state.manaSettings, status: 'ready', data: readback, draft: null, saveStatus: 'ready', saveError: null, error: null };
|
|
1224
|
-
} else {
|
|
1225
|
-
state.manaSettings.saveStatus = 'saved_unverified';
|
|
1226
|
-
}
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
state.manaSettings.saveStatus = 'error_retryable';
|
|
1229
|
-
state.manaSettings.saveError = normalizeError(error);
|
|
1230
|
-
}
|
|
1231
|
-
render(); notifyChange(); return state.manaSettings;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
async function callApi(target, request = {}) {
|
|
1235
|
-
if (typeof api !== 'function') throw new Error('outcome_mana_api_unavailable');
|
|
1236
|
-
return api(target, { method: 'GET', headers: { accept: 'application/json' }, ...request });
|
|
1237
|
-
}
|
|
1238
|
-
async function callMutation(target, request = {}) {
|
|
1239
|
-
if (typeof apiMutation !== 'function') throw new Error('outcome_mana_api_mutation_unavailable');
|
|
1240
|
-
const method = request.method ?? 'POST';
|
|
1241
|
-
const key = request.key ?? idempotencyKey('mana-outcome');
|
|
1242
|
-
return apiMutation(target, { method, key, body: request.body ?? {} });
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
function notifyChange() { if (typeof options.onChange === 'function') options.onChange(state); }
|
|
1246
|
-
function setError(target, error) {
|
|
1247
|
-
target.error = normalizeError(error);
|
|
1248
|
-
target.status = target.error.kind;
|
|
1249
|
-
}
|
|
1250
|
-
function confirmAction(message) {
|
|
1251
|
-
if (typeof options.confirm === 'function') return options.confirm(message);
|
|
1252
|
-
if (typeof globalThis.confirm === 'function') return globalThis.confirm(message);
|
|
1253
|
-
return true;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
async function loadContracts() {
|
|
1257
|
-
state.contract.status = 'loading'; state.contract.error = null; render();
|
|
1258
|
-
try {
|
|
1259
|
-
const payload = await callApi(path('contractList'));
|
|
1260
|
-
const normalized = normalizeContracts(payload);
|
|
1261
|
-
state.contract = { ...state.contract, ...normalized, status: normalized.state, error: null };
|
|
1262
|
-
if (state.contract.records.length && !state.contract.records.some((record) => record.contractId === state.contract.selectedId)) state.contract.selectedId = state.contract.records[0].contractId;
|
|
1263
|
-
if (state.contract.selectedId) await loadContractDetail(state.contract.selectedId, { renderAfter: false });
|
|
1264
|
-
} catch (error) {
|
|
1265
|
-
setError(state.contract, error);
|
|
1266
|
-
}
|
|
1267
|
-
render(); notifyChange();
|
|
1268
|
-
return state.contract;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
async function loadContractDetail(id = contractId(), { renderAfter = true } = {}) {
|
|
1272
|
-
if (!id) return null;
|
|
1273
|
-
state.contract.status = 'loading'; state.contract.error = null;
|
|
1274
|
-
if (renderAfter) render();
|
|
1275
|
-
try {
|
|
1276
|
-
const payload = await callApi(path('contractDetail', { contractId: id }));
|
|
1277
|
-
const contract = normalizeContract(payload);
|
|
1278
|
-
state.contract.detail = contract.contractId ? contract : { ...contract, contractId: id };
|
|
1279
|
-
state.contract.selectedId = id;
|
|
1280
|
-
state.contract.status = 'ready';
|
|
1281
|
-
const authorityPayload = objectValue(payload).authority ?? objectValue(payload).authorityMatrix;
|
|
1282
|
-
if (authorityPayload !== undefined) state.authority = { ...normalizeAuthority(authorityPayload), error: null };
|
|
1283
|
-
const triggerPayload = objectValue(payload).trigger ?? objectValue(payload).triggers;
|
|
1284
|
-
if (triggerPayload !== undefined) state.triggers = { ...state.triggers, status: 'ready', trigger: normalizeTrigger(triggerPayload), error: null, draft: null };
|
|
1285
|
-
await loadTriggerCatalog(state.contract.detail.trigger?.type ?? 'manual', { renderAfter: false, enterDetail: false });
|
|
1286
|
-
} catch (error) {
|
|
1287
|
-
setError(state.contract, error);
|
|
1288
|
-
}
|
|
1289
|
-
if (renderAfter) { render(); notifyChange(); }
|
|
1290
|
-
return state.contract.detail;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
async function loadTriggerCatalog(type = triggerFromContract(currentContract(), state.triggers.draft).type, { renderAfter = true, enterDetail = true } = {}) {
|
|
1294
|
-
if (enterDetail) setActiveStage('triggers');
|
|
1295
|
-
if (type === 'manual') {
|
|
1296
|
-
state.triggers.catalogStatus = 'ready'; state.triggers.schedules = []; state.triggers.subscriptions = []; state.triggers.catalogError = null;
|
|
1297
|
-
if (renderAfter) { render(); notifyChange(); }
|
|
1298
|
-
return state.triggers;
|
|
1299
|
-
}
|
|
1300
|
-
state.triggers.catalogStatus = 'loading'; state.triggers.catalogError = null;
|
|
1301
|
-
if (renderAfter) render();
|
|
1302
|
-
try {
|
|
1303
|
-
const payload = objectValue(await callApi(path('triggerCatalog', { id: type })));
|
|
1304
|
-
state.triggers.schedules = Array.isArray(payload.schedules) ? payload.schedules.filter((item) => item && typeof item === 'object') : [];
|
|
1305
|
-
state.triggers.subscriptions = Array.isArray(payload.subscriptions) ? payload.subscriptions.filter((item) => item && typeof item === 'object') : [];
|
|
1306
|
-
state.triggers.catalogStatus = 'ready';
|
|
1307
|
-
} catch (error) {
|
|
1308
|
-
state.triggers.catalogError = normalizeError(error); state.triggers.catalogStatus = state.triggers.catalogError.kind;
|
|
1309
|
-
}
|
|
1310
|
-
if (renderAfter) { render(); notifyChange(); }
|
|
1311
|
-
return state.triggers;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
function triggerAvailability(trigger) {
|
|
1315
|
-
if (trigger.type === 'manual') return { available: true, reason: '' };
|
|
1316
|
-
if (state.triggers.catalogStatus !== 'ready') return { available: false, reason: '接続候補を確認できていないため、この起動方式は利用できません。' };
|
|
1317
|
-
if (trigger.type === 'schedule') {
|
|
1318
|
-
const candidate = state.triggers.schedules.find((item) => item.id === trigger.scheduleId);
|
|
1319
|
-
if (!candidate) return { available: false, reason: '一致する定期実行の接続が見つからないため、定期実行は利用できません。' };
|
|
1320
|
-
if (!candidate.connected) return { available: false, reason: '定期実行の接続を確認できないため、定期実行は利用できません。' };
|
|
1321
|
-
if (!candidate.enabled) return { available: false, reason: '定期実行の接続が無効なため、定期実行は利用できません。' };
|
|
1322
|
-
return { available: true, reason: '' };
|
|
1323
|
-
}
|
|
1324
|
-
const candidate = state.triggers.subscriptions.find((item) => item.id === trigger.subscriptionId && item.provider === trigger.provider);
|
|
1325
|
-
const provider = trigger.provider ? `${trigger.provider === 'docusign' ? 'DocuSign' : trigger.provider}の` : '';
|
|
1326
|
-
if (!candidate) return { available: false, reason: `${provider}購読接続が見つからないため、文書追加は利用できません。` };
|
|
1327
|
-
if (!candidate.connected) return { available: false, reason: `${provider}購読接続を確認できないため、文書追加は利用できません。` };
|
|
1328
|
-
if (!candidate.enabled) return { available: false, reason: `${provider}購読接続が無効なため、文書追加は利用できません。` };
|
|
1329
|
-
if (candidate.available !== true) {
|
|
1330
|
-
const reasons = {
|
|
1331
|
-
document_event_authenticator_missing: `${provider}文書イベントの署名検証が未接続のため、文書追加は利用できません。`,
|
|
1332
|
-
document_subscription_unsubscriber_missing: `${provider}購読解除処理が未接続のため、文書追加は利用できません。`,
|
|
1333
|
-
document_subscription_scope_mismatch: 'この接続の組織・プロジェクト・実行者が現在の委任と一致しないため利用できません。委任に対応する接続を選択してください。',
|
|
1334
|
-
github_document_ingress_credentials_missing: 'GitHubの文書取り込み用Webhook認証情報が未設定のため、文書追加は利用できません。Webhook用認証情報を設定するか、管理者に確認してください。',
|
|
1335
|
-
github_document_ingress_configuration_missing: 'GitHubのリポジトリ・参照先・取り込みパス・コールバック先の設定が未設定または不正なため、文書追加は利用できません。リポジトリ、参照先、取り込みパス、コールバック先を確認してください。',
|
|
1336
|
-
};
|
|
1337
|
-
return { available: false, reason: reasons[candidate.reason] ?? `${provider}外部プロバイダー固有のWebhook接続を確認できないため、文書追加は利用できません。` };
|
|
1338
|
-
}
|
|
1339
|
-
return { available: true, reason: '' };
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
async function verifyContractReadback(id, mutationPayload, expectedStatus = null, expectedFields = null) {
|
|
1343
|
-
const applyReadback = (readback) => {
|
|
1344
|
-
state.contract.detail = readback;
|
|
1345
|
-
state.contract.selectedId = id;
|
|
1346
|
-
state.contract.status = 'ready';
|
|
1347
|
-
const index = state.contract.records.findIndex((record) => record.contractId === id);
|
|
1348
|
-
if (index === -1) state.contract.records = [...state.contract.records, readback];
|
|
1349
|
-
else state.contract.records = state.contract.records.map((record, recordIndex) => recordIndex === index ? readback : record);
|
|
1350
|
-
return readback;
|
|
1351
|
-
};
|
|
1352
|
-
const inline = normalizeContract(mutationPayload);
|
|
1353
|
-
const expectedVersion = inline.version;
|
|
1354
|
-
if (!Number.isInteger(expectedVersion)) return null;
|
|
1355
|
-
const expectation = { id, version: expectedVersion, fields: expectedFields, status: expectedStatus };
|
|
1356
|
-
if (contractMatchesExpected(inline, expectation) && isReadbackVerified(mutationPayload)) return applyReadback(inline);
|
|
1357
|
-
try {
|
|
1358
|
-
const payload = await callApi(path('contractDetail', { contractId: id }));
|
|
1359
|
-
const readback = normalizeContract(payload);
|
|
1360
|
-
if (contractMatchesExpected(readback, expectation)) return applyReadback(readback);
|
|
1361
|
-
} catch {
|
|
1362
|
-
// A successful write without a readback is intentionally not a verified state.
|
|
1363
|
-
}
|
|
1364
|
-
return null;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
async function saveContractDraft(draft, { create = false } = {}) {
|
|
1368
|
-
const id = contractId();
|
|
1369
|
-
const body = objectValue(draft);
|
|
1370
|
-
state.contract.formDraft = body;
|
|
1371
|
-
state.contract.status = 'saving'; state.contract.error = null; render();
|
|
1372
|
-
try {
|
|
1373
|
-
const target = create || !id ? path('contractList') : path('contractDetail', { contractId: id });
|
|
1374
|
-
const method = create || !id ? 'POST' : 'PATCH';
|
|
1375
|
-
const mutationBody = id && !create && Number.isInteger(currentContract()?.version) ? { ...body, expected_version: currentContract().version } : body;
|
|
1376
|
-
const fingerprint = `${method}:${target}:${JSON.stringify(mutationBody)}`;
|
|
1377
|
-
if (state.contract.pendingSave?.fingerprint !== fingerprint) {
|
|
1378
|
-
state.contract.pendingSave = { fingerprint, key: idempotencyKey(create || !id ? 'mana-contract-create' : 'mana-contract-save') };
|
|
1379
|
-
}
|
|
1380
|
-
const payload = await callMutation(target, {
|
|
1381
|
-
method,
|
|
1382
|
-
body: mutationBody,
|
|
1383
|
-
key: state.contract.pendingSave.key,
|
|
1384
|
-
});
|
|
1385
|
-
const saved = normalizeContract(payload);
|
|
1386
|
-
const savedId = saved.contractId || id;
|
|
1387
|
-
state.contract.formDraft = null;
|
|
1388
|
-
if (savedId) {
|
|
1389
|
-
const readback = await verifyContractReadback(savedId, payload, null, body);
|
|
1390
|
-
if (readback) {
|
|
1391
|
-
state.contract.detail = readback; state.contract.selectedId = savedId;
|
|
1392
|
-
state.contract.records = state.contract.records.some((item) => item.contractId === savedId)
|
|
1393
|
-
? state.contract.records.map((item) => item.contractId === savedId ? readback : item)
|
|
1394
|
-
: [readback, ...state.contract.records];
|
|
1395
|
-
state.contract.status = 'ready';
|
|
1396
|
-
state.contract.pendingSave = null;
|
|
1397
|
-
if (state.safeTest.result) await refreshSafeTestReadback();
|
|
1398
|
-
} else {
|
|
1399
|
-
state.contract.status = 'saved_unverified';
|
|
1400
|
-
}
|
|
1401
|
-
} else state.contract.status = 'saved_unverified';
|
|
1402
|
-
} catch (error) {
|
|
1403
|
-
setError(state.contract, error);
|
|
1404
|
-
}
|
|
1405
|
-
render(); notifyChange(); return state.contract;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
async function cloneContract(id = contractId()) {
|
|
1409
|
-
if (!id) return null;
|
|
1410
|
-
state.contract.status = 'saving'; render();
|
|
1411
|
-
try {
|
|
1412
|
-
const payload = await callMutation(path('contractClone', { contractId: id }), { body: {}, key: idempotencyKey('mana-contract-clone') });
|
|
1413
|
-
const cloned = normalizeContract(payload);
|
|
1414
|
-
const readback = cloned.contractId ? await verifyContractReadback(cloned.contractId, payload, 'draft') : null;
|
|
1415
|
-
if (readback) {
|
|
1416
|
-
state.contract.records = [readback, ...state.contract.records.filter((item) => item.contractId !== readback.contractId)];
|
|
1417
|
-
state.contract.selectedId = readback.contractId; state.contract.detail = readback; state.contract.status = 'ready';
|
|
1418
|
-
} else state.contract.status = 'saved_unverified';
|
|
1419
|
-
} catch (error) { setError(state.contract, error); }
|
|
1420
|
-
render(); notifyChange(); return state.contract;
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
async function retireContract(id = contractId()) {
|
|
1424
|
-
if (!id || !confirmAction('この委任契約を廃止します。履歴は保持されます。続けますか?')) return null;
|
|
1425
|
-
state.contract.status = 'saving'; render();
|
|
1426
|
-
try {
|
|
1427
|
-
const current = currentContract();
|
|
1428
|
-
const payload = await callMutation(path('contractRetire', { contractId: id }), {
|
|
1429
|
-
body: Number.isInteger(current?.version) ? { expected_version: current.version } : {}, key: idempotencyKey('mana-contract-retire'),
|
|
1430
|
-
});
|
|
1431
|
-
const readback = await verifyContractReadback(id, payload, 'retired');
|
|
1432
|
-
if (readback) {
|
|
1433
|
-
state.contract.detail = readback; state.contract.records = state.contract.records.map((item) => item.contractId === id ? readback : item); state.contract.status = 'ready';
|
|
1434
|
-
} else state.contract.status = 'saved_unverified';
|
|
1435
|
-
} catch (error) { setError(state.contract, error); }
|
|
1436
|
-
render(); notifyChange(); return state.contract;
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
async function loadAuthority(id = contractId()) {
|
|
1440
|
-
setActiveStage('authority');
|
|
1441
|
-
if (!id) { state.authority.status = 'unknown'; render(); return state.authority; }
|
|
1442
|
-
state.authority.status = 'loading'; state.authority.error = null; render();
|
|
1443
|
-
try {
|
|
1444
|
-
const payload = await callApi(path('authority', { contractId: id, contractVersion: currentContract()?.version }));
|
|
1445
|
-
state.authority = { ...state.authority, ...normalizeAuthority(payload), error: null };
|
|
1446
|
-
} catch (error) { setError(state.authority, error); }
|
|
1447
|
-
render(); notifyChange(); return state.authority;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
async function saveAuthority(matrix, id = contractId()) {
|
|
1451
|
-
if (!id) return null;
|
|
1452
|
-
state.authority.draft = matrix; state.authority.status = 'saving'; state.authority.error = null; render();
|
|
1453
|
-
try {
|
|
1454
|
-
const current = currentContract();
|
|
1455
|
-
const entries = Array.isArray(matrix) ? matrix : [];
|
|
1456
|
-
for (const entry of entries) {
|
|
1457
|
-
await callMutation(path('authority', { contractId: id }), {
|
|
1458
|
-
method: 'PUT', body: { contract_version: current?.version, operation: entry.operation,
|
|
1459
|
-
resource: entry.resource ?? entry.target, effect: entry.effect ?? entry.policy },
|
|
1460
|
-
key: idempotencyKey(`mana-authority-save-${entry.operation ?? 'rule'}`),
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
const payload = await callApi(path('authority', { contractId: id, contractVersion: current?.version }));
|
|
1464
|
-
const readback = normalizeAuthority(payload);
|
|
1465
|
-
const signature = (entry) => JSON.stringify({
|
|
1466
|
-
operation: entry.operation,
|
|
1467
|
-
resource: entry.resource ?? entry.target,
|
|
1468
|
-
effect: entry.effect ?? entry.policy,
|
|
1469
|
-
actor: entry.actorId ?? entry.actor ?? null,
|
|
1470
|
-
});
|
|
1471
|
-
const expected = entries.map(signature).sort();
|
|
1472
|
-
const actual = readback.matrix.map(signature).sort();
|
|
1473
|
-
state.authority = { ...state.authority, ...readback, draft: null,
|
|
1474
|
-
status: expected.length === actual.length && expected.every((value, index) => value === actual[index]) ? 'ready' : 'saved_unverified', error: null };
|
|
1475
|
-
} catch (error) { setError(state.authority, error); }
|
|
1476
|
-
render(); notifyChange(); return state.authority;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
async function saveTriggers(trigger, id = contractId()) {
|
|
1480
|
-
if (!id) return null;
|
|
1481
|
-
state.triggers.draft = normalizeTrigger(trigger); state.triggers.status = 'saving'; state.triggers.error = null; render();
|
|
1482
|
-
try {
|
|
1483
|
-
const current = currentContract();
|
|
1484
|
-
const normalized = normalizeTrigger(trigger);
|
|
1485
|
-
const cleanTrigger = normalized.type === 'schedule' ? { type: 'schedule', scheduleId: normalized.scheduleId }
|
|
1486
|
-
: normalized.type === 'document' ? { type: 'document', subscriptionId: normalized.subscriptionId, provider: normalized.provider }
|
|
1487
|
-
: { type: 'manual' };
|
|
1488
|
-
const payload = await callMutation(path('triggers', { contractId: id }), {
|
|
1489
|
-
method: 'PUT', body: { contract: contractInput(current, { trigger: cleanTrigger }), ...(Number.isInteger(current?.version) ? { expected_version: current.version } : {}) }, key: idempotencyKey('mana-trigger-save'),
|
|
1490
|
-
});
|
|
1491
|
-
if (isReadbackVerified(payload)) {
|
|
1492
|
-
const savedContract = normalizeContract(payload);
|
|
1493
|
-
const saved = savedContract.trigger;
|
|
1494
|
-
state.contract.detail = savedContract;
|
|
1495
|
-
state.triggers = { ...state.triggers, status: 'ready', trigger: saved, error: null, draft: null };
|
|
1496
|
-
await loadTriggerCatalog(saved.type, { renderAfter: false });
|
|
1497
|
-
} else state.triggers.status = 'saved_unverified';
|
|
1498
|
-
} catch (error) { setError(state.triggers, error); }
|
|
1499
|
-
render(); notifyChange(); return state.triggers;
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
async function runSafeTest(input = {}, id = contractId()) {
|
|
1503
|
-
setActiveStage('safe_test');
|
|
1504
|
-
if (!id) return null;
|
|
1505
|
-
const body = {
|
|
1506
|
-
contract_version: input.contractVersion ?? input.contract_version ?? currentContract()?.version,
|
|
1507
|
-
sample_input: input.sampleInput ?? input.sample_input ?? '',
|
|
1508
|
-
mode: 'isolated',
|
|
1509
|
-
external_send: false,
|
|
1510
|
-
production_writes: false,
|
|
1511
|
-
};
|
|
1512
|
-
state.safeTest.formDraft = body; state.safeTest.status = 'loading'; state.safeTest.error = null; render();
|
|
1513
|
-
try {
|
|
1514
|
-
const payload = await callMutation(path('tests', { contractId: id }), { body, key: idempotencyKey('mana-safe-test') });
|
|
1515
|
-
const accepted = normalizeRun(payload);
|
|
1516
|
-
let readback = null;
|
|
1517
|
-
if (accepted.id) {
|
|
1518
|
-
try { readback = normalizeRun(await callApi(path('runDetail', { runId: accepted.id }))); } catch { /* accepted is not completion */ }
|
|
1519
|
-
}
|
|
1520
|
-
const result = readback?.id ? readback : payload;
|
|
1521
|
-
state.safeTest.result = result;
|
|
1522
|
-
state.safeTest.formDraft = null;
|
|
1523
|
-
state.safeTest.status = safeTestStatus(result, currentContract());
|
|
1524
|
-
} catch (error) { setError(state.safeTest, error); }
|
|
1525
|
-
render(); notifyChange(); return state.safeTest;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
async function refreshSafeTestReadback() {
|
|
1529
|
-
const runId = normalizeRun(state.safeTest.result).id;
|
|
1530
|
-
if (!runId) return state.safeTest;
|
|
1531
|
-
try {
|
|
1532
|
-
const result = normalizeRun(await callApi(path('runDetail', { runId })));
|
|
1533
|
-
state.safeTest.result = result;
|
|
1534
|
-
state.safeTest.status = safeTestStatus(result, currentContract());
|
|
1535
|
-
state.safeTest.error = null;
|
|
1536
|
-
} catch {
|
|
1537
|
-
state.safeTest.status = 'unknown';
|
|
1538
|
-
}
|
|
1539
|
-
return state.safeTest;
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
async function activate(id = contractId()) {
|
|
1543
|
-
setActiveStage('triggers');
|
|
1544
|
-
if (!id) return null;
|
|
1545
|
-
const contract = currentContract();
|
|
1546
|
-
const availability = triggerAvailability(triggerFromContract(currentContract(), state.triggers.draft));
|
|
1547
|
-
const testStatus = state.safeTest.result ? safeTestStatus(state.safeTest.result, contract) : state.safeTest.status;
|
|
1548
|
-
const authorityVerified = state.authority.status === 'ready' && state.authority.matrix.length > 0;
|
|
1549
|
-
const blockedReason = !availability.available ? availability.reason
|
|
1550
|
-
: !authorityVerified ? '権限マトリクスを確認できていないため、委任を有効化できません。'
|
|
1551
|
-
: testStatus !== 'verified' ? '現在の設定で完了条件まで検証した隔離試験が必要です。' : '';
|
|
1552
|
-
if (blockedReason) {
|
|
1553
|
-
state.activation = { status: 'unknown', error: { kind: 'unknown', message: blockedReason }, message: '' };
|
|
1554
|
-
render(); notifyChange(); return null;
|
|
1555
|
-
}
|
|
1556
|
-
state.activation.status = 'loading'; state.activation.error = null; state.activation.message = ''; render();
|
|
1557
|
-
try {
|
|
1558
|
-
const current = currentContract();
|
|
1559
|
-
const payload = await callMutation(path('activation', { contractId: id }), {
|
|
1560
|
-
body: { action: 'activate', ...(Number.isInteger(current?.version) ? { expected_version: current.version } : {}) }, key: idempotencyKey('mana-activation'),
|
|
1561
|
-
});
|
|
1562
|
-
const readback = await verifyContractReadback(id, payload, 'active');
|
|
1563
|
-
if (readback) { state.activation.status = 'verified'; state.activation.message = '有効化済みの契約を再取得して確認しました。'; }
|
|
1564
|
-
else state.activation.status = 'saved_unverified';
|
|
1565
|
-
} catch (error) { const normalized = normalizeError(error); state.activation.error = normalized; state.activation.status = normalized.kind; }
|
|
1566
|
-
render(); notifyChange(); return state.activation;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
async function stopActivation(id = contractId()) {
|
|
1570
|
-
setActiveStage('triggers');
|
|
1571
|
-
if (!id || !confirmAction('この委任を停止します。進行中の実行は実行詳細で確認してください。続けますか?')) return null;
|
|
1572
|
-
state.activation.status = 'loading'; state.activation.error = null; render();
|
|
1573
|
-
try {
|
|
1574
|
-
const current = currentContract();
|
|
1575
|
-
const payload = await callMutation(path('activation', { contractId: id }), {
|
|
1576
|
-
body: { action: 'stop', ...(Number.isInteger(current?.version) ? { expected_version: current.version } : {}) }, key: idempotencyKey('mana-activation-stop'),
|
|
1577
|
-
});
|
|
1578
|
-
const readback = await verifyContractReadback(id, payload, 'stopped');
|
|
1579
|
-
state.activation.status = readback ? 'verified' : 'saved_unverified';
|
|
1580
|
-
if (readback) state.activation.message = '停止状態を再取得して確認しました。';
|
|
1581
|
-
} catch (error) { const normalized = normalizeError(error); state.activation.error = normalized; state.activation.status = normalized.kind; }
|
|
1582
|
-
render(); notifyChange(); return state.activation;
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
async function loadRuns(id = contractId()) {
|
|
1586
|
-
setActiveStage('runs');
|
|
1587
|
-
if (!id) { state.runs.status = 'unknown'; render(); return state.runs; }
|
|
1588
|
-
state.runs.status = 'loading'; state.runs.error = null; render();
|
|
1589
|
-
try {
|
|
1590
|
-
const normalized = normalizeRuns(await callApi(path('runList', { contractId: id })));
|
|
1591
|
-
state.runs = { ...state.runs, ...normalized, status: normalized.state, error: null };
|
|
1592
|
-
const latestSafeTest = normalized.records.find((run) => run.mode === 'safe_test' && run.contractId === id);
|
|
1593
|
-
if (latestSafeTest) {
|
|
1594
|
-
state.safeTest.result = latestSafeTest;
|
|
1595
|
-
state.safeTest.status = safeTestStatus(latestSafeTest, currentContract());
|
|
1596
|
-
state.safeTest.error = null;
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
catch (error) { setError(state.runs, error); }
|
|
1600
|
-
render(); notifyChange(); return state.runs;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
async function loadRunDetail(id = state.runs.selectedId, { renderAfter = true } = {}) {
|
|
1604
|
-
setActiveStage('runs');
|
|
1605
|
-
if (!id) { state.runs.status = 'unknown'; if (renderAfter) render(); return null; }
|
|
1606
|
-
state.runs.status = 'loading'; state.runs.error = null; state.runs.selectedId = id; if (renderAfter) render();
|
|
1607
|
-
try {
|
|
1608
|
-
const payload = await callApi(path('runDetail', { runId: id }));
|
|
1609
|
-
state.runs.detail = normalizeRun(payload); state.runs.status = 'ready';
|
|
1610
|
-
} catch (error) { setError(state.runs, error); }
|
|
1611
|
-
if (renderAfter) { render(); notifyChange(); }
|
|
1612
|
-
return state.runs.detail;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
async function runAction(action, input = {}, id = state.runs.selectedId) {
|
|
1616
|
-
setActiveStage('runs');
|
|
1617
|
-
if (!id) return null;
|
|
1618
|
-
const detail = state.runs.detail ?? state.runs.records.find((item) => item.id === id);
|
|
1619
|
-
const current = objectValue(detail);
|
|
1620
|
-
if (action === 'retry' && current.status === 'stopped') {
|
|
1621
|
-
state.runAction = { status: 'unknown', error: { kind: 'unknown', message: STOPPED_RUN_RESTART_UNAVAILABLE }, draft: null };
|
|
1622
|
-
render(); notifyChange(); return state.runAction;
|
|
1623
|
-
}
|
|
1624
|
-
if (action === 'approve' && !normalizeApprovalTarget(current.pendingApproval, current.contractVersion).complete) {
|
|
1625
|
-
state.runAction = { status: 'unknown', error: { kind: 'unknown', message: '承認対象の差分を取得できないため承認できません。' }, draft: null };
|
|
1626
|
-
render(); notifyChange(); return state.runAction;
|
|
1627
|
-
}
|
|
1628
|
-
const body = { ...objectValue(input) };
|
|
1629
|
-
if (Number.isInteger(current.version)) body.expected_version = current.version;
|
|
1630
|
-
state.runAction = { status: 'loading', error: null, draft: body }; render();
|
|
1631
|
-
try {
|
|
1632
|
-
let targetName = 'runStop';
|
|
1633
|
-
if (action === 'approve' || action === 'reject') {
|
|
1634
|
-
targetName = 'decisions'; body.decision = action;
|
|
1635
|
-
body.approvalId = current.pendingApproval?.approvalId ?? current.pendingApproval?.approval_id ?? '';
|
|
1636
|
-
}
|
|
1637
|
-
else if (action === 'retry') targetName = 'resume';
|
|
1638
|
-
const payload = await callMutation(path(targetName, { runId: id }), { body, key: idempotencyKey(`mana-run-${action}`) });
|
|
1639
|
-
state.runAction.draft = null;
|
|
1640
|
-
const readback = await loadRunDetail(id, { renderAfter: false });
|
|
1641
|
-
state.runAction.status = readback ? 'verified' : isReadbackVerified(payload) ? 'verified' : 'saved_unverified';
|
|
1642
|
-
} catch (error) { state.runAction.status = normalizeError(error).kind; state.runAction.error = normalizeError(error); }
|
|
1643
|
-
render(); notifyChange(); return state.runAction;
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
function renderContractSection() {
|
|
1647
|
-
const section = createElement(document, 'section', { className: 'outcome-mana-section outcome-mana-contract', attrs: { 'aria-labelledby': 'outcome-mana-contract-heading' } });
|
|
1648
|
-
section.append(sectionHeading(document, '成果契約', state.contract.status, '期待する成果、対象範囲、根拠、保存先、完了条件を版付きで管理します。保存と有効化は別操作です。', 'outcome-mana-contract-heading'));
|
|
1649
|
-
const content = createElement(document, 'div', { className: 'outcome-mana-section-content' });
|
|
1650
|
-
if (['loading', 'empty', 'permission_denied', 'error_retryable', 'conflict', 'unknown'].includes(state.contract.status)) {
|
|
1651
|
-
const message = state.contract.status === 'empty'
|
|
1652
|
-
? stateMessage(document, EMPTY, { emptyTitle: '委任契約はありません', emptyText: '新しい下書きを作成すると、成果と完了条件を入力できます。' })
|
|
1653
|
-
: stateMessage(document, state.contract.status, { message: state.contract.error?.message, retry: loadContracts });
|
|
1654
|
-
if (message) content.append(message);
|
|
1655
|
-
if (state.contract.status === 'empty' && canManage(currentSession())) content.append(button(document, '新しい下書きを作成', () => { state.contract.detail = normalizeContract({ status: 'draft', project: currentProject().code }); state.contract.selectedId = null; state.contract.formDraft = {}; state.contract.status = 'ready'; render(); }));
|
|
1656
|
-
section.append(content); return section;
|
|
1657
|
-
}
|
|
1658
|
-
const list = createElement(document, 'div', { className: 'outcome-mana-contract-list', attrs: { role: 'list', 'aria-label': '委任契約一覧' } });
|
|
1659
|
-
state.contract.records.forEach((record) => {
|
|
1660
|
-
const item = createElement(document, 'div', { className: `outcome-mana-contract-list-item${record.contractId === state.contract.selectedId ? ' is-selected' : ''}`, attrs: { role: 'listitem' } });
|
|
1661
|
-
const select = button(document, `${present(record.outcome, '成果未確認')} / ${record.version === null ? '版未確認' : `v${record.version}`}`, () => { state.contract.selectedId = record.contractId; loadContractDetail(record.contractId); }, { className: 'outcome-mana-list-select', attrs: { 'aria-current': record.contractId === state.contract.selectedId ? 'true' : 'false' } });
|
|
1662
|
-
item.append(select, statusBadge(document, record.status)); list.append(item);
|
|
1663
|
-
});
|
|
1664
|
-
content.append(list);
|
|
1665
|
-
const contract = currentContract() ?? normalizeContract({ status: 'draft', project: currentProject().code });
|
|
1666
|
-
const values = contractFormValues(contract, state.contract.formDraft);
|
|
1667
|
-
const form = createElement(document, 'form', { className: 'outcome-mana-form', attrs: { 'aria-label': '成果契約の編集' } });
|
|
1668
|
-
const fields = {};
|
|
1669
|
-
const addField = (name, label, type, options = {}) => { const result = field(document, label, type, name, values[name], options); fields[name] = result.input; form.append(result.label); };
|
|
1670
|
-
addField('outcome', '期待する成果', 'textarea', { attrs: { rows: '4', required: 'required', maxlength: '2000' }, helper: '手順ではなく、受け取りたい状態を具体的に書きます。' });
|
|
1671
|
-
addField('scope', '対象範囲', 'textarea', { attrs: { rows: '3', required: 'required', maxlength: '2000' }, helper: '対象プロジェクトや資料の範囲。サーバーでも検証します。' });
|
|
1672
|
-
addField('inputRefs', '入力資料(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', required: 'required', maxlength: '4000' } });
|
|
1673
|
-
addField('knowledgeRefs', '参照する知識(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', maxlength: '4000' } });
|
|
1674
|
-
addField('judgmentRefs', '参照する判断(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', maxlength: '4000' } });
|
|
1675
|
-
addField('adapterId', '成果物アダプター', 'input', { attrs: { required: 'required', maxlength: '128' } });
|
|
1676
|
-
addField('location', '成果物の保存先', 'input', { attrs: { required: 'required', maxlength: '2000' } });
|
|
1677
|
-
addField('environment', '保存環境', 'select', { options: [{ value: 'isolated', label: '隔離保存先' }, { value: 'production', label: '本番保存先' }], helper: '隔離試験は本番保存先へ書き込みません。' });
|
|
1678
|
-
addField('completionCriteria', '完了条件(id・説明・kind・expected、タブ区切り)', 'textarea', { attrs: { rows: '5', required: 'required', maxlength: '8000' }, helper: '例: criteria-1[Tab]本文を含む[Tab]content_includes[Tab]報告' });
|
|
1679
|
-
addField('ownerActorId', '責任者の主体ID', 'input', { attrs: { required: 'required', maxlength: '128' }, helper: '主体はサーバーで現在の組織権限と照合します。' });
|
|
1680
|
-
const limits = createElement(document, 'div', { className: 'outcome-mana-inline-fields' });
|
|
1681
|
-
for (const [name, label, helper] of [
|
|
1682
|
-
['maxAttempts', '最大試行回数'],
|
|
1683
|
-
['timeoutMs', 'タイムアウト(ms)'],
|
|
1684
|
-
['budgetUnits', '外部処理の実行上限', 'AI生成・保存・外部送信を新しく開始するたびに1回として数えます。1回の処理に含まれる保存確認などは追加で数えません。読み取り・確認、完了済み処理の再開は数えません。'],
|
|
1685
|
-
]) {
|
|
1686
|
-
const result = field(document, label, 'input', name, values[name], { attrs: { type: 'number', min: '1', required: 'required' }, helper }); fields[name] = result.input; limits.append(result.label);
|
|
1687
|
-
}
|
|
1688
|
-
form.append(limits);
|
|
1689
|
-
const formError = state.contract.error ? createElement(document, 'p', { className: 'outcome-mana-inline-error', text: state.contract.error.message, attrs: { role: 'alert' } }) : null;
|
|
1690
|
-
if (formError) form.append(formError);
|
|
1691
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1692
|
-
const manage = canManage(currentSession());
|
|
1693
|
-
const isExisting = Boolean(contract.contractId);
|
|
1694
|
-
const save = button(document, isExisting ? '下書きを保存' : '下書きを作成', async () => {
|
|
1695
|
-
if (typeof form.reportValidity === 'function' && !form.reportValidity()) return;
|
|
1696
|
-
await saveContractDraft(contractDraftFromForm(fields), { create: !isExisting });
|
|
1697
|
-
}, { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !manage || state.contract.status === 'saving', attrs: { 'aria-label': isExisting ? '成果契約の下書きを保存' : '成果契約の下書きを作成' } });
|
|
1698
|
-
actions.append(save);
|
|
1699
|
-
if (isExisting) {
|
|
1700
|
-
actions.append(button(document, '複製', () => cloneContract(contract.contractId), { disabled: !manage || state.contract.status === 'saving' }));
|
|
1701
|
-
if (contract.status !== 'retired') actions.append(button(document, '廃止', () => retireContract(contract.contractId), { className: 'outcome-mana-button outcome-mana-button-danger', disabled: !manage || state.contract.status === 'saving' }));
|
|
1702
|
-
}
|
|
1703
|
-
form.append(actions);
|
|
1704
|
-
listen(form, 'submit', (event) => { event.preventDefault?.(); save.click?.(); });
|
|
1705
|
-
content.append(form); section.append(content); return section;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
function renderAuthoritySection() {
|
|
1709
|
-
const section = createElement(document, 'section', { className: 'outcome-mana-section', attrs: { 'aria-labelledby': 'outcome-mana-authority-heading' } });
|
|
1710
|
-
section.append(sectionHeading(document, '権限マトリクス', state.authority.status, '操作・対象資源・プロジェクト・主体ごとに、自動許可・承認・禁止を設定します。選択だけで権限は拡大しません。', 'outcome-mana-authority-heading'));
|
|
1711
|
-
const content = createElement(document, 'div', { className: 'outcome-mana-section-content' });
|
|
1712
|
-
if (state.authority.status === 'idle') content.append(stateMessage(document, 'unknown', { message: '契約を選択すると、実際の権限マトリクスを確認できます.', retry: contractId() ? () => loadAuthority() : null }));
|
|
1713
|
-
else if (['loading', 'empty', 'permission_denied', 'error_retryable', 'conflict', 'unknown'].includes(state.authority.status)) {
|
|
1714
|
-
const message = stateMessage(document, state.authority.status, { emptyTitle: '権限設定は未登録です', emptyText: '上流の権限設定を確認してから追加します。', message: state.authority.error?.message, retry: () => loadAuthority() });
|
|
1715
|
-
if (message) content.append(message);
|
|
1716
|
-
} else {
|
|
1717
|
-
const matrix = state.authority.draft ?? state.authority.matrix;
|
|
1718
|
-
const table = createElement(document, 'div', { className: 'outcome-mana-authority-table', attrs: { role: 'table', 'aria-label': '操作権限マトリクス' } });
|
|
1719
|
-
const header = createElement(document, 'div', { className: 'outcome-mana-authority-row outcome-mana-authority-header', attrs: { role: 'row' } });
|
|
1720
|
-
['操作', '対象資源', 'プロジェクト', '主体', '設定'].forEach((label) => header.append(createElement(document, 'span', { text: label, attrs: { role: 'columnheader' } }))); table.append(header);
|
|
1721
|
-
const controls = [];
|
|
1722
|
-
matrix.forEach((entry, index) => {
|
|
1723
|
-
const row = createElement(document, 'div', { className: 'outcome-mana-authority-row', attrs: { role: 'row' } });
|
|
1724
|
-
row.append(createElement(document, 'span', { text: entry.operation }), createElement(document, 'span', { text: entry.target }), createElement(document, 'span', { text: entry.project }), createElement(document, 'span', { text: entry.actor }));
|
|
1725
|
-
const select = createElement(document, 'select', { className: 'outcome-mana-input', attrs: { 'aria-label': `${entry.operation}の権限設定` } });
|
|
1726
|
-
Object.entries(AUTHORITY_LABELS).forEach(([value, label]) => select.append(createElement(document, 'option', { text: label, attrs: { value } })));
|
|
1727
|
-
select.value = entry.policy ?? '';
|
|
1728
|
-
controls.push({ select, index }); row.append(select); table.append(row);
|
|
1729
|
-
});
|
|
1730
|
-
content.append(table);
|
|
1731
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1732
|
-
actions.append(button(document, '権限設定を保存', () => {
|
|
1733
|
-
const next = matrix.map((entry, index) => ({ ...entry, policy: controls[index]?.select.value || null }));
|
|
1734
|
-
saveAuthority(next);
|
|
1735
|
-
}, { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) || state.authority.status === 'saving' }));
|
|
1736
|
-
content.append(actions);
|
|
1737
|
-
}
|
|
1738
|
-
section.append(content); return section;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function renderTriggersSection() {
|
|
1742
|
-
const section = createElement(document, 'section', { className: 'outcome-mana-section', attrs: { 'aria-labelledby': 'outcome-mana-triggers-heading' } });
|
|
1743
|
-
const contract = currentContract();
|
|
1744
|
-
section.append(sectionHeading(document, '起動条件と有効化', contract?.status ?? state.triggers.status, '手動・定期・文書追加を設定し、接続・権限・入力・保存先・完了条件を確認してから明示的に有効化します。', 'outcome-mana-triggers-heading'));
|
|
1745
|
-
const content = createElement(document, 'div', { className: 'outcome-mana-section-content' });
|
|
1746
|
-
if (!contract) { content.append(stateMessage(document, 'unknown', { message: '契約を選択すると起動条件を設定できます。' })); section.append(content); return section; }
|
|
1747
|
-
const trigger = triggerFromContract(contract, state.triggers.draft);
|
|
1748
|
-
const availability = triggerAvailability(trigger);
|
|
1749
|
-
if (state.triggers.status === 'permission_denied' || state.triggers.status === 'error_retryable' || state.triggers.status === 'conflict') {
|
|
1750
|
-
const message = stateMessage(document, state.triggers.status, { message: state.triggers.error?.message, retry: () => loadContractDetail(contract.contractId) }); if (message) content.append(message);
|
|
1751
|
-
}
|
|
1752
|
-
const form = createElement(document, 'form', { className: 'outcome-mana-trigger-form', attrs: { 'aria-label': '起動条件の編集' } });
|
|
1753
|
-
const radioGroup = createElement(document, 'fieldset', { className: 'outcome-mana-trigger-options' });
|
|
1754
|
-
radioGroup.append(createElement(document, 'legend', { text: '起動方式' }));
|
|
1755
|
-
const triggerRadios = [];
|
|
1756
|
-
for (const [value, label] of Object.entries(TRIGGER_LABELS)) {
|
|
1757
|
-
const item = createElement(document, 'label', { className: 'outcome-mana-choice' });
|
|
1758
|
-
const radio = createElement(document, 'input', { attrs: { type: 'radio', name: 'outcome-mana-trigger-type', value } }); radio.checked = trigger.type === value; item.append(radio, createElement(document, 'span', { text: label })); radioGroup.append(item);
|
|
1759
|
-
triggerRadios.push(radio);
|
|
1760
|
-
}
|
|
1761
|
-
form.append(radioGroup);
|
|
1762
|
-
const controls = {};
|
|
1763
|
-
const schedule = field(document, 'スケジュールID', 'input', 'scheduleId', trigger.scheduleId, { attrs: { maxlength: '128' }, helper: '定期実行の接続が未確認なら有効化できません。' });
|
|
1764
|
-
const subscription = field(document, '文書購読ID', 'input', 'subscriptionId', trigger.subscriptionId, { attrs: { maxlength: '256' } });
|
|
1765
|
-
const provider = field(document, '文書プロバイダー', 'input', 'provider', trigger.provider, { attrs: { maxlength: '128' } });
|
|
1766
|
-
controls.scheduleId = schedule.input; controls.subscriptionId = subscription.input; controls.provider = provider.input;
|
|
1767
|
-
form.append(schedule.label, subscription.label, provider.label);
|
|
1768
|
-
const triggerStatus = availability.available
|
|
1769
|
-
? statusBadge(document, 'verified', '利用準備確認済み')
|
|
1770
|
-
: createElement(document, 'p', { className: 'outcome-mana-inline-error', text: availability.reason, attrs: { role: 'status' } });
|
|
1771
|
-
form.append(createElement(document, 'div', { className: 'outcome-mana-trigger-status' }), triggerStatus);
|
|
1772
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1773
|
-
actions.append(button(document, '起動条件を保存', () => {
|
|
1774
|
-
const selected = triggerRadios.find((item) => item.checked)?.value ?? trigger.type;
|
|
1775
|
-
saveTriggers({ type: selected, scheduleId: controls.scheduleId.value, subscriptionId: controls.subscriptionId.value, provider: controls.provider.value });
|
|
1776
|
-
}, { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) }));
|
|
1777
|
-
const isActive = contract.status === 'active';
|
|
1778
|
-
const effectiveTestStatus = state.safeTest.status === 'loading' ? 'loading' : state.safeTest.result
|
|
1779
|
-
? safeTestStatus(state.safeTest.result, contract) : state.safeTest.status;
|
|
1780
|
-
const testVerified = effectiveTestStatus === 'verified';
|
|
1781
|
-
const authorityVerified = state.authority.status === 'ready' && state.authority.matrix.length > 0;
|
|
1782
|
-
actions.append(button(document, isActive ? '委任を停止' : '委任を有効化', () => isActive ? stopActivation(contract.contractId) : activate(contract.contractId), { className: isActive ? 'outcome-mana-button outcome-mana-button-danger' : 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) || state.activation.status === 'loading' || (!isActive && (!testVerified || !availability.available || !authorityVerified)) }));
|
|
1783
|
-
if (!isActive && !testVerified) actions.append(createElement(document, 'small', { className: 'outcome-mana-helper', text: '有効化前に隔離試験を実行し、完了条件の検証済み結果を確認してください。' }));
|
|
1784
|
-
if (!isActive && !authorityVerified) actions.append(createElement(document, 'small', { className: 'outcome-mana-helper', text: '有効化前に権限マトリクスのreadbackを確認してください。' }));
|
|
1785
|
-
if (state.activation.error) actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: state.activation.error.message, attrs: { role: 'alert' } }));
|
|
1786
|
-
if (state.activation.message) actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-success', text: state.activation.message, attrs: { role: 'status' } }));
|
|
1787
|
-
form.append(actions); listen(form, 'submit', (event) => event.preventDefault?.()); content.append(form); section.append(content); return section;
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
function renderSafeTestSection() {
|
|
1791
|
-
const section = createElement(document, 'section', { className: 'outcome-mana-section', attrs: { 'aria-labelledby': 'outcome-mana-safe-test-heading' } });
|
|
1792
|
-
const contract = currentContract();
|
|
1793
|
-
const effectiveTestStatus = state.safeTest.status === 'loading' ? 'loading' : state.safeTest.result
|
|
1794
|
-
? safeTestStatus(state.safeTest.result, contract) : state.safeTest.status;
|
|
1795
|
-
section.append(sectionHeading(document, '隔離試験', effectiveTestStatus, '契約版とサンプル入力で試験します。外部送信と本番書き込みはリクエストでも遮断し、未実行の操作を成功とは表示しません。', 'outcome-mana-safe-test-heading'));
|
|
1796
|
-
const content = createElement(document, 'div', { className: 'outcome-mana-section-content' });
|
|
1797
|
-
if (!contract) { content.append(stateMessage(document, 'unknown', { message: '契約を選択すると隔離試験を実行できます。' })); section.append(content); return section; }
|
|
1798
|
-
const form = createElement(document, 'form', { className: 'outcome-mana-safe-test-form', attrs: { 'aria-label': '隔離試験の実行' } });
|
|
1799
|
-
const draft = state.safeTest.formDraft ?? {};
|
|
1800
|
-
const version = field(document, '試験する契約版', 'input', 'contractVersion', draft.contract_version ?? contract.version ?? '', { attrs: { type: 'number', min: '1', required: 'required' }, helper: '実行は選択した版へ固定されます。' });
|
|
1801
|
-
const sample = field(document, 'サンプル入力', 'textarea', 'sampleInput', draft.sample_input ?? '', { attrs: { rows: '6', required: 'required', maxlength: '12000' }, helper: '送信先・本番データではなく、試験用の入力を指定します。' });
|
|
1802
|
-
form.append(version.label, sample.label);
|
|
1803
|
-
const safety = createElement(document, 'p', { className: 'outcome-mana-safe-note', text: '隔離モード: 外部送信なし / 本番書き込みなし / 成果物は隔離保存先のみ' }); form.append(safety);
|
|
1804
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1805
|
-
actions.append(button(document, '隔離試験を実行', () => runSafeTest({ contractVersion: Number(version.input.value), sampleInput: sample.input.value }), { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) || state.safeTest.status === 'loading' }));
|
|
1806
|
-
form.append(actions); listen(form, 'submit', (event) => event.preventDefault?.()); content.append(form);
|
|
1807
|
-
if (effectiveTestStatus === 'unexecuted') content.append(stateMessage(document, 'unknown', { message: '試験要求は受理されましたが、実行結果を確認できていません。成功とは扱いません。', retry: () => runSafeTest({ contractVersion: Number(version.input.value), sampleInput: sample.input.value }) }));
|
|
1808
|
-
if (effectiveTestStatus === 'executed') content.append(stateMessage(document, 'unknown', { message: '試験は実行済みですが、完了条件の検証済み結果を確認できていません。', retry: () => runSafeTest({ contractVersion: Number(version.input.value), sampleInput: sample.input.value }) }));
|
|
1809
|
-
if (effectiveTestStatus === 'verified') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-success', text: 'この設定で試験済み', attrs: { role: 'status' } }));
|
|
1810
|
-
if (effectiveTestStatus === 'stale') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '設定変更のため再試験が必要', attrs: { role: 'status' } }));
|
|
1811
|
-
if (effectiveTestStatus === 'completion_unverified') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '試験は実行済みですが、完了条件の検証結果を確認できていません。', attrs: { role: 'status' } }));
|
|
1812
|
-
if (effectiveTestStatus === 'configuration_snapshot_missing') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '試験時の設定記録が不足しているため、現在の設定と一致するか確認できません。再試験が必要です。', attrs: { role: 'status' } }));
|
|
1813
|
-
if (effectiveTestStatus === 'failed') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '隔離試験が失敗しました。外部への副作用は成功扱いにしていません。', attrs: { role: 'alert' } }));
|
|
1814
|
-
if (state.safeTest.error) content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: state.safeTest.error.message, attrs: { role: 'alert' } }));
|
|
1815
|
-
if (state.safeTest.result) {
|
|
1816
|
-
const result = objectValue(state.safeTest.result);
|
|
1817
|
-
const execution = objectValue(result.execution ?? result.testRun ?? result.run);
|
|
1818
|
-
content.append(definitionList(document, [['実行状態', statusLabel(completionState(state.safeTest.result))], ['試験ID', firstPresent(result.test_id, result.testId, result.id, execution.id)], ['対象版', firstPresent(result.contract_version, result.contractVersion, contract.version)]]));
|
|
1819
|
-
content.append(evidenceList(document, '予定した操作', result.operations ?? execution.operations));
|
|
1820
|
-
content.append(evidenceList(document, '必要な承認', result.approvals ?? execution.approvals));
|
|
1821
|
-
content.append(evidenceList(document, '参照した根拠', result.evidence ?? execution.evidence));
|
|
1822
|
-
content.append(evidenceList(document, '隔離成果物', result.artifacts ?? execution.artifacts));
|
|
1823
|
-
content.append(evidenceList(document, '完了条件の評価', result.completion?.criteria ?? result.completion ?? execution.completion?.criteria ?? execution.completion));
|
|
1824
|
-
}
|
|
1825
|
-
section.append(content); return section;
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
function renderRunDetail() {
|
|
1829
|
-
const detail = state.runs.detail;
|
|
1830
|
-
const section = createElement(document, 'div', { className: 'outcome-mana-run-detail' });
|
|
1831
|
-
if (!detail) { section.append(stateMessage(document, 'unknown', { message: '実行を選択すると、起動理由・契約版・段階・成果物・完了条件・再開地点を確認できます。' })); return section; }
|
|
1832
|
-
section.append(createElement(document, 'h3', { text: '実行詳細' }), statusBadge(document, detail.status));
|
|
1833
|
-
section.append(definitionList(document, [
|
|
1834
|
-
['実行ID', detail.id],
|
|
1835
|
-
['起動理由', triggerLabel(detail.trigger ?? detail.reason)],
|
|
1836
|
-
['契約版', detail.contractVersion === null ? UNKNOWN : `v${detail.contractVersion}`],
|
|
1837
|
-
['設定ハッシュ', detail.configHash],
|
|
1838
|
-
['現在の処理段階', detail.stage],
|
|
1839
|
-
['再開地点', detail.resumePoint],
|
|
1840
|
-
['成果物readback', approvalDisplayValue(detail.artifactReadback)],
|
|
1841
|
-
['判断結果', approvalDisplayValue(detail.judgment)],
|
|
1842
|
-
['判断理由', detail.judgmentReason],
|
|
1843
|
-
['開始', dateLabel(detail.startedAt)],
|
|
1844
|
-
['更新', dateLabel(detail.updatedAt)],
|
|
1845
|
-
]));
|
|
1846
|
-
section.append(stageList(document, detail.stages));
|
|
1847
|
-
section.append(evidenceList(document, '参照した根拠', detail.evidence), evidenceList(document, '成果物', detail.artifacts), evidenceList(document, '完了条件', detail.completion.criteria));
|
|
1848
|
-
if (detail.completion.reason) section.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: detail.completion.reason, attrs: { role: 'alert' } }));
|
|
1849
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1850
|
-
const manageable = canManage(currentSession());
|
|
1851
|
-
if (detail.status === 'waiting_approval') {
|
|
1852
|
-
const approval = normalizeApprovalTarget(detail.pendingApproval, detail.contractVersion);
|
|
1853
|
-
section.append(createElement(document, 'h4', { text: '承認対象の差分と外部影響' }));
|
|
1854
|
-
section.append(definitionList(document, [
|
|
1855
|
-
['差分', approvalDisplayValue(approval.diff)],
|
|
1856
|
-
['操作', approvalDisplayValue(approval.operation)],
|
|
1857
|
-
['対象リソース', approvalDisplayValue(approval.resource)],
|
|
1858
|
-
['payload hash', approvalDisplayValue(approval.payloadHash)],
|
|
1859
|
-
['外部送信先', approvalDisplayValue(approval.destination)],
|
|
1860
|
-
['契約版', approval.contractVersion === null ? UNKNOWN : `v${approval.contractVersion}`],
|
|
1861
|
-
['承認期限', dateLabel(approval.expiresAt)],
|
|
1862
|
-
]));
|
|
1863
|
-
if (!approval.complete) section.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '承認対象の差分を取得できないため承認できません。未確認項目を補ってから再読込してください。', attrs: { role: 'alert' } }));
|
|
1864
|
-
const reason = field(document, '却下理由(必須)', 'textarea', 'rejectReason', state.runAction.draft?.reason ?? '', { attrs: { rows: '3', maxlength: '2000' } });
|
|
1865
|
-
section.append(reason.label);
|
|
1866
|
-
actions.append(button(document, '承認する', () => runAction('approve'), { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !manageable || !approval.complete }), button(document, '却下する', () => runAction('reject', { reason: reason.input.value }), { className: 'outcome-mana-button outcome-mana-button-danger', disabled: !manageable }));
|
|
1867
|
-
}
|
|
1868
|
-
if (detail.status === 'failed') actions.append(button(document, '失敗地点から再開', () => runAction('retry'), { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !manageable }));
|
|
1869
|
-
if (detail.status === 'stopped') section.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: STOPPED_RUN_RESTART_UNAVAILABLE }));
|
|
1870
|
-
if (['queued', 'running', 'waiting_approval'].includes(detail.status)) actions.append(button(document, 'この実行を停止', () => { if (confirmAction('この実行を停止します。続けますか?')) runAction('stop'); }, { className: 'outcome-mana-button outcome-mana-button-danger', disabled: !manageable }));
|
|
1871
|
-
if (state.runAction.error) actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: state.runAction.error.message, attrs: { role: 'alert' } }));
|
|
1872
|
-
if (state.runAction.status === 'saved_unverified') actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '操作は保存されましたが、最新の実行状態を確認できていません。成功とは扱いません。', attrs: { role: 'alert' } }));
|
|
1873
|
-
section.append(actions); return section;
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
function renderRunsSection() {
|
|
1877
|
-
const section = createElement(document, 'section', { className: 'outcome-mana-section outcome-mana-runs', attrs: { 'aria-labelledby': 'outcome-mana-runs-heading' } });
|
|
1878
|
-
section.append(sectionHeading(document, '実行履歴', state.runs.status, '起動理由、契約版、処理段階、承認、成果物、完了検証を実行単位で確認します。', 'outcome-mana-runs-heading'));
|
|
1879
|
-
const content = createElement(document, 'div', { className: 'outcome-mana-section-content' });
|
|
1880
|
-
const toolbar = createElement(document, 'div', { className: 'outcome-mana-toolbar' }); toolbar.append(button(document, '実行履歴を読み込む', () => loadRuns(), { className: 'outcome-mana-button outcome-mana-button-secondary', disabled: state.runs.status === 'loading' })); content.append(toolbar);
|
|
1881
|
-
if (['loading', 'empty', 'permission_denied', 'error_retryable', 'conflict', 'unknown'].includes(state.runs.status)) {
|
|
1882
|
-
const message = stateMessage(document, state.runs.status, { emptyTitle: '実行履歴はありません', emptyText: '有効化後に起動した実行がここへ表示されます。', message: state.runs.error?.message, retry: () => loadRuns() }); if (message) content.append(message);
|
|
1883
|
-
} else {
|
|
1884
|
-
const list = createElement(document, 'div', { className: 'outcome-mana-run-list outcome-mana-run-table', attrs: { role: 'list', 'aria-label': 'Mana実行履歴' } });
|
|
1885
|
-
const tableHeader = createElement(document, 'div', { className: 'outcome-mana-run-table-header', attrs: { role: 'row' } });
|
|
1886
|
-
['起動理由', '契約版', '状態', '更新'].forEach((label) => tableHeader.append(createElement(document, 'span', { text: label, attrs: { role: 'columnheader' } })));
|
|
1887
|
-
list.append(tableHeader);
|
|
1888
|
-
state.runs.records.forEach((run) => {
|
|
1889
|
-
const item = createElement(document, 'div', { className: `outcome-mana-run-item${run.id === state.runs.selectedId ? ' is-selected' : ''}`, attrs: { role: 'listitem' } });
|
|
1890
|
-
item.append(
|
|
1891
|
-
button(document, runReasonLabel(run.reason, run.trigger), () => loadRunDetail(run.id), { className: 'outcome-mana-list-select', attrs: { 'aria-current': run.id === state.runs.selectedId ? 'true' : 'false' } }),
|
|
1892
|
-
createElement(document, 'span', { className: 'outcome-mana-run-version', text: run.contractVersion === null ? '版未確認' : `v${run.contractVersion}` }),
|
|
1893
|
-
statusBadge(document, run.status),
|
|
1894
|
-
createElement(document, 'time', { className: 'outcome-mana-run-updated', text: dateLabel(run.updatedAt ?? run.startedAt) }),
|
|
1895
|
-
); list.append(item);
|
|
1896
|
-
});
|
|
1897
|
-
content.append(list, renderRunDetail());
|
|
1898
|
-
}
|
|
1899
|
-
section.append(content); return section;
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
function renderWorkspaceNavigation() {
|
|
1903
|
-
const nav = createElement(document, 'nav', { className: 'outcome-mana-view-nav', attrs: { 'aria-label': 'Mana管理画面' } });
|
|
1904
|
-
const activeView = derivedActiveView() === 'delegation_settings' ? 'overview' : derivedActiveView();
|
|
1905
|
-
for (const view of MANA_VIEWS) {
|
|
1906
|
-
nav.append(button(document, view.label, () => {
|
|
1907
|
-
setActiveView(view.id);
|
|
1908
|
-
if (view.id === 'runs' && state.runs.status === 'idle' && contractId()) loadRuns();
|
|
1909
|
-
else if (view.id === 'connections' && state.connections.status === 'unknown') loadConnections();
|
|
1910
|
-
else if (view.id === 'settings' && state.manaSettings.status === 'unknown') loadManaSettings();
|
|
1911
|
-
else render();
|
|
1912
|
-
}, {
|
|
1913
|
-
className: `outcome-mana-view-tab${activeView === view.id ? ' is-active' : ''}`,
|
|
1914
|
-
attrs: { 'aria-current': activeView === view.id ? 'page' : 'false' },
|
|
1915
|
-
}));
|
|
1916
|
-
}
|
|
1917
|
-
return nav;
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
function renderOverviewView() {
|
|
1921
|
-
const view = createElement(document, 'section', { className: 'outcome-mana-overview', attrs: { 'aria-labelledby': 'outcome-mana-overview-heading' } });
|
|
1922
|
-
function startNewDelegation() {
|
|
1923
|
-
state.contract.detail = null;
|
|
1924
|
-
state.contract.selectedId = null;
|
|
1925
|
-
state.contract.formDraft = {};
|
|
1926
|
-
setActiveStage('contract');
|
|
1927
|
-
render();
|
|
1928
|
-
}
|
|
1929
|
-
const heading = createElement(document, 'div', { className: 'outcome-mana-view-heading' });
|
|
1930
|
-
heading.append(group(document, 'div', {}, [
|
|
1931
|
-
createElement(document, 'h2', { id: 'outcome-mana-overview-heading', text: '委任一覧' }),
|
|
1932
|
-
createElement(document, 'p', { text: 'Manaに任せた仕事の状態と、次に必要な操作を比較します。' }),
|
|
1933
|
-
]));
|
|
1934
|
-
if (state.contract.status !== 'empty') {
|
|
1935
|
-
heading.append(button(document, '新しい委任を作成', startNewDelegation, { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) }));
|
|
1936
|
-
}
|
|
1937
|
-
view.append(heading);
|
|
1938
|
-
const hasContractSnapshot = ['ready', 'empty'].includes(state.contract.status);
|
|
1939
|
-
const records = state.contract.records;
|
|
1940
|
-
// The runs endpoint is scoped to a single delegation, so it cannot
|
|
1941
|
-
// support a workspace-wide running count without an aggregate API.
|
|
1942
|
-
const runningCount = UNKNOWN;
|
|
1943
|
-
const kpis = [
|
|
1944
|
-
['登録委任', hasContractSnapshot ? records.length : UNKNOWN, 'contract-count'],
|
|
1945
|
-
['有効', hasContractSnapshot ? records.filter((item) => item.status === 'active').length : UNKNOWN, 'active-count'],
|
|
1946
|
-
['確認待ち', hasContractSnapshot ? records.filter((item) => ['draft', 'waiting_approval', 'saved_unverified'].includes(item.status)).length : UNKNOWN, 'waiting-count'],
|
|
1947
|
-
['実行中', runningCount, 'running-count'],
|
|
1948
|
-
];
|
|
1949
|
-
const kpiGrid = createElement(document, 'div', { className: 'outcome-mana-kpi-grid', attrs: { 'aria-label': 'Mana委任の概要指標' } });
|
|
1950
|
-
kpis.forEach(([label, value, id]) => {
|
|
1951
|
-
const card = createElement(document, 'article', { className: 'outcome-mana-kpi-card', attrs: { 'data-kpi': id } });
|
|
1952
|
-
card.append(createElement(document, 'span', { className: 'outcome-mana-kpi-label', text: label }), createElement(document, 'strong', { className: 'outcome-mana-kpi-value', text: String(value) }));
|
|
1953
|
-
kpiGrid.append(card);
|
|
1954
|
-
});
|
|
1955
|
-
view.append(kpiGrid);
|
|
1956
|
-
if (state.contract.status === 'empty') {
|
|
1957
|
-
const empty = createElement(document, 'section', {
|
|
1958
|
-
className: 'outcome-mana-state outcome-mana-empty outcome-mana-empty-workspace',
|
|
1959
|
-
attrs: { role: 'region', 'aria-labelledby': 'outcome-mana-empty-workspace-heading' },
|
|
1960
|
-
});
|
|
1961
|
-
empty.append(
|
|
1962
|
-
createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '最初の委任' }),
|
|
1963
|
-
createElement(document, 'h3', { id: 'outcome-mana-empty-workspace-heading', text: 'Manaに仕事を委任しましょう' }),
|
|
1964
|
-
createElement(document, 'p', { text: '登録された委任はありません。成果と責任者を決め、使える資源と起動条件を確認してから安全試験へ進めます。' }),
|
|
1965
|
-
);
|
|
1966
|
-
const steps = createElement(document, 'ol', { className: 'outcome-mana-empty-steps' });
|
|
1967
|
-
[
|
|
1968
|
-
['成果と責任者', 'Manaに任せる結果と、その結果を確認する人を決めます。'],
|
|
1969
|
-
['資源と起動条件', '委任で使える資源と、実行を始める条件を設定します。'],
|
|
1970
|
-
['安全試験と有効化', '保存後に安全試験を行い、内容を確認してから有効化します。'],
|
|
1971
|
-
].forEach(([title, description], index) => {
|
|
1972
|
-
const item = createElement(document, 'li', { className: 'outcome-mana-empty-step' });
|
|
1973
|
-
item.append(createElement(document, 'span', { className: 'outcome-mana-empty-step-number', text: String(index + 1) }));
|
|
1974
|
-
item.append(group(document, 'span', { className: 'outcome-mana-empty-step-copy' }, [
|
|
1975
|
-
createElement(document, 'strong', { text: title }),
|
|
1976
|
-
createElement(document, 'small', { text: description }),
|
|
1977
|
-
]));
|
|
1978
|
-
steps.append(item);
|
|
1979
|
-
});
|
|
1980
|
-
empty.append(steps);
|
|
1981
|
-
empty.append(button(document, '成果契約を入力する', startNewDelegation, {
|
|
1982
|
-
className: 'outcome-mana-button outcome-mana-button-primary',
|
|
1983
|
-
disabled: !canManage(currentSession()),
|
|
1984
|
-
}));
|
|
1985
|
-
if (!canManage(currentSession())) {
|
|
1986
|
-
empty.append(createElement(document, 'small', { className: 'outcome-mana-helper', text: '委任を作成できる権限を持つ管理者に依頼してください。' }));
|
|
1987
|
-
}
|
|
1988
|
-
view.append(empty);
|
|
1989
|
-
return view;
|
|
1990
|
-
}
|
|
1991
|
-
if (['loading', 'permission_denied', 'error_retryable', 'unknown'].includes(state.contract.status)) {
|
|
1992
|
-
view.append(stateMessage(document, state.contract.status, { message: state.contract.error?.message, retry: loadContracts }));
|
|
1993
|
-
return view;
|
|
1994
|
-
}
|
|
1995
|
-
const layout = createElement(document, 'div', { className: 'outcome-mana-overview-layout' });
|
|
1996
|
-
const list = createElement(document, 'div', { className: 'outcome-mana-delegation-list', attrs: { role: 'list', 'aria-label': 'Mana委任一覧' } });
|
|
1997
|
-
for (const contract of state.contract.records) {
|
|
1998
|
-
const selected = contract.contractId === state.contract.selectedId;
|
|
1999
|
-
const item = button(document, '', async () => { await loadContractDetail(contract.contractId); setActiveView('overview'); render(); }, {
|
|
2000
|
-
className: `outcome-mana-delegation-card${selected ? ' is-selected' : ''}`,
|
|
2001
|
-
attrs: { role: 'listitem', 'aria-current': selected ? 'true' : 'false' },
|
|
2002
|
-
});
|
|
2003
|
-
const copy = createElement(document, 'span', { className: 'outcome-mana-delegation-copy' });
|
|
2004
|
-
const nextAction = contract.nextAction || (contract.status === 'active' ? '実行履歴を確認' : contract.status === 'draft' ? '委任設定を完了' : '状態を確認');
|
|
2005
|
-
copy.append(
|
|
2006
|
-
createElement(document, 'strong', { text: present(contract.outcome, '成果未登録') }),
|
|
2007
|
-
createElement(document, 'small', { text: `${triggerLabel(contract.trigger?.type)} · v${contract.version ?? UNKNOWN}` }),
|
|
2008
|
-
createElement(document, 'small', { text: `次の操作: ${nextAction}` }),
|
|
2009
|
-
);
|
|
2010
|
-
item.append(copy, statusBadge(document, contract.status));
|
|
2011
|
-
list.append(item);
|
|
2012
|
-
}
|
|
2013
|
-
const selected = currentContract();
|
|
2014
|
-
const inspector = createElement(document, 'aside', { className: 'outcome-mana-inspector', attrs: { 'aria-label': '選択した委任の概要' } });
|
|
2015
|
-
if (!selected) inspector.append(stateMessage(document, 'unknown', { message: '委任を選ぶと成果、責任者、起動条件、最終実行を確認できます。' }));
|
|
2016
|
-
else {
|
|
2017
|
-
inspector.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '選択中の委任' }), createElement(document, 'h3', { text: present(selected.outcome, '成果未登録') }));
|
|
2018
|
-
inspector.append(definitionList(document, [
|
|
2019
|
-
['責任者', present(selected.owner?.actorId)], ['対象範囲', present(selected.scope)], ['起動条件', triggerLabel(selected.trigger?.type)],
|
|
2020
|
-
['契約版', selected.version === null ? UNKNOWN : `v${selected.version}`], ['最終実行', state.runs.records[0] ? dateLabel(state.runs.records[0].updatedAt ?? state.runs.records[0].startedAt) : UNKNOWN],
|
|
2021
|
-
]));
|
|
2022
|
-
inspector.append(button(document, '委任設定を開く', () => { setActiveView('delegation_settings'); render(); }, { className: 'outcome-mana-button outcome-mana-button-primary' }));
|
|
2023
|
-
}
|
|
2024
|
-
layout.append(list, inspector); view.append(layout); return view;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
function projectConnectors() {
|
|
2028
|
-
const project = currentProject();
|
|
2029
|
-
const source = project.connections ?? project.connectors ?? project.foundationConnections ?? project.foundation_connections;
|
|
2030
|
-
const values = Array.isArray(source) ? source : Object.entries(objectValue(source)).map(([id, value]) => ({ id, ...objectValue(value) }));
|
|
2031
|
-
const byId = new Map(values.map((item) => [normalizeConnector(item).id.toLowerCase(), normalizeConnector(item)]));
|
|
2032
|
-
const registeredById = new Map(connectorRecordsFromProject().map((item) => [item.id.toLowerCase(), item]));
|
|
2033
|
-
const loadedById = new Map(state.connections.records.map((item) => [item.id.toLowerCase(), item]));
|
|
2034
|
-
return CONNECTOR_DEFINITIONS.map((definition) => ({
|
|
2035
|
-
...definition,
|
|
2036
|
-
...(registeredById.get(definition.id) ?? {}),
|
|
2037
|
-
...(byId.get(definition.id) ?? {}),
|
|
2038
|
-
...(loadedById.get(definition.id) ?? {}),
|
|
2039
|
-
label: definition.label,
|
|
2040
|
-
description: definition.description,
|
|
2041
|
-
}));
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
function renderConnectionsView() {
|
|
2045
|
-
const view = createElement(document, 'section', { className: 'outcome-mana-connections', attrs: { 'aria-labelledby': 'outcome-mana-connections-heading' } });
|
|
2046
|
-
const headingCopy = group(document, 'div', {}, [createElement(document, 'h2', { id: 'outcome-mana-connections-heading', text: '接続状態' }), createElement(document, 'p', { text: 'Mana runtimeの組織接続と、選択中の委任が利用できる資源を分けて表示します。' })]);
|
|
2047
|
-
const headingActions = createElement(document, 'div', { className: 'outcome-mana-toolbar' });
|
|
2048
|
-
headingActions.append(button(document, state.connections.status === 'loading' ? '確認中…' : '接続を再確認', () => loadConnections(), { className: 'outcome-mana-button outcome-mana-button-secondary', disabled: state.connections.status === 'loading' }));
|
|
2049
|
-
view.append(group(document, 'div', { className: 'outcome-mana-view-heading' }, [headingCopy, headingActions]));
|
|
2050
|
-
const connectors = projectConnectors();
|
|
2051
|
-
if (!state.connections.selectedId && connectors.length) state.connections.selectedId = connectors[0].id;
|
|
2052
|
-
const selectedConnector = connectors.find((item) => item.id === state.connections.selectedId) ?? null;
|
|
2053
|
-
const table = createElement(document, 'div', { className: 'outcome-mana-connection-table', attrs: { role: 'table', 'aria-label': 'Mana組織接続一覧' } });
|
|
2054
|
-
const header = createElement(document, 'div', { className: 'outcome-mana-connection-row outcome-mana-connection-header', attrs: { role: 'row' } });
|
|
2055
|
-
['接続', '状態', 'アカウント', '登録資源', '最終確認'].forEach((label) => header.append(createElement(document, 'span', { text: label, attrs: { role: 'columnheader' } })));
|
|
2056
|
-
table.append(header);
|
|
2057
|
-
for (const connector of connectors) {
|
|
2058
|
-
const selected = connector.id === state.connections.selectedId;
|
|
2059
|
-
const row = button(document, '', () => {
|
|
2060
|
-
state.connections.selectedId = connector.id;
|
|
2061
|
-
state.connections.detail = connector;
|
|
2062
|
-
render();
|
|
2063
|
-
}, { className: `outcome-mana-connection-row${selected ? ' is-selected' : ''}`, attrs: { role: 'row', 'aria-current': selected ? 'true' : 'false' } });
|
|
2064
|
-
row.append(
|
|
2065
|
-
connectorIdentity(document, connector, { attrs: { role: 'cell' } }),
|
|
2066
|
-
group(document, 'span', { attrs: { role: 'cell' } }, [statusBadge(document, connector.status ?? 'unknown', connectionStatusLabel(connector.status))]),
|
|
2067
|
-
createElement(document, 'span', { text: present(connector.account), attrs: { role: 'cell' } }),
|
|
2068
|
-
createElement(document, 'span', { text: connector.resources?.length ? connector.resources.map(resourceLabel).join('、') : UNKNOWN, attrs: { role: 'cell' } }),
|
|
2069
|
-
createElement(document, 'span', { text: dateLabel(connector.checkedAt), attrs: { role: 'cell' } }),
|
|
2070
|
-
);
|
|
2071
|
-
table.append(row);
|
|
2072
|
-
}
|
|
2073
|
-
const contract = currentContract();
|
|
2074
|
-
const inspector = createElement(document, 'aside', { className: 'outcome-mana-connection-inspector', attrs: { 'aria-label': '選択した接続の詳細' } });
|
|
2075
|
-
inspector.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '選択中の接続' }));
|
|
2076
|
-
if (selectedConnector) {
|
|
2077
|
-
inspector.append(connectorIdentity(document, selectedConnector, { large: true }));
|
|
2078
|
-
inspector.append(definitionList(document, [['接続状態', connectionStatusLabel(selectedConnector.status)], ['接続先', selectedConnector.account], ['確認日時', dateLabel(selectedConnector.checkedAt)], ['確認理由', connectionReasonLabel(selectedConnector.reason)]]));
|
|
2079
|
-
inspector.append(evidenceList(document, '登録資源', selectedConnector.resources));
|
|
2080
|
-
} else inspector.append(stateMessage(document, 'unknown', { message: '接続を選ぶと登録資源と確認状態を表示します。' }));
|
|
2081
|
-
const allowed = createElement(document, 'aside', { className: 'outcome-mana-resource-panel' });
|
|
2082
|
-
allowed.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '委任単位の許可範囲' }), createElement(document, 'h3', { text: 'この委任が利用できる資源' }));
|
|
2083
|
-
if (!contract) allowed.append(stateMessage(document, 'unknown', { message: '委任を選ぶと、契約版に固定された参照先と保存先を確認できます。' }));
|
|
2084
|
-
else {
|
|
2085
|
-
const permitted = permittedResources(contract);
|
|
2086
|
-
allowed.append(evidenceList(document, '許可リソース', permitted));
|
|
2087
|
-
allowed.append(evidenceList(document, '入力', contract.inputRefs), evidenceList(document, '知識', contract.knowledgeRefs), evidenceList(document, '判断', contract.judgmentRefs), definitionList(document, [['保存先', present(contract.artifactDestination?.location)], ['接続ID', present(contract.artifactDestination?.adapterId)], ['環境', present(contract.artifactDestination?.environment)]]));
|
|
2088
|
-
}
|
|
2089
|
-
view.append(table, group(document, 'div', { className: 'outcome-mana-connection-detail-layout' }, [inspector, allowed])); return view;
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
function renderDelegationSettings() {
|
|
2093
|
-
const view = createElement(document, 'section', { className: 'outcome-mana-settings-workspace', attrs: { 'aria-labelledby': 'outcome-mana-settings-workspace-heading' } });
|
|
2094
|
-
const top = createElement(document, 'div', { className: 'outcome-mana-view-heading' });
|
|
2095
|
-
top.append(group(document, 'div', {}, [createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '委任設定' }), createElement(document, 'h2', { id: 'outcome-mana-settings-workspace-heading', text: present(currentContract()?.outcome, '新しい委任') }), createElement(document, 'p', { text: '保存、安全試験、有効化を別々に確認しながら進めます。' })]), button(document, '一覧へ戻る', () => { setActiveView('overview'); render(); }, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
2096
|
-
const tabs = createElement(document, 'div', { className: 'outcome-mana-detail-tabs', attrs: { role: 'tablist', 'aria-label': '委任詳細' } });
|
|
2097
|
-
[['summary', '概要'], ['settings', '設定'], ['runs', '実行履歴']].forEach(([id, label]) => {
|
|
2098
|
-
const active = state.delegationTab === id;
|
|
2099
|
-
tabs.append(button(document, label, () => {
|
|
2100
|
-
if (id === 'summary') { setActiveView('overview'); return; }
|
|
2101
|
-
state.delegationTab = id;
|
|
2102
|
-
setActiveView('delegation_settings');
|
|
2103
|
-
render();
|
|
2104
|
-
}, { className: `outcome-mana-detail-tab${active ? ' is-active' : ''}`, attrs: { role: 'tab', 'aria-selected': active ? 'true' : 'false' } }));
|
|
2105
|
-
});
|
|
2106
|
-
view.append(top, tabs);
|
|
2107
|
-
if (state.delegationTab === 'runs') {
|
|
2108
|
-
view.append(renderRunsSection());
|
|
2109
|
-
return view;
|
|
2110
|
-
}
|
|
2111
|
-
const workspace = createElement(document, 'div', { className: 'outcome-mana-delegation-layout' });
|
|
2112
|
-
workspace.append(renderStageNavigator(), renderStageWorkspace());
|
|
2113
|
-
view.append(workspace);
|
|
2114
|
-
const sticky = createElement(document, 'div', { className: 'outcome-mana-sticky-actions', attrs: { 'aria-label': '委任設定の次の操作' } });
|
|
2115
|
-
sticky.append(createElement(document, 'span', { className: 'outcome-mana-muted', text: '保存と安全試験は別の工程として記録します。' }));
|
|
2116
|
-
sticky.append(button(document, '安全試験を開く', () => { state.delegationTab = 'settings'; setActiveStage('safe_test'); render(); }, { className: 'outcome-mana-button outcome-mana-button-primary' }));
|
|
2117
|
-
sticky.append(button(document, '実行履歴を見る', () => { state.delegationTab = 'runs'; setActiveView('delegation_settings'); render(); }, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
2118
|
-
view.append(sticky);
|
|
2119
|
-
return view;
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
function renderRunsView() {
|
|
2123
|
-
const view = createElement(document, 'section', { className: 'outcome-mana-runs-view', attrs: { 'aria-labelledby': 'outcome-mana-runs-view-heading' } });
|
|
2124
|
-
view.append(group(document, 'div', { className: 'outcome-mana-view-heading' }, [group(document, 'div', {}, [createElement(document, 'h2', { id: 'outcome-mana-runs-view-heading', text: '実行履歴' }), createElement(document, 'p', { text: '成果生成から保存、readback、完了判定までを実行単位で追跡します。' })])]));
|
|
2125
|
-
view.append(renderRunsSection()); return view;
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
function renderSettingsView() {
|
|
2129
|
-
const view = createElement(document, 'section', { className: 'outcome-mana-runtime-settings', attrs: { 'aria-labelledby': 'outcome-mana-runtime-settings-heading' } });
|
|
2130
|
-
view.append(group(document, 'div', { className: 'outcome-mana-view-heading' }, [group(document, 'div', {}, [createElement(document, 'h2', { id: 'outcome-mana-runtime-settings-heading', text: 'Mana設定' }), createElement(document, 'p', { text: 'runtime全体の既定値、安全上限、通知、監査を確認します。実効権限は各委任契約の版で決まります。' })])]));
|
|
2131
|
-
if (['loading', 'permission_denied', 'error_retryable', 'conflict'].includes(state.manaSettings.status)) {
|
|
2132
|
-
const message = stateMessage(document, state.manaSettings.status, {
|
|
2133
|
-
message: state.manaSettings.error?.message,
|
|
2134
|
-
retry: () => loadManaSettings(),
|
|
2135
|
-
loadingText: 'Mana設定を読み込んでいます…',
|
|
2136
|
-
});
|
|
2137
|
-
if (message) view.append(message);
|
|
2138
|
-
return view;
|
|
2139
|
-
}
|
|
2140
|
-
if (state.manaSettings.status !== 'ready') {
|
|
2141
|
-
view.append(stateMessage(document, 'unknown', { message: '現在のruntime設定を確認できていません。取得できるまで編集と保存はできません。' }));
|
|
2142
|
-
return view;
|
|
2143
|
-
}
|
|
2144
|
-
const data = objectValue(state.manaSettings.data);
|
|
2145
|
-
const defaults = objectValue(firstPresent(data.defaults, data.runtimeDefaults, data.runtime_defaults));
|
|
2146
|
-
const safety = objectValue(firstPresent(data.safetyLimits, data.safety_limits));
|
|
2147
|
-
const notifications = objectValue(data.notifications);
|
|
2148
|
-
const audit = objectValue(data.audit);
|
|
2149
|
-
const controls = {};
|
|
2150
|
-
const form = createElement(document, 'form', { className: 'outcome-mana-settings-form', attrs: { 'aria-label': 'Mana runtime設定の編集' } });
|
|
2151
|
-
const defaultFieldset = createElement(document, 'fieldset', { className: 'outcome-mana-settings-fieldset' });
|
|
2152
|
-
defaultFieldset.append(createElement(document, 'legend', { text: '既定値' }));
|
|
2153
|
-
const timeout = field(document, 'タイムアウト表示', 'input', 'timeout', settingValue(defaults, 'timeout', 'duration'), { helper: '未確認の場合は空欄のまま保持します。', attrs: { placeholder: UNKNOWN } });
|
|
2154
|
-
const timeoutMs = field(document, 'タイムアウト(ms)', 'input', 'timeoutMs', settingValue(defaults, 'timeoutMs', 'timeout_ms'), { attrs: { inputmode: 'numeric', placeholder: UNKNOWN } });
|
|
2155
|
-
const budgetUnits = field(document, '外部処理の上限', 'input', 'budgetUnits', settingValue(defaults, 'budgetUnits', 'budget_units'), { attrs: { inputmode: 'numeric', placeholder: UNKNOWN } });
|
|
2156
|
-
controls.timeout = timeout.input; controls.timeoutMs = timeoutMs.input; controls.budgetUnits = budgetUnits.input;
|
|
2157
|
-
defaultFieldset.append(timeout.label, timeoutMs.label, budgetUnits.label);
|
|
2158
|
-
const safetyFieldset = createElement(document, 'fieldset', { className: 'outcome-mana-settings-fieldset' });
|
|
2159
|
-
safetyFieldset.append(createElement(document, 'legend', { text: '安全上限' }));
|
|
2160
|
-
const attemptKeys = ['maxAttempts', 'max_attempts', 'attempts'];
|
|
2161
|
-
const knownSafetyAttemptKey = attemptKeys.find((key) => hasOwn(safety, key));
|
|
2162
|
-
const knownDefaultsAttemptKey = attemptKeys.find((key) => hasOwn(defaults, key));
|
|
2163
|
-
const rawMaxAttempts = knownSafetyAttemptKey ? safety[knownSafetyAttemptKey] : knownDefaultsAttemptKey ? defaults[knownDefaultsAttemptKey] : '';
|
|
2164
|
-
const maxAttemptsValue = scalarSettingValue(rawMaxAttempts);
|
|
2165
|
-
const maxAttempts = field(document, '最大試行回数', 'input', 'maxAttempts', maxAttemptsValue, { attrs: { inputmode: 'numeric', placeholder: UNKNOWN } });
|
|
2166
|
-
controls.maxAttempts = maxAttempts.input;
|
|
2167
|
-
safetyFieldset.append(maxAttempts.label);
|
|
2168
|
-
const toggleField = (labelText, name, value) => {
|
|
2169
|
-
const label = createElement(document, 'label', { className: 'outcome-mana-toggle-field' });
|
|
2170
|
-
const input = createElement(document, 'input', { attrs: { type: 'checkbox', name, id: `outcome-mana-${name}` } });
|
|
2171
|
-
const known = typeof value === 'boolean';
|
|
2172
|
-
input.checked = known && value;
|
|
2173
|
-
if (!known) input.disabled = true;
|
|
2174
|
-
label.append(input, createElement(document, 'span', { text: labelText }), createElement(document, 'small', { className: 'outcome-mana-helper', text: known ? (value ? '有効' : '無効') : UNKNOWN }));
|
|
2175
|
-
return { label, input };
|
|
2176
|
-
};
|
|
2177
|
-
const safeTestRequired = toggleField('安全試験を必須にする', 'safeTestRequired', firstPresent(safety.safeTestRequired, safety.safe_test_required));
|
|
2178
|
-
const externalEffectApproval = toggleField('外部作用に承認を要求する', 'externalEffectApproval', firstPresent(safety.externalEffectApproval, safety.external_effect_approval));
|
|
2179
|
-
controls.safeTestRequired = safeTestRequired.input; controls.externalEffectApproval = externalEffectApproval.input;
|
|
2180
|
-
safetyFieldset.append(safeTestRequired.label, externalEffectApproval.label);
|
|
2181
|
-
const notificationFieldset = createElement(document, 'fieldset', { className: 'outcome-mana-settings-fieldset' });
|
|
2182
|
-
notificationFieldset.append(createElement(document, 'legend', { text: '通知' }));
|
|
2183
|
-
const channel = field(document, '通知先', 'input', 'channel', settingValue(notifications, 'channel', 'destination'), { attrs: { placeholder: UNKNOWN } });
|
|
2184
|
-
const events = field(document, '対象イベント', 'input', 'events', settingValue(notifications, 'events', 'eventFilter', 'event_filter'), { attrs: { placeholder: UNKNOWN } });
|
|
2185
|
-
controls.channel = channel.input; controls.events = events.input;
|
|
2186
|
-
notificationFieldset.append(channel.label, events.label);
|
|
2187
|
-
const auditFieldset = createElement(document, 'fieldset', { className: 'outcome-mana-settings-fieldset' });
|
|
2188
|
-
auditFieldset.append(createElement(document, 'legend', { text: '監査' }));
|
|
2189
|
-
const retention = field(document, '保持期間', 'input', 'retention', settingValue(audit, 'retention', 'retentionPeriod', 'retention_period'), { attrs: { placeholder: UNKNOWN } });
|
|
2190
|
-
const auditDestination = field(document, '監査保存先', 'input', 'auditDestination', settingValue(audit, 'destination', 'location'), { attrs: { placeholder: UNKNOWN } });
|
|
2191
|
-
controls.retention = retention.input; controls.auditDestination = auditDestination.input;
|
|
2192
|
-
auditFieldset.append(retention.label, auditDestination.label);
|
|
2193
|
-
form.append(defaultFieldset, safetyFieldset, notificationFieldset, auditFieldset);
|
|
2194
|
-
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
2195
|
-
const saveTarget = settingsSaveTarget();
|
|
2196
|
-
actions.append(button(document, 'Mana設定を保存', () => saveManaSettings(settingsDraftFromFields(data, controls)), { className: 'outcome-mana-button outcome-mana-button-primary', disabled: state.manaSettings.status !== 'ready' || state.manaSettings.saveStatus === 'saving' || !saveTarget || !canManage(currentSession()) }));
|
|
2197
|
-
if (!saveTarget) actions.append(createElement(document, 'small', { className: 'outcome-mana-helper', text: '設定保存経路は未確認です。読み取り専用のまま保持しています。' }));
|
|
2198
|
-
if (state.manaSettings.saveStatus === 'saved_unverified') actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '保存応答は受け取りましたが、設定のreadbackを確認できていません。', attrs: { role: 'alert' } }));
|
|
2199
|
-
if (state.manaSettings.saveStatus === 'error_retryable') actions.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: state.manaSettings.saveError?.message ?? '設定を保存できませんでした。', attrs: { role: 'alert' } }));
|
|
2200
|
-
form.append(actions);
|
|
2201
|
-
const rail = createElement(document, 'aside', { className: 'outcome-mana-settings-rail', attrs: { 'aria-label': 'Mana設定の適用範囲' } });
|
|
2202
|
-
rail.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '適用範囲' }), createElement(document, 'h3', { text: 'runtime全体' }));
|
|
2203
|
-
rail.append(definitionList(document, [['取得状態', state.manaSettings.status === 'ready' ? '取得済み' : UNKNOWN], ['保存状態', state.manaSettings.saveStatus === 'ready' ? '保存済み・readback確認済み' : state.manaSettings.saveStatus === 'saved_unverified' ? '保存済み・確認待ち' : UNKNOWN], ['実効権限', '委任契約の版で確定'], ['外部作用', '委任単位の許可範囲に従う']]));
|
|
2204
|
-
rail.append(createElement(document, 'p', { className: 'outcome-mana-safe-note', text: 'runtimeの既定値は初期値です。実効権限、利用可能な資源、外部作用は委任契約の版が所有します。' }));
|
|
2205
|
-
const layout = createElement(document, 'div', { className: 'outcome-mana-settings-layout' });
|
|
2206
|
-
layout.append(form, rail);
|
|
2207
|
-
view.append(layout); return view;
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
function renderStageNavigator() {
|
|
2211
|
-
const nav = createElement(document, 'nav', { className: 'outcome-mana-stage-nav', attrs: { 'aria-label': 'Manaの設定工程' } });
|
|
2212
|
-
const list = createElement(document, 'ol', { className: 'outcome-mana-stage-list' });
|
|
2213
|
-
const activeStage = derivedActiveStage();
|
|
2214
|
-
for (const [index, stage] of WORKFLOW_STAGES.entries()) {
|
|
2215
|
-
const item = createElement(document, 'li', { className: `outcome-mana-stage-item${activeStage === stage.id ? ' is-active' : ''}` });
|
|
2216
|
-
const status = stageStatus(stage.id);
|
|
2217
|
-
const stageButton = button(document, '', () => { setActiveStage(stage.id); render(); }, {
|
|
2218
|
-
className: 'outcome-mana-stage-button',
|
|
2219
|
-
attrs: {
|
|
2220
|
-
'aria-current': activeStage === stage.id ? 'step' : 'false',
|
|
2221
|
-
'aria-controls': 'outcome-mana-stage-workspace',
|
|
2222
|
-
'aria-label': `${stage.label} / ${statusLabel(status)}`,
|
|
2223
|
-
},
|
|
2224
|
-
});
|
|
2225
|
-
const stageCopy = createElement(document, 'span', { className: 'outcome-mana-stage-copy' });
|
|
2226
|
-
stageCopy.append(
|
|
2227
|
-
createElement(document, 'strong', { text: stage.label }),
|
|
2228
|
-
createElement(document, 'small', { text: stage.description }),
|
|
2229
|
-
);
|
|
2230
|
-
stageButton.append(
|
|
2231
|
-
createElement(document, 'span', { className: 'outcome-mana-stage-number', text: String(index + 1).padStart(2, '0'), attrs: { 'aria-hidden': 'true' } }),
|
|
2232
|
-
stageCopy,
|
|
2233
|
-
statusBadge(document, status),
|
|
2234
|
-
);
|
|
2235
|
-
item.append(stageButton);
|
|
2236
|
-
list.append(item);
|
|
2237
|
-
}
|
|
2238
|
-
nav.append(list);
|
|
2239
|
-
return nav;
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
function renderStageWorkspace() {
|
|
2243
|
-
const activeStage = derivedActiveStage();
|
|
2244
|
-
const workspace = createElement(document, 'section', {
|
|
2245
|
-
className: 'outcome-mana-stage-workspace',
|
|
2246
|
-
attrs: { id: 'outcome-mana-stage-workspace', 'aria-label': `${WORKFLOW_STAGES.find((stage) => stage.id === activeStage)?.label ?? 'Mana'}の作業領域`, 'aria-live': 'polite' },
|
|
2247
|
-
});
|
|
2248
|
-
const renderers = {
|
|
2249
|
-
contract: renderContractSection,
|
|
2250
|
-
authority: renderAuthoritySection,
|
|
2251
|
-
triggers: renderTriggersSection,
|
|
2252
|
-
safe_test: renderSafeTestSection,
|
|
2253
|
-
runs: renderRunsSection,
|
|
2254
|
-
};
|
|
2255
|
-
const panel = renderers[activeStage]?.() ?? renderContractSection();
|
|
2256
|
-
panel.className = `${panel.className} outcome-mana-stage-panel`;
|
|
2257
|
-
panel.setAttribute?.('data-stage', activeStage);
|
|
2258
|
-
workspace.append(panel);
|
|
2259
|
-
return workspace;
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
function render(target = root) {
|
|
2263
|
-
const destination = target;
|
|
2264
|
-
const activeStage = derivedActiveStage();
|
|
2265
|
-
if (!destination) {
|
|
2266
|
-
const fragment = document.createDocumentFragment?.() ?? createElement(document, 'div');
|
|
2267
|
-
fragment.append(renderStageNavigator(), renderStageWorkspace());
|
|
2268
|
-
return fragment;
|
|
2269
|
-
}
|
|
2270
|
-
clearElement(destination);
|
|
2271
|
-
const activeView = derivedActiveView();
|
|
2272
|
-
const wrapper = createElement(document, 'div', { className: 'outcome-mana-root', attrs: { 'data-project-code': currentProject().code ?? '', 'data-active-stage': activeStage, 'data-active-view': activeView } });
|
|
2273
|
-
const header = createElement(document, 'header', { className: 'outcome-mana-header' });
|
|
2274
|
-
const headerCopy = createElement(document, 'div', { className: 'outcome-mana-header-copy' });
|
|
2275
|
-
headerCopy.append(createElement(document, 'div', { className: 'outcome-mana-eyebrow', text: 'BRAINBASE / MANA' }), createElement(document, 'h1', { text: 'Manaに任せる仕事' }), createElement(document, 'p', { text: `${present(currentProject().name, 'プロジェクト未確認')} / ${present(currentProject().code, 'プロジェクトコード未確認')} / セッション: ${present(currentSession().displayName ?? currentSession().actorId, '利用者未確認')}` }));
|
|
2276
|
-
header.append(headerCopy, statusBadge(document, currentContract()?.status ?? state.contract.status));
|
|
2277
|
-
const renderers = { overview: renderOverviewView, delegation_settings: renderDelegationSettings, connections: renderConnectionsView, runs: renderRunsView, settings: renderSettingsView };
|
|
2278
|
-
wrapper.append(header, renderWorkspaceNavigation(), renderers[activeView]?.() ?? renderOverviewView());
|
|
2279
|
-
destination.append(wrapper); return destination;
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
const controller = {
|
|
2283
|
-
state,
|
|
2284
|
-
render,
|
|
2285
|
-
setActiveView: (id) => { setActiveView(id); render(); return state.activeView; },
|
|
2286
|
-
setActiveStage: (id) => { setActiveStage(id); render(); return state.activeStage; },
|
|
2287
|
-
load: loadContracts,
|
|
2288
|
-
loadContracts,
|
|
2289
|
-
loadContractDetail,
|
|
2290
|
-
loadConnections,
|
|
2291
|
-
loadManaSettings,
|
|
2292
|
-
loadTriggerCatalog,
|
|
2293
|
-
saveContractDraft,
|
|
2294
|
-
cloneContract,
|
|
2295
|
-
retireContract,
|
|
2296
|
-
loadAuthority,
|
|
2297
|
-
saveAuthority,
|
|
2298
|
-
saveTriggers,
|
|
2299
|
-
runSafeTest,
|
|
2300
|
-
activate,
|
|
2301
|
-
stopActivation,
|
|
2302
|
-
loadRuns,
|
|
2303
|
-
loadRunDetail,
|
|
2304
|
-
saveManaSettings,
|
|
2305
|
-
approveRun: (id, input = {}) => runAction('approve', input, id),
|
|
2306
|
-
rejectRun: (id, input = {}) => runAction('reject', input, id),
|
|
2307
|
-
retryRun: (id, input = {}) => runAction('retry', input, id),
|
|
2308
|
-
stopRun: (id, input = {}) => runAction('stop', input, id),
|
|
2309
|
-
paths: currentPaths,
|
|
2310
|
-
};
|
|
2311
|
-
if (root && options.autoLoad !== false) {
|
|
2312
|
-
render();
|
|
2313
|
-
if (typeof api === 'function') loadContracts();
|
|
2314
|
-
} else if (root) render();
|
|
2315
|
-
return controller;
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
|
-
export function renderManaOutcome(target, options = {}) {
|
|
2319
|
-
return createManaOutcomeUI({ ...options, root: target, autoLoad: options.autoLoad ?? false }).render();
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
export const createOutcomeManaUI = createManaOutcomeUI;
|
|
2323
|
-
export const renderOutcomeMana = renderManaOutcome;
|
|
2324
|
-
export { authorityLabel, triggerLabel, statusLabel, normalizeError, isReadbackVerified };
|
|
2325
|
-
export default createManaOutcomeUI;
|