@unson/brainbase-mcp 0.6.0 → 0.8.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.
Files changed (148) hide show
  1. package/contracts/foundation/graph-history.sql +634 -0
  2. package/contracts/judgment-value-proof/schema.json +43 -1
  3. package/dist/canonical-task-contract.d.ts +9 -0
  4. package/dist/canonical-task-contract.js +39 -0
  5. package/dist/canonical-task-principal.d.ts +16 -0
  6. package/dist/canonical-task-principal.js +36 -0
  7. package/dist/canonical-task-service.d.ts +251 -0
  8. package/dist/canonical-task-service.js +820 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +107 -0
  11. package/dist/company-os-evaluation.d.ts +193 -0
  12. package/dist/company-os-evaluation.js +1150 -0
  13. package/dist/company-os-impact-review.d.ts +248 -0
  14. package/dist/company-os-impact-review.js +663 -0
  15. package/dist/company-os-learning-adoption.d.ts +288 -0
  16. package/dist/company-os-learning-adoption.js +944 -0
  17. package/dist/company-os-objectives.d.ts +63 -0
  18. package/dist/company-os-objectives.js +234 -0
  19. package/dist/company-os-receipt-adapter.d.ts +109 -0
  20. package/dist/company-os-receipt-adapter.js +636 -0
  21. package/dist/constraint-exception-store.d.ts +23 -0
  22. package/dist/constraint-exception-store.js +108 -0
  23. package/dist/constraint-resolution.d.ts +175 -0
  24. package/dist/constraint-resolution.js +922 -0
  25. package/dist/decision-adapter.d.ts +145 -0
  26. package/dist/decision-adapter.js +586 -0
  27. package/dist/durable-wait-http.d.ts +51 -0
  28. package/dist/durable-wait-http.js +497 -0
  29. package/dist/durable-waits.d.ts +234 -0
  30. package/dist/durable-waits.js +944 -0
  31. package/dist/execution-authority-http.d.ts +28 -0
  32. package/dist/execution-authority-http.js +359 -0
  33. package/dist/execution-authority.d.ts +201 -0
  34. package/dist/execution-authority.js +634 -0
  35. package/dist/foundation-authenticated-tools.d.ts +56 -0
  36. package/dist/foundation-authenticated-tools.js +347 -0
  37. package/dist/foundation-catalog.d.ts +26 -0
  38. package/dist/foundation-catalog.js +206 -0
  39. package/dist/foundation-constraint-store.d.ts +50 -0
  40. package/dist/foundation-constraint-store.js +215 -0
  41. package/dist/foundation-graph-http.d.ts +43 -0
  42. package/dist/foundation-graph-http.js +243 -0
  43. package/dist/foundation-graph-write.d.ts +90 -0
  44. package/dist/foundation-graph-write.js +447 -0
  45. package/dist/foundation-http.d.ts +90 -0
  46. package/dist/foundation-http.js +439 -0
  47. package/dist/foundation-public-provider.d.ts +140 -0
  48. package/dist/foundation-public-provider.js +192 -0
  49. package/dist/foundation-store.d.ts +132 -0
  50. package/dist/foundation-store.js +654 -0
  51. package/dist/graph-corrections.d.ts +157 -0
  52. package/dist/graph-corrections.js +620 -0
  53. package/dist/graph-foundation-reader.d.ts +60 -0
  54. package/dist/graph-foundation-reader.js +494 -0
  55. package/dist/graph-retrieval.d.ts +6 -1
  56. package/dist/graph-revision-reader.d.ts +80 -0
  57. package/dist/graph-revision-reader.js +246 -0
  58. package/dist/graph-web-http.d.ts +26 -0
  59. package/dist/graph-web-http.js +215 -0
  60. package/dist/graph-web.d.ts +364 -0
  61. package/dist/graph-web.js +639 -0
  62. package/dist/judgment-dag-composition.d.ts +224 -0
  63. package/dist/judgment-dag-composition.js +731 -0
  64. package/dist/judgment-dag.d.ts +2 -0
  65. package/dist/judgment-dag.js +1 -0
  66. package/dist/judgment-problem-snapshot.d.ts +213 -0
  67. package/dist/judgment-problem-snapshot.js +1073 -0
  68. package/dist/judgment-value-proof-review.d.ts +166 -0
  69. package/dist/judgment-value-proof-review.js +437 -0
  70. package/dist/judgment-value-proof.d.ts +28 -1
  71. package/dist/judgment-value-proof.js +53 -8
  72. package/dist/judgment-view-http.d.ts +22 -0
  73. package/dist/judgment-view-http.js +128 -0
  74. package/dist/judgment-view.d.ts +222 -0
  75. package/dist/judgment-view.js +627 -0
  76. package/dist/knowledge-adapter.d.ts +112 -0
  77. package/dist/knowledge-adapter.js +691 -0
  78. package/dist/knowledge-continuation.d.ts +127 -0
  79. package/dist/knowledge-continuation.js +446 -0
  80. package/dist/knowledge-lookup.d.ts +178 -0
  81. package/dist/knowledge-lookup.js +758 -0
  82. package/dist/local-web-fetch-bridge.d.ts +5 -0
  83. package/dist/local-web-fetch-bridge.js +37 -0
  84. package/dist/local-web-host.d.ts +109 -0
  85. package/dist/local-web-host.js +688 -0
  86. package/dist/local-web-security.d.ts +25 -0
  87. package/dist/local-web-security.js +95 -0
  88. package/dist/ontology-foundation.d.ts +209 -0
  89. package/dist/ontology-foundation.js +1000 -0
  90. package/dist/ontology.d.ts +13 -0
  91. package/dist/ontology.js +14 -0
  92. package/dist/organization-connection.d.ts +164 -0
  93. package/dist/organization-connection.js +428 -0
  94. package/dist/organization-graph.d.ts +15 -0
  95. package/dist/organization-graph.js +24 -4
  96. package/dist/personal-knowledge.d.ts +132 -0
  97. package/dist/personal-knowledge.js +779 -0
  98. package/dist/personal-memory-handover.d.ts +108 -0
  99. package/dist/personal-memory-handover.js +369 -0
  100. package/dist/philosophy-revision-reader.d.ts +85 -0
  101. package/dist/philosophy-revision-reader.js +241 -0
  102. package/dist/portable-graph.d.ts +24 -0
  103. package/dist/portable-graph.js +77 -0
  104. package/dist/problem-candidates.d.ts +149 -0
  105. package/dist/problem-candidates.js +898 -0
  106. package/dist/problem-selection.d.ts +238 -0
  107. package/dist/problem-selection.js +995 -0
  108. package/dist/resource-reservation-http.d.ts +31 -0
  109. package/dist/resource-reservation-http.js +371 -0
  110. package/dist/resource-reservations.d.ts +221 -0
  111. package/dist/resource-reservations.js +577 -0
  112. package/dist/server.d.ts +104 -2
  113. package/dist/server.js +186 -4
  114. package/dist/ssot.d.ts +6 -1
  115. package/dist/ssot.js +63 -5
  116. package/dist/streamable-http.d.ts +42 -0
  117. package/dist/streamable-http.js +288 -0
  118. package/dist/types.d.ts +20 -0
  119. package/dist/value-proof-review-http.d.ts +25 -0
  120. package/dist/value-proof-review-http.js +185 -0
  121. package/dist/world-model.d.ts +199 -0
  122. package/dist/world-model.js +955 -0
  123. package/package.json +230 -3
  124. package/ui/brainbase-tokens.css +56 -0
  125. package/ui/graph-own-share.js +367 -0
  126. package/ui/graph-projects-view.css +23 -0
  127. package/ui/graph-projects-view.js +730 -0
  128. package/ui/graph-registry-view.css +101 -0
  129. package/ui/graph-registry-view.js +804 -0
  130. package/ui/graph-view-shared.css +522 -0
  131. package/ui/graph-view-shared.js +1227 -0
  132. package/ui/judgment-view.css +312 -0
  133. package/ui/judgment-view.js +508 -0
  134. package/ui/local-web-shell.css +396 -0
  135. package/ui/local-web-shell.js +378 -0
  136. package/ui/objective-editor-http-port.js +154 -0
  137. package/ui/objective-editor.css +286 -0
  138. package/ui/objective-editor.js +1586 -0
  139. package/ui/outcome-knowledge.css +50 -14
  140. package/ui/outcome-knowledge.js +15 -1
  141. package/ui/outcome-mana.css +91 -14
  142. package/ui/outcome-mana.js +958 -61
  143. package/ui/value-proof-review.css +504 -0
  144. package/ui/value-proof-review.js +1104 -0
  145. package/ui/workspace-kit.css +485 -0
  146. package/ui/workspace-kit.js +229 -0
  147. package/ui/world-model-view.css +188 -0
  148. package/ui/world-model-view.js +486 -0
@@ -0,0 +1,188 @@
1
+ /*
2
+ * Read-only World Model view (現状と見通し), on the organization edition's
3
+ * screen pattern: a section title under the page head (workspace-kit.css),
4
+ * then one read-only ledger per block. Uses only the shared tokens in
5
+ * brainbase-tokens.css.
6
+ */
7
+ .bb-wm {
8
+ display: block;
9
+ min-width: 0;
10
+ color: var(--bb-color-ink);
11
+ font-family: var(--bb-font-sans);
12
+ line-height: 1.5;
13
+ }
14
+
15
+ .bb-wm *,
16
+ .bb-wm *::before,
17
+ .bb-wm *::after {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ .bb-wm h3,
22
+ .bb-wm p {
23
+ margin: 0;
24
+ }
25
+
26
+ .bb-wm > .bb-ws-section-title {
27
+ margin-top: var(--bb-space-4);
28
+ }
29
+
30
+ /* Blocks: a heading, a lead, state notices and the ledger. */
31
+ .bb-wm-block {
32
+ display: grid;
33
+ gap: var(--bb-space-2);
34
+ min-width: 0;
35
+ margin-top: var(--bb-space-6);
36
+ }
37
+
38
+ .bb-wm-heading {
39
+ color: var(--bb-color-ink);
40
+ font-size: 15px;
41
+ }
42
+
43
+ .bb-wm-lead {
44
+ margin-bottom: var(--bb-space-2);
45
+ color: var(--bb-color-muted);
46
+ font-size: 13px;
47
+ }
48
+
49
+ .bb-wm-block > .bb-ws-notice {
50
+ margin: 0;
51
+ }
52
+
53
+ .bb-wm-notice-body {
54
+ display: grid;
55
+ gap: var(--bb-space-2);
56
+ min-width: 0;
57
+ }
58
+
59
+ .bb-wm-notice-body .bb-ws-button {
60
+ justify-self: start;
61
+ }
62
+
63
+ /* The local shell resets `.bb-shell button { font: inherit }`, which outranks the kit's button type. */
64
+ .bb-wm .bb-ws-button {
65
+ font-size: 12px;
66
+ font-weight: 680;
67
+ }
68
+
69
+ /* Rows wrap: a reference view keeps its details readable instead of cutting them. */
70
+ .bb-wm .bb-ws-ledger-row:not(.bb-ws-ledger-head) {
71
+ align-items: start;
72
+ }
73
+
74
+ .bb-wm .bb-ws-ledger-row > span {
75
+ white-space: normal;
76
+ overflow-wrap: anywhere;
77
+ }
78
+
79
+ .bb-wm-cell {
80
+ display: grid;
81
+ gap: 2px;
82
+ min-width: 0;
83
+ }
84
+
85
+ .bb-wm-main {
86
+ color: var(--bb-color-ink);
87
+ font-size: 13px;
88
+ }
89
+
90
+ .bb-wm-cell small {
91
+ color: var(--bb-color-muted);
92
+ font-size: 11px;
93
+ }
94
+
95
+ .bb-wm-cell small.is-warning {
96
+ color: var(--bb-color-warning);
97
+ font-weight: 650;
98
+ }
99
+
100
+ .bb-wm-cell small.is-accent {
101
+ color: var(--bb-color-accent-strong);
102
+ font-weight: 650;
103
+ }
104
+
105
+ .bb-wm-cell.is-value .bb-wm-main,
106
+ .bb-wm-view-ledger .bb-ws-ledger-row > span:first-child .bb-wm-main {
107
+ font-weight: 690;
108
+ }
109
+
110
+ .bb-wm-cell.is-verified .bb-wm-main,
111
+ .bb-wm-cell.is-supported .bb-wm-main,
112
+ .bb-wm-cell.is-approved .bb-wm-main {
113
+ color: var(--bb-color-accent-strong);
114
+ font-weight: 690;
115
+ }
116
+
117
+ .bb-wm-cell.is-hypothesis .bb-wm-main,
118
+ .bb-wm-cell.is-unverified .bb-wm-main,
119
+ .bb-wm-cell.is-pending .bb-wm-main {
120
+ color: var(--bb-color-warning);
121
+ font-weight: 690;
122
+ }
123
+
124
+ .bb-wm-cell.is-refuted .bb-wm-main {
125
+ color: var(--bb-color-danger);
126
+ font-weight: 690;
127
+ }
128
+
129
+ .bb-wm-cell.is-unresolved .bb-wm-main {
130
+ color: var(--bb-color-quiet);
131
+ }
132
+
133
+ .bb-wm-code {
134
+ color: var(--bb-color-muted);
135
+ font-family: var(--bb-font-mono);
136
+ font-size: 11px;
137
+ }
138
+
139
+ .bb-wm .bb-ws-ledger-row.is-superseded {
140
+ background: var(--bb-color-surface);
141
+ }
142
+
143
+ .bb-wm .bb-ws-ledger-row.is-superseded .bb-wm-cell.is-value .bb-wm-main {
144
+ color: var(--bb-color-muted);
145
+ text-decoration: line-through;
146
+ }
147
+
148
+ /* Column templates. */
149
+ .bb-wm-view-ledger .bb-ws-ledger-row {
150
+ grid-template-columns: minmax(180px, 2.2fr) minmax(84px, .8fr) 84px minmax(96px, .9fr);
151
+ }
152
+
153
+ .bb-wm-observation-ledger .bb-ws-ledger-row {
154
+ grid-template-columns: minmax(120px, 1.3fr) minmax(96px, 1fr) minmax(128px, 1.3fr) 104px minmax(110px, 1.1fr);
155
+ }
156
+
157
+ .bb-wm-adoption-ledger .bb-ws-ledger-row {
158
+ grid-template-columns: minmax(140px, 1.4fr) 72px minmax(140px, 1.6fr) 56px minmax(84px, .8fr) 104px;
159
+ }
160
+
161
+ @media (max-width: 760px) {
162
+ .bb-wm-view-ledger .bb-ws-ledger-row {
163
+ grid-template-columns: minmax(0, 1fr) 72px;
164
+ }
165
+
166
+ .bb-wm-view-ledger .bb-ws-ledger-row > span:nth-child(2),
167
+ .bb-wm-view-ledger .bb-ws-ledger-row > span:nth-child(4) {
168
+ display: none;
169
+ }
170
+
171
+ .bb-wm-observation-ledger .bb-ws-ledger-row {
172
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
173
+ }
174
+
175
+ .bb-wm-observation-ledger .bb-ws-ledger-row > span:nth-child(3),
176
+ .bb-wm-observation-ledger .bb-ws-ledger-row > span:nth-child(4),
177
+ .bb-wm-observation-ledger .bb-ws-ledger-row > span:nth-child(5) {
178
+ display: none;
179
+ }
180
+
181
+ .bb-wm-adoption-ledger .bb-ws-ledger-row {
182
+ grid-template-columns: minmax(0, 1fr) 72px;
183
+ }
184
+
185
+ .bb-wm-adoption-ledger .bb-ws-ledger-row > span:nth-child(n + 3) {
186
+ display: none;
187
+ }
188
+ }
@@ -0,0 +1,486 @@
1
+ /*
2
+ * Read-only World Model view (現状と見通し).
3
+ *
4
+ * Shows recorded observations apart from the variables and models that are
5
+ * the owner's way of seeing them (which may be hypotheses), plus model
6
+ * adoptions. The host injects the fetcher and base path; this module keeps
7
+ * no global state and never writes. Each section loads on its own: a failed
8
+ * or unverifiable section is reported in place and never shown as zero items.
9
+ *
10
+ * Drawn with the organization edition's screen pattern (workspace-kit): a
11
+ * section title under the page head, then one read-only ledger per block.
12
+ * Rows are not selectable, so the view never writes to the host's rail.
13
+ *
14
+ * A section the host cannot serve answers `{ status: 'unavailable', reason }`
15
+ * (HTTP 200). It is shown as unavailable, never as zero items; a host with no
16
+ * World Model source at all can pass `unavailableNotice` to show one notice
17
+ * with its own copy instead of four.
18
+ */
19
+
20
+ import { workspaceButton, workspaceLedger, workspaceNotice, workspaceSectionTitle } from './workspace-kit.js';
21
+
22
+ export const WORLD_MODEL_VIEW_CONTRACT_VERSION = 'brainbase.world-model-view.v1';
23
+
24
+ export const WORLD_MODEL_SECTIONS = Object.freeze(['variables', 'models', 'observations', 'adoptions']);
25
+
26
+ const SECTION_KEYS = Object.freeze({ variables: 'records', models: 'records', observations: 'observations', adoptions: 'adoptions' });
27
+
28
+ export const EPISTEMIC_STATE_LABELS = Object.freeze({
29
+ unverified: '未検証',
30
+ hypothesis: '仮説',
31
+ supported: '支持',
32
+ verified: '検証済み',
33
+ refuted: '反証',
34
+ });
35
+ const ADOPTION_LABELS = Object.freeze({ draft: '下書き', proposed: '提案中', approved: '承認済み', retired: '終了' });
36
+ const VALIDATION_LABELS = Object.freeze({ unverified: '未検証', in_progress: '検証中', supported: '支持', verified: '検証済み', refuted: '反証' });
37
+ const SOURCE_KIND_LABELS = Object.freeze({ candidate: '候補', document: '文書', observation: '観測', decision: '判断', import: '取り込み' });
38
+ const USE_LABELS = Object.freeze({ draft: '下書き', judgment: '判断', evaluation: '評価', execution: '実行' });
39
+ const UNREADABLE_LABELS = Object.freeze({
40
+ authorization_denied: '読む権限がない',
41
+ scope_violation: '扱える範囲の外',
42
+ invalid_input: '変数の定義と合わない',
43
+ not_found: '参照先が見つからない',
44
+ });
45
+
46
+ function getDocument(explicit) {
47
+ const value = explicit ?? (typeof document === 'undefined' ? null : document);
48
+ if (!value || typeof value.createElement !== 'function') throw new Error('document_unavailable');
49
+ return value;
50
+ }
51
+
52
+ function makeElement(doc, tag, { className, text, attrs = {} } = {}) {
53
+ const element = doc.createElement(tag);
54
+ if (className) element.className = className;
55
+ if (text !== undefined && text !== null) element.textContent = String(text);
56
+ for (const [name, value] of Object.entries(attrs)) {
57
+ if (value === undefined || value === null || value === false) continue;
58
+ element.setAttribute(name, value === true ? '' : String(value));
59
+ }
60
+ return element;
61
+ }
62
+
63
+ function isRecord(value) {
64
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
65
+ }
66
+
67
+ function text(value) {
68
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
69
+ }
70
+
71
+ function formatDateTime(value) {
72
+ const time = typeof value === 'string' ? Date.parse(value) : Number.NaN;
73
+ if (Number.isNaN(time)) return '日時不明';
74
+ const date = new Date(time);
75
+ const pad = (number) => String(number).padStart(2, '0');
76
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
77
+ }
78
+
79
+ function formatValue(value, unit) {
80
+ const rendered = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
81
+ ? String(value)
82
+ : JSON.stringify(value);
83
+ return unit ? `${rendered} ${unit}` : rendered;
84
+ }
85
+
86
+ function isRevisionRef(value) {
87
+ return isRecord(value) && typeof value.id === 'string' && typeof value.revision === 'string';
88
+ }
89
+
90
+ function isDefinitionRecord(value, type) {
91
+ return isRecord(value) && isRecord(value.definition) && value.definition.type === type
92
+ && typeof value.definition.id === 'string' && typeof value.definition.revision === 'string';
93
+ }
94
+
95
+ function isObservation(value) {
96
+ return isRecord(value) && typeof value.id === 'string' && isRevisionRef(value.variableRef)
97
+ && typeof value.subjectId === 'string' && typeof value.occurredAt === 'string' && typeof value.recordedAt === 'string'
98
+ && isRecord(value.period) && isRecord(value.sourceRef) && 'value' in value;
99
+ }
100
+
101
+ function isAdoption(value) {
102
+ return isRecord(value) && typeof value.adoptionId === 'string' && isRevisionRef(value.modelRef)
103
+ && typeof value.adoptionState === 'string' && isRecord(value.candidate);
104
+ }
105
+
106
+ const ITEM_CHECKS = Object.freeze({
107
+ variables: (item) => isDefinitionRecord(item, 'variable'),
108
+ models: (item) => isDefinitionRecord(item, 'model'),
109
+ observations: isObservation,
110
+ adoptions: isAdoption,
111
+ });
112
+
113
+ /**
114
+ * Normalize one section response. A missing array or a malformed item is
115
+ * `invalid`; an empty array is `empty` only when the host confirmed absence.
116
+ */
117
+ export function normalizeWorldModelSection(section, payload) {
118
+ const key = SECTION_KEYS[section];
119
+ if (key && isRecord(payload) && (payload.status === 'unavailable' || payload.state === 'unavailable')) {
120
+ return { state: 'unavailable', items: null, reason: text(payload.reason) };
121
+ }
122
+ if (!key || !isRecord(payload) || !Array.isArray(payload[key])) {
123
+ return { state: 'invalid', items: null, reason: '応答の形式が不正です' };
124
+ }
125
+ const items = payload[key];
126
+ if (!items.every(ITEM_CHECKS[section])) return { state: 'invalid', items: null, reason: '応答の記録の形式が不正です' };
127
+ const unreadableCount = Number.isInteger(payload.unreadable?.count) && payload.unreadable.count > 0 ? payload.unreadable.count : 0;
128
+ const unreadableCodes = Array.isArray(payload.unreadable?.codes) ? payload.unreadable.codes.filter((code) => typeof code === 'string') : [];
129
+ const unreadable = { count: unreadableCount, codes: unreadableCodes };
130
+ if (items.length === 0) {
131
+ return payload.absence_confirmed === true && unreadableCount === 0
132
+ ? { state: 'empty', items: [], unreadable }
133
+ : { state: 'unknown', items: null, unreadable };
134
+ }
135
+ return { state: unreadableCount > 0 ? 'partial' : 'ready', items, unreadable };
136
+ }
137
+
138
+ function unreadableMessage(unreadable) {
139
+ const reasons = unreadable.codes.map((code) => UNREADABLE_LABELS[code] ?? code).join('、');
140
+ return `読めない記録が${unreadable.count}件あります${reasons ? `(${reasons})` : ''}。読めた記録だけを表示しています。`;
141
+ }
142
+
143
+ function refLabel(ref, names) {
144
+ const name = names.get(ref.id);
145
+ return name ? `${name}(${ref.id}@${ref.revision})` : `${ref.id}@${ref.revision}`;
146
+ }
147
+
148
+ /** A ledger cell with a main line and quieter detail lines (the World Model rows wrap instead of cutting text). */
149
+ function stack(doc, main, details = [], className = '') {
150
+ const cell = makeElement(doc, 'span', { className: `bb-wm-cell${className ? ` ${className}` : ''}` });
151
+ cell.append(makeElement(doc, 'span', { className: 'bb-wm-main', text: main }));
152
+ for (const detail of details) {
153
+ if (!detail) continue;
154
+ const [value, tone] = Array.isArray(detail) ? detail : [detail, ''];
155
+ cell.append(makeElement(doc, 'small', { className: tone ? `is-${tone}` : undefined, text: value }));
156
+ }
157
+ return cell;
158
+ }
159
+
160
+ /** One element for a notice body, so the kit's label column stays on the left. */
161
+ function noticeBody(doc, message, action) {
162
+ const body = makeElement(doc, 'div', { className: 'bb-wm-notice-body' });
163
+ body.append(makeElement(doc, 'p', { text: message }));
164
+ if (action) body.append(action);
165
+ return body;
166
+ }
167
+
168
+ /**
169
+ * A section's state as a notice, or nothing when its rows can be shown.
170
+ * Returns `{ notice, rows }`: rows is false when the section has none to show.
171
+ */
172
+ function sectionStatus(doc, label, section, sectionState, callbacks, emptyText) {
173
+ const { state } = sectionState;
174
+ if (state === 'loading' || state === 'idle') {
175
+ return { notice: workspaceNotice(doc, { label, text: '読み込んでいます。' }), rows: false };
176
+ }
177
+ if (state === 'approval_unverifiable') {
178
+ return {
179
+ notice: workspaceNotice(doc, { label, tone: 'warning', text: '承認を確かめられないため表示できません。承認済みの採用を確かめる仕組みがこのホストにありません。ほかの欄はそのまま使えます。' }),
180
+ rows: false,
181
+ };
182
+ }
183
+ if (state === 'error' || state === 'invalid') {
184
+ const retry = workspaceButton(doc, { text: '再試行', variant: 'quiet', onClick: () => void callbacks.onRetry?.(section) });
185
+ return {
186
+ notice: workspaceNotice(doc, { label, tone: 'danger', text: noticeBody(doc, `読み取れませんでした(${sectionState.reason ?? '理由不明'})。0件ではありません。`, retry) }),
187
+ rows: false,
188
+ };
189
+ }
190
+ if (state === 'unavailable') {
191
+ return {
192
+ notice: workspaceNotice(doc, { label, tone: 'warning', text: `このホストでは読めません(${sectionState.reason ?? '理由不明'})。0件ではありません。` }),
193
+ rows: false,
194
+ };
195
+ }
196
+ if (state === 'unknown') {
197
+ const message = sectionState.unreadable?.count
198
+ ? unreadableMessage(sectionState.unreadable)
199
+ : '記録の有無を確かめられません。0件ではありません。';
200
+ return { notice: workspaceNotice(doc, { label, tone: 'warning', text: message }), rows: false };
201
+ }
202
+ if (state === 'empty') return { notice: workspaceNotice(doc, { label, text: emptyText }), rows: false };
203
+ if (state === 'partial') return { notice: workspaceNotice(doc, { label, tone: 'warning', text: unreadableMessage(sectionState.unreadable) }), rows: true };
204
+ return { notice: null, rows: true };
205
+ }
206
+
207
+ function epistemicText(state) {
208
+ return EPISTEMIC_STATE_LABELS[state] ?? null;
209
+ }
210
+
211
+ function viewRows(doc, state, statuses, variableNames) {
212
+ const rows = [];
213
+ if (statuses.variables.rows) {
214
+ for (const record of state.variables.items) {
215
+ const definition = record.definition;
216
+ const epistemic = epistemicText(definition.epistemicState);
217
+ rows.push({
218
+ key: `variable:${definition.id}`,
219
+ cells: [
220
+ stack(doc, text(definition.meaning) ?? definition.id, [
221
+ text(definition.subject) ? `対象: ${text(definition.subject)}` : null,
222
+ text(definition.unit) ? `単位: ${text(definition.unit)}` : null,
223
+ text(definition.measurementMethod) ? `測り方: ${text(definition.measurementMethod)}` : null,
224
+ ]),
225
+ stack(doc, '変数', [ADOPTION_LABELS[definition.adoptionState] ?? (definition.adoptionState ? String(definition.adoptionState) : '採用 未記録')]),
226
+ stack(doc, epistemic ?? '未記録', [], epistemic ? `is-${definition.epistemicState}` : 'is-unresolved'),
227
+ { text: `${definition.id}@${definition.revision}`, className: 'bb-wm-code' },
228
+ ],
229
+ });
230
+ }
231
+ }
232
+ if (statuses.models.rows) {
233
+ for (const record of state.models.items) {
234
+ const definition = record.definition;
235
+ const refs = (value) => (Array.isArray(value) && value.length > 0 ? value.filter(isRevisionRef).map((ref) => refLabel(ref, variableNames)).join('、') : null);
236
+ const epistemic = epistemicText(definition.epistemicState);
237
+ rows.push({
238
+ key: `model:${definition.id}`,
239
+ cells: [
240
+ stack(doc, text(definition.meaning) ?? definition.id, [
241
+ text(definition.relationship) ? `関係: ${text(definition.relationship)}` : null,
242
+ text(definition.uncertainty) ? `不確かさ: ${text(definition.uncertainty)}` : null,
243
+ refs(definition.inputVariableRefs) ? `入力: ${refs(definition.inputVariableRefs)}` : null,
244
+ refs(definition.outputVariableRefs) ? `出力: ${refs(definition.outputVariableRefs)}` : null,
245
+ ]),
246
+ stack(doc, 'モデル', [`検証: ${VALIDATION_LABELS[definition.validationState] ?? '未記録'}`]),
247
+ stack(doc, epistemic ?? '未記録', [], epistemic ? `is-${definition.epistemicState}` : 'is-unresolved'),
248
+ { text: `${definition.id}@${definition.revision}`, className: 'bb-wm-code' },
249
+ ],
250
+ });
251
+ }
252
+ }
253
+ return rows;
254
+ }
255
+
256
+ function observationRows(doc, state, names, units) {
257
+ const correctedBy = new Map();
258
+ for (const observation of state.observations.items) {
259
+ if (typeof observation.supersedes === 'string') correctedBy.set(observation.supersedes, observation.id);
260
+ }
261
+ return state.observations.items.map((observation) => {
262
+ const newer = correctedBy.get(observation.id);
263
+ const source = observation.sourceRef;
264
+ const chain = [];
265
+ if (typeof observation.supersedes === 'string') chain.push([`観測「${observation.supersedes}」を訂正`, 'accent']);
266
+ if (newer) chain.push([`観測「${newer}」で訂正済み`, 'warning']);
267
+ return {
268
+ key: observation.id,
269
+ className: newer ? 'is-superseded' : '',
270
+ cells: [
271
+ stack(doc, observation.subjectId, [refLabel(observation.variableRef, names)]),
272
+ stack(doc, formatValue(observation.value, units.get(observation.variableRef.id)), chain, 'is-value'),
273
+ stack(doc, `${formatDateTime(observation.period.from)} 〜 ${formatDateTime(observation.period.until)}`, [`発生 ${formatDateTime(observation.occurredAt)}`]),
274
+ stack(doc, formatDateTime(observation.recordedAt)),
275
+ stack(doc, `${SOURCE_KIND_LABELS[source.sourceKind] ?? String(source.sourceKind)}(${String(source.sourceId)})`, [`観測ID ${observation.id}`]),
276
+ ],
277
+ };
278
+ });
279
+ }
280
+
281
+ function adoptionRows(doc, state, modelNames) {
282
+ return state.adoptions.items.map((adoption) => {
283
+ const candidate = adoption.candidate;
284
+ return {
285
+ key: adoption.adoptionId,
286
+ cells: [
287
+ stack(doc, refLabel(adoption.modelRef, modelNames)),
288
+ stack(doc, ADOPTION_LABELS[adoption.adoptionState] ?? String(adoption.adoptionState), [], adoption.adoptionState === 'approved' ? 'is-approved' : 'is-pending'),
289
+ stack(doc, text(candidate.hypothesis) ?? '仮説の記録なし', [
290
+ EPISTEMIC_STATE_LABELS[candidate.epistemicState] ? `候補の認識: ${EPISTEMIC_STATE_LABELS[candidate.epistemicState]}` : null,
291
+ Array.isArray(candidate.evidenceIds) ? `証拠 ${candidate.evidenceIds.length}件` : null,
292
+ ]),
293
+ stack(doc, USE_LABELS[adoption.authorizedUse] ?? text(adoption.authorizedUse) ?? '未記録'),
294
+ stack(doc, isRevisionRef(adoption.approvalRef) ? `${adoption.approvalRef.id}@${adoption.approvalRef.revision}` : 'なし'),
295
+ stack(doc, formatDateTime(adoption.adoptedAt)),
296
+ ],
297
+ };
298
+ });
299
+ }
300
+
301
+ function namesOf(section) {
302
+ const names = new Map();
303
+ const units = new Map();
304
+ for (const record of section.items ?? []) {
305
+ const definition = record.definition;
306
+ if (text(definition.meaning)) names.set(definition.id, text(definition.meaning));
307
+ if (text(definition.unit)) units.set(definition.id, text(definition.unit));
308
+ }
309
+ return { names, units };
310
+ }
311
+
312
+ function block(doc, label, heading, lead) {
313
+ const section = makeElement(doc, 'section', { className: 'bb-wm-block', attrs: { 'aria-label': label } });
314
+ section.append(makeElement(doc, 'h3', { className: 'bb-wm-heading', text: heading }), makeElement(doc, 'p', { className: 'bb-wm-lead', text: lead }));
315
+ return section;
316
+ }
317
+
318
+ /**
319
+ * The host's single notice when no section can be served: its title and
320
+ * guidance, then the reasons the sections gave. Null when the host passed no
321
+ * copy or when any section is readable, loading or failed in another way.
322
+ */
323
+ function unavailableSummary(doc, state, unavailableNotice) {
324
+ if (!isRecord(unavailableNotice) || !text(unavailableNotice.title)) return null;
325
+ if (!WORLD_MODEL_SECTIONS.every((section) => state[section]?.state === 'unavailable')) return null;
326
+ const labels = isRecord(unavailableNotice.reasonLabels) ? unavailableNotice.reasonLabels : {};
327
+ const reasonText = (reason) => (Object.hasOwn(labels, reason) && text(labels[reason])) || reason;
328
+ const reasons = [...new Set(WORLD_MODEL_SECTIONS.map((section) => state[section].reason).filter(Boolean).map(reasonText))];
329
+ const body = makeElement(doc, 'div', { className: 'bb-wm-notice-body' });
330
+ if (text(unavailableNotice.guidance)) body.append(makeElement(doc, 'p', { text: text(unavailableNotice.guidance) }));
331
+ body.append(makeElement(doc, 'p', { text: `理由: ${reasons.length > 0 ? reasons.join('、') : '理由不明'}。0件ではありません。` }));
332
+ return workspaceNotice(doc, { label: text(unavailableNotice.title), text: body, tone: 'info' });
333
+ }
334
+
335
+ /**
336
+ * Render the whole view into a host-owned root: a section title and read-only ledgers.
337
+ * `options.unavailableNotice` is the host's `{ title, guidance }` shown once
338
+ * when every section is unavailable.
339
+ */
340
+ export function renderWorldModelView(root, state, callbacks = {}, options = {}) {
341
+ const doc = getDocument(options.document);
342
+ root.replaceChildren();
343
+ const surface = makeElement(doc, 'section', { className: 'bb-wm', attrs: { 'data-contract-version': WORLD_MODEL_VIEW_CONTRACT_VERSION, 'aria-label': '現状と見通し' } });
344
+ surface.append(workspaceSectionTitle(doc, {
345
+ title: '現状と見通し',
346
+ lead: '見方(変数とモデル。仮説を含みます)と、記録された観測を分けて表示します。この欄は表示だけです。',
347
+ }));
348
+ const unavailable = unavailableSummary(doc, state, options.unavailableNotice);
349
+ if (unavailable) {
350
+ surface.append(unavailable);
351
+ root.append(surface);
352
+ return surface;
353
+ }
354
+
355
+ const variables = namesOf(state.variables);
356
+ const models = namesOf(state.models);
357
+
358
+ const view = block(doc, '変数とモデル', '見方:変数とモデル', '何を測り、どう関係すると考えているか。認識の状態は、確かめた度合いです。観測ではありません。');
359
+ const statuses = {
360
+ variables: sectionStatus(doc, '変数', 'variables', state.variables, callbacks, '変数はまだ登録がありません。'),
361
+ models: sectionStatus(doc, 'モデル', 'models', state.models, callbacks, 'モデルはまだ登録がありません。'),
362
+ };
363
+ for (const status of Object.values(statuses)) if (status.notice) view.append(status.notice);
364
+ const rows = viewRows(doc, state, statuses, variables.names);
365
+ if (rows.length > 0) {
366
+ view.append(workspaceLedger(doc, { className: 'bb-wm-view-ledger', ariaLabel: '変数とモデル', columns: ['名前', '種類', '認識の状態', '版'], rows }));
367
+ }
368
+ surface.append(view);
369
+
370
+ const observed = block(doc, '観測', '観測:記録された値', '実際に記録された値です。推定や予測は含みません。訂正は新しい観測として残り、元の観測との関係を示します。');
371
+ const observedStatus = sectionStatus(doc, '観測', 'observations', state.observations, callbacks, '観測はまだ記録がありません。');
372
+ if (observedStatus.notice) observed.append(observedStatus.notice);
373
+ if (observedStatus.rows) {
374
+ observed.append(workspaceLedger(doc, {
375
+ className: 'bb-wm-observation-ledger',
376
+ ariaLabel: '観測',
377
+ columns: ['対象', '値', '期間', '記録', '出典'],
378
+ rows: observationRows(doc, state, variables.names, variables.units),
379
+ }));
380
+ }
381
+ surface.append(observed);
382
+
383
+ const adopted = block(doc, 'モデルの採用', 'モデルの採用', 'どのモデルを、どの根拠で判断などに使うことにしたか。');
384
+ const adoptedStatus = sectionStatus(doc, '採用', 'adoptions', state.adoptions, callbacks, 'モデルの採用はまだありません。');
385
+ if (adoptedStatus.notice) adopted.append(adoptedStatus.notice);
386
+ if (adoptedStatus.rows) {
387
+ adopted.append(workspaceLedger(doc, {
388
+ className: 'bb-wm-adoption-ledger',
389
+ ariaLabel: 'モデルの採用',
390
+ columns: ['モデル', '状態', '根拠', '用途', '承認', '日時'],
391
+ rows: adoptionRows(doc, state, models.names),
392
+ }));
393
+ }
394
+ surface.append(adopted);
395
+
396
+ root.append(surface);
397
+ return surface;
398
+ }
399
+
400
+ async function readError(response) {
401
+ try {
402
+ const body = await response.json();
403
+ return { code: text(body?.error?.code), message: text(body?.error?.message) };
404
+ } catch {
405
+ return { code: null, message: null };
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Mounts the read-only World Model view.
411
+ *
412
+ * @param {object} options
413
+ * @param {Element} options.root Where the view is drawn.
414
+ * @param {Element} [options.rail] Accepted for the host's screen contract; the view never writes to it.
415
+ * @param {Document} [options.document] The document to build elements with (default: the global one).
416
+ * @param {Function} [options.fetcher] `fetch`-compatible function for the sections.
417
+ * @param {string} [options.basePath='/api/world-model'] Where `{variables,models,observations,adoptions}` are served.
418
+ * @param {boolean} [options.autoLoad=true] Reads every section on mount.
419
+ * @param {{ title: string, guidance?: string, reasonLabels?: Record<string, string> }} [options.unavailableNotice]
420
+ * When every section is unavailable (for example the host has no World Model source), one notice
421
+ * with this title (the notice's short label) and guidance, plus the reasons, replaces the blocks.
422
+ * `reasonLabels` says a reason code in the host's words. Without it each block reports its own
423
+ * state, as by default.
424
+ * @returns The controller: `state`, `render()`, `load()`, `loadSection(section)`.
425
+ */
426
+ export function createWorldModelView({
427
+ root,
428
+ /** Accepted for the host's screen contract; the read-only view keeps the rail to the screen's editor. */
429
+ rail: _rail,
430
+ document: explicitDocument,
431
+ fetcher,
432
+ basePath = '/api/world-model',
433
+ autoLoad = true,
434
+ unavailableNotice,
435
+ } = {}) {
436
+ if (!root) throw new TypeError('root is required');
437
+ const doc = getDocument(explicitDocument);
438
+ const request = typeof fetcher === 'function'
439
+ ? fetcher
440
+ : typeof globalThis.fetch === 'function' ? (path, init) => globalThis.fetch(path, init) : null;
441
+ const base = String(basePath).replace(/\/+$/u, '');
442
+ const state = Object.fromEntries(WORLD_MODEL_SECTIONS.map((section) => [section, { state: 'loading', items: null }]));
443
+
444
+ const callbacks = { onRetry: (section) => controller.loadSection(section) };
445
+
446
+ const controller = {
447
+ get state() { return state; },
448
+ render() {
449
+ renderWorldModelView(root, state, callbacks, { document: doc, unavailableNotice });
450
+ return controller;
451
+ },
452
+ async loadSection(section) {
453
+ state[section] = { state: 'loading', items: null };
454
+ controller.render();
455
+ if (!request) {
456
+ state[section] = { state: 'error', items: null, reason: '取得先が設定されていません' };
457
+ controller.render();
458
+ return state[section];
459
+ }
460
+ try {
461
+ const response = await request(`${base}/${section}`);
462
+ if (!response.ok) {
463
+ const error = await readError(response);
464
+ state[section] = error.code === 'approval_reference_unresolved'
465
+ ? { state: 'approval_unverifiable', items: null }
466
+ : { state: 'error', items: null, reason: error.message ?? error.code ?? `HTTP ${response.status}` };
467
+ } else {
468
+ state[section] = normalizeWorldModelSection(section, await response.json());
469
+ }
470
+ } catch (error) {
471
+ state[section] = { state: 'error', items: null, reason: error instanceof Error ? error.message : 'request_failed' };
472
+ }
473
+ controller.render();
474
+ return state[section];
475
+ },
476
+ async load() {
477
+ await Promise.all(WORLD_MODEL_SECTIONS.map((section) => controller.loadSection(section)));
478
+ return state;
479
+ },
480
+ };
481
+ controller.render();
482
+ if (autoLoad) void controller.load();
483
+ return controller;
484
+ }
485
+
486
+ export default createWorldModelView;