@unson/brainbase-mcp 0.7.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 (83) 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-service.js +16 -5
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +107 -0
  6. package/dist/company-os-objectives.d.ts +11 -0
  7. package/dist/company-os-objectives.js +84 -47
  8. package/dist/foundation-authenticated-tools.d.ts +56 -0
  9. package/dist/foundation-authenticated-tools.js +347 -0
  10. package/dist/foundation-graph-http.d.ts +43 -0
  11. package/dist/foundation-graph-http.js +243 -0
  12. package/dist/foundation-graph-write.d.ts +90 -0
  13. package/dist/foundation-graph-write.js +447 -0
  14. package/dist/foundation-public-provider.d.ts +140 -0
  15. package/dist/foundation-public-provider.js +192 -0
  16. package/dist/graph-corrections.d.ts +157 -0
  17. package/dist/graph-corrections.js +620 -0
  18. package/dist/graph-foundation-reader.d.ts +60 -0
  19. package/dist/graph-foundation-reader.js +494 -0
  20. package/dist/graph-retrieval.d.ts +6 -1
  21. package/dist/graph-revision-reader.d.ts +80 -0
  22. package/dist/graph-revision-reader.js +246 -0
  23. package/dist/graph-web-http.d.ts +26 -0
  24. package/dist/graph-web-http.js +215 -0
  25. package/dist/graph-web.d.ts +364 -0
  26. package/dist/graph-web.js +639 -0
  27. package/dist/judgment-problem-snapshot.d.ts +30 -2
  28. package/dist/judgment-problem-snapshot.js +302 -6
  29. package/dist/judgment-value-proof-review.d.ts +166 -0
  30. package/dist/judgment-value-proof-review.js +437 -0
  31. package/dist/judgment-value-proof.d.ts +28 -1
  32. package/dist/judgment-value-proof.js +53 -8
  33. package/dist/knowledge-continuation.d.ts +127 -0
  34. package/dist/knowledge-continuation.js +446 -0
  35. package/dist/knowledge-lookup.d.ts +178 -0
  36. package/dist/knowledge-lookup.js +758 -0
  37. package/dist/local-web-fetch-bridge.d.ts +5 -0
  38. package/dist/local-web-fetch-bridge.js +37 -0
  39. package/dist/local-web-host.d.ts +109 -0
  40. package/dist/local-web-host.js +688 -0
  41. package/dist/local-web-security.d.ts +25 -0
  42. package/dist/local-web-security.js +95 -0
  43. package/dist/organization-graph.d.ts +15 -0
  44. package/dist/organization-graph.js +24 -4
  45. package/dist/personal-knowledge.d.ts +12 -0
  46. package/dist/personal-knowledge.js +29 -0
  47. package/dist/personal-memory-handover.d.ts +108 -0
  48. package/dist/personal-memory-handover.js +369 -0
  49. package/dist/philosophy-revision-reader.d.ts +85 -0
  50. package/dist/philosophy-revision-reader.js +241 -0
  51. package/dist/portable-graph.d.ts +24 -0
  52. package/dist/portable-graph.js +77 -0
  53. package/dist/server.d.ts +7 -2
  54. package/dist/server.js +9 -6
  55. package/dist/value-proof-review-http.d.ts +25 -0
  56. package/dist/value-proof-review-http.js +185 -0
  57. package/dist/world-model.d.ts +21 -3
  58. package/dist/world-model.js +94 -12
  59. package/package.json +94 -3
  60. package/ui/brainbase-tokens.css +56 -0
  61. package/ui/graph-own-share.js +367 -0
  62. package/ui/graph-projects-view.css +23 -0
  63. package/ui/graph-projects-view.js +730 -0
  64. package/ui/graph-registry-view.css +101 -0
  65. package/ui/graph-registry-view.js +804 -0
  66. package/ui/graph-view-shared.css +522 -0
  67. package/ui/graph-view-shared.js +1227 -0
  68. package/ui/judgment-view.css +119 -45
  69. package/ui/local-web-shell.css +396 -0
  70. package/ui/local-web-shell.js +378 -0
  71. package/ui/objective-editor-http-port.js +154 -0
  72. package/ui/objective-editor.css +180 -74
  73. package/ui/objective-editor.js +490 -43
  74. package/ui/outcome-knowledge.css +50 -14
  75. package/ui/outcome-knowledge.js +15 -1
  76. package/ui/outcome-mana.css +91 -14
  77. package/ui/outcome-mana.js +958 -61
  78. package/ui/value-proof-review.css +504 -0
  79. package/ui/value-proof-review.js +1104 -0
  80. package/ui/workspace-kit.css +485 -0
  81. package/ui/workspace-kit.js +229 -0
  82. package/ui/world-model-view.css +188 -0
  83. package/ui/world-model-view.js +486 -0
@@ -53,6 +53,35 @@
53
53
  { "type": "boolean" },
54
54
  { "const": "unconfirmed" }
55
55
  ]
56
+ },
57
+ "judgment_kind": {
58
+ "oneOf": [
59
+ { "type": "null" },
60
+ {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": ["key", "label"],
64
+ "properties": {
65
+ "key": { "type": "string", "pattern": "^[a-z0-9_]{1,64}$" },
66
+ "label": { "type": "string", "minLength": 1 }
67
+ }
68
+ }
69
+ ]
70
+ },
71
+ "inheritance": {
72
+ "oneOf": [
73
+ { "type": "null" },
74
+ {
75
+ "type": "object",
76
+ "additionalProperties": false,
77
+ "required": ["sources", "same_conditions", "rechecked_conditions"],
78
+ "properties": {
79
+ "sources": { "type": "array", "items": { "$ref": "#/$defs/inheritance_source" } },
80
+ "same_conditions": { "type": "array", "items": { "type": "string", "minLength": 1 } },
81
+ "rechecked_conditions": { "type": "array", "items": { "type": "string", "minLength": 1 } }
82
+ }
83
+ }
84
+ ]
56
85
  }
57
86
  }
58
87
  },
@@ -123,7 +152,20 @@
123
152
  "required": ["entity_id", "application"],
124
153
  "properties": {
125
154
  "entity_id": { "type": "string", "minLength": 1 },
126
- "application": { "type": "string", "minLength": 1 }
155
+ "application": { "type": "string", "minLength": 1 },
156
+ "layer": { "enum": ["philosophy", "objective", "world_model", "method", "constraint", "other"] },
157
+ "version": { "type": ["string", "null"] }
158
+ }
159
+ },
160
+ "inheritance_source": {
161
+ "type": "object",
162
+ "additionalProperties": false,
163
+ "required": ["kind", "ref", "version", "label"],
164
+ "properties": {
165
+ "kind": { "enum": ["judgment", "method", "decision", "other"] },
166
+ "ref": { "type": "string", "minLength": 1 },
167
+ "version": { "type": ["string", "null"] },
168
+ "label": { "type": "string", "minLength": 1 }
127
169
  }
128
170
  },
129
171
  "artifact_ref": {
@@ -55,13 +55,16 @@ function failValidation(fieldErrors) {
55
55
  throw new CanonicalTaskError('validation_failed', 'Canonical Task request is invalid', 422, { fieldErrors }, { fieldErrors });
56
56
  }
57
57
  function normalizeString(value, field, options = {}) {
58
- const normalized = value == null ? '' : String(value).normalize('NFKC').trim();
58
+ const text = value == null ? '' : String(value).normalize('NFKC');
59
+ // Multi-line fields keep line breaks and tabs; CRLF/CR are stored as LF.
60
+ const normalized = (options.multiline ? text.replace(/\r\n?/gu, '\n') : text).trim();
59
61
  if (!normalized) {
60
62
  if (options.required)
61
63
  fail('validation_error', `${field} is required`, 400, { field });
62
64
  return null;
63
65
  }
64
- if (/[\u0000-\u001f\u007f]/u.test(normalized)) {
66
+ const controlCharacters = options.multiline ? /[\u0000-\u0008\u000b-\u001f\u007f]/u : /[\u0000-\u001f\u007f]/u;
67
+ if (controlCharacters.test(normalized)) {
65
68
  fail('validation_error', `${field} contains control characters`, 400, { field });
66
69
  }
67
70
  if (options.max && normalized.length > options.max) {
@@ -163,6 +166,14 @@ function projectTaskResult(result) {
163
166
  }
164
167
  return result;
165
168
  }
169
+ // Repositories report warnings as `{ code, message }` objects or legacy strings.
170
+ // The page contract is string[], so keep the code rather than "[object Object]".
171
+ function warningCode(warning) {
172
+ if (typeof warning === 'string')
173
+ return warning;
174
+ const code = warning?.code;
175
+ return typeof code === 'string' && code ? code : JSON.stringify(warning);
176
+ }
166
177
  function normalizePage(page, normalize) {
167
178
  const items = Array.isArray(page.items) ? page.items.map(normalize) : [];
168
179
  // An explicit null means that the backend did not request or cannot provide
@@ -185,7 +196,7 @@ function normalizePage(page, normalize) {
185
196
  count_status: countStatus,
186
197
  next_cursor: nextCursor,
187
198
  read_status: readStatus,
188
- warnings: items.flatMap((task) => Array.isArray(task.normalization_warnings) ? task.normalization_warnings.map(String) : []),
199
+ warnings: items.flatMap((task) => Array.isArray(task.normalization_warnings) ? task.normalization_warnings.map(warningCode) : []),
189
200
  };
190
201
  }
191
202
  export class CanonicalTaskService {
@@ -598,7 +609,7 @@ export class CanonicalTaskService {
598
609
  fail('validation_error', 'priority is invalid', 400, { field: 'priority' });
599
610
  return {
600
611
  title,
601
- description: normalizeString(input.description, 'description', { max: 10000 }),
612
+ description: normalizeString(input.description, 'description', { max: 10000, multiline: true }),
602
613
  priority,
603
614
  assignee_person_id: normalizeString(input.assignee_person_id, 'assignee_person_id', { max: 200 }),
604
615
  due_at: normalizeIsoDate(input.due_at, 'due_at'),
@@ -618,7 +629,7 @@ export class CanonicalTaskService {
618
629
  if ('title' in input)
619
630
  patch.title = normalizeString(input.title, 'title', { required: true, max: MAX_TITLE_LENGTH });
620
631
  if ('description' in input)
621
- patch.description = normalizeString(input.description, 'description', { max: 10000 });
632
+ patch.description = normalizeString(input.description, 'description', { max: 10000, multiline: true });
622
633
  if ('priority' in input) {
623
634
  const priority = input.priority == null || input.priority === '' ? null : String(input.priority);
624
635
  if (priority != null && !isCanonicalTaskPriority(priority))
package/dist/cli.d.ts CHANGED
@@ -7,6 +7,8 @@ interface CliIo {
7
7
  stderr?: {
8
8
  write(chunk: string): unknown;
9
9
  };
10
+ /** Stops a long-running command such as web:serve (besides SIGINT/SIGTERM). */
11
+ signal?: AbortSignal;
10
12
  }
11
13
  export declare function runCli(argv?: string[], io?: CliIo): Promise<number>;
12
14
  export declare function isCliEntrypoint(moduleUrl: string, invokedPath: string | undefined): boolean;
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createPortableGraph, portableGraphDigest } from './portable-graph.js';
3
3
  import { createOrganizationGraphConfig, createOrganizationGraphClient } from './organization-graph.js';
4
+ import { listLocalMemories, memoryListJson, registerSelectedMemories, registrationPreviewJson, renderMemoryList, renderRegistrationPreview, renderRegistrationResult, selectLocalMemories } from './personal-memory-handover.js';
4
5
  import { constants, realpathSync } from 'node:fs';
5
6
  import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
6
7
  import { delimiter, dirname, isAbsolute, join } from 'node:path';
@@ -19,6 +20,8 @@ import { buildProjectRegistrationPlan, parseProjectSource, parseProjectStakehold
19
20
  import { renderGuidedFirstRun } from './guided-onboarding.js';
20
21
  import { blockedJudgmentOutput, processJudgmentHook } from './judgment-host.js';
21
22
  import { applyCanonicalWrites, buildCanonicalEdge } from './canonical-edge-builder.js';
23
+ import { defaultJudgmentJournalRoot } from './judgment-value-proof-review.js';
24
+ import { createLocalWebHost, LOCAL_WEB_DEFAULT_PORT } from './local-web-host.js';
22
25
  export async function runCli(argv = process.argv.slice(2), io = process) {
23
26
  const parsed = parseArgs(argv);
24
27
  try {
@@ -41,6 +44,27 @@ export async function runCli(argv = process.argv.slice(2), io = process) {
41
44
  sourceFilesUnchanged: true, graphId: config.graphId }) + '\n');
42
45
  return 0;
43
46
  }
47
+ case 'graph:bundles': {
48
+ const config = createOrganizationGraphConfig();
49
+ if (!config)
50
+ throw new Error('graph:bundles requires all BRAINBASE_ORGANIZATION_* settings');
51
+ const format = first(parsed, 'format') ?? 'text';
52
+ if (format !== 'text' && format !== 'json')
53
+ throw new Error('graph:bundles --format must be text or json');
54
+ const list = await createOrganizationGraphClient(config).listPortableGraphs();
55
+ if (format === 'json')
56
+ write(io, `${JSON.stringify(list, null, 2)}\n`);
57
+ else if (list.bundles.length === 0)
58
+ write(io, 'No Graph bundles have been uploaded to this organization yet.\n');
59
+ else
60
+ write(io, list.bundles.map((entry) => `${entry.createdAt} ${entry.projectCode} ${entry.graphId} ${entry.digest}\n`).join('')
61
+ + (list.truncated ? `Only the newest ${list.bundles.length} bundle(s) are shown.\n` : ''));
62
+ return 0;
63
+ }
64
+ case 'memory:list':
65
+ return await memoryList(parsed, io);
66
+ case 'memory:register':
67
+ return await memoryRegister(parsed, io);
44
68
  case 'onboard:init':
45
69
  return await onboardInit(parsed, io);
46
70
  case 'onboard:seed':
@@ -86,6 +110,10 @@ export async function runCli(argv = process.argv.slice(2), io = process) {
86
110
  return await judgmentInstall(parsed, io);
87
111
  case 'doctor':
88
112
  return await doctor(parsed, io);
113
+ case 'web:serve':
114
+ case 'review:serve':
115
+ // review:serve is kept as an alias of the same local Web host.
116
+ return await webServe(parsed, io, parsed.command);
89
117
  case 'mcp':
90
118
  await import('./index.js');
91
119
  return 0;
@@ -175,6 +203,36 @@ async function readHookStdin(input) {
175
203
  text += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
176
204
  return text;
177
205
  }
206
+ function textOrJsonFormat(parsed, command) {
207
+ const format = first(parsed, 'format') ?? 'text';
208
+ if (format !== 'text' && format !== 'json')
209
+ throw new Error(`${command} --format must be text or json`);
210
+ return format;
211
+ }
212
+ async function memoryList(parsed, io) {
213
+ const format = textOrJsonFormat(parsed, 'memory:list');
214
+ const memories = await listLocalMemories(first(parsed, 'dir'));
215
+ write(io, format === 'json' ? `${JSON.stringify(memoryListJson(memories), null, 2)}\n` : renderMemoryList(memories));
216
+ return 0;
217
+ }
218
+ async function memoryRegister(parsed, io) {
219
+ const format = textOrJsonFormat(parsed, 'memory:register');
220
+ const ids = parsed.values.get('id') ?? [];
221
+ if (ids.length === 0)
222
+ throw new Error('memory:register requires at least one --id <id> (see brainbase memory:list)');
223
+ const dataDir = first(parsed, 'dir');
224
+ if (!parsed.flags.has('write')) {
225
+ const selected = selectLocalMemories(await listLocalMemories(dataDir), ids);
226
+ write(io, format === 'json' ? `${JSON.stringify(registrationPreviewJson(selected), null, 2)}\n` : renderRegistrationPreview(selected));
227
+ return 0;
228
+ }
229
+ const config = createOrganizationGraphConfig();
230
+ if (!config)
231
+ throw new Error('memory:register --write requires all BRAINBASE_ORGANIZATION_* settings; nothing was sent');
232
+ const result = await registerSelectedMemories({ dataDir, ids, organization: { url: config.url, token: config.token } });
233
+ write(io, format === 'json' ? `${JSON.stringify({ status: 'registered', ...result }, null, 2)}\n` : renderRegistrationResult(result));
234
+ return 0;
235
+ }
178
236
  async function onboardInit(parsed, io) {
179
237
  const dataDir = resolveDataDir(first(parsed, 'dir'));
180
238
  await initializePersonalOs(dataDir);
@@ -976,6 +1034,50 @@ async function doctor(parsed, io) {
976
1034
  function graphDiagnosisExitCode(status) {
977
1035
  return status === 'invalid' || status === 'unavailable' || status === 'migration_required' ? 1 : 0;
978
1036
  }
1037
+ async function webServe(parsed, io, command) {
1038
+ const dataDir = resolveDataDir(first(parsed, 'dir'));
1039
+ const journalRoot = first(parsed, 'journal')
1040
+ ?? process.env.BRAINBASE_JUDGMENT_JOURNAL_DIR
1041
+ ?? defaultJudgmentJournalRoot(dataDir);
1042
+ const port = Number(first(parsed, 'port') ?? String(LOCAL_WEB_DEFAULT_PORT));
1043
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
1044
+ throw new Error(`${command} requires --port to be an integer between 0 and 65535`);
1045
+ }
1046
+ const { server } = createLocalWebHost({ dataDir, journalRoot });
1047
+ await new Promise((resolveListen, rejectListen) => {
1048
+ server.once('error', rejectListen);
1049
+ server.listen(port, '127.0.0.1', () => resolveListen());
1050
+ });
1051
+ const address = server.address();
1052
+ const actualPort = typeof address === 'object' && address ? address.port : port;
1053
+ const origin = `http://127.0.0.1:${actualPort}`;
1054
+ write(io, [
1055
+ `Brainbase: ${origin}/`,
1056
+ `- 今日(判断の見返し): ${origin}/#today`,
1057
+ `- 目的と現状: ${origin}/#objectives`,
1058
+ `- プロジェクトと関係者: ${origin}/#projects`,
1059
+ `- 情報と関係: ${origin}/#graph`,
1060
+ `データ: ${dataDir}`,
1061
+ `判断journal: ${journalRoot}`,
1062
+ '終了: Ctrl+C',
1063
+ ''
1064
+ ].join('\n'));
1065
+ await new Promise((resolveClose) => {
1066
+ const stop = () => {
1067
+ process.off('SIGINT', stop);
1068
+ process.off('SIGTERM', stop);
1069
+ io.signal?.removeEventListener('abort', stop);
1070
+ server.close(() => resolveClose());
1071
+ };
1072
+ process.once('SIGINT', stop);
1073
+ process.once('SIGTERM', stop);
1074
+ if (io.signal?.aborted)
1075
+ stop();
1076
+ else
1077
+ io.signal?.addEventListener('abort', stop, { once: true });
1078
+ });
1079
+ return 0;
1080
+ }
979
1081
  function parseArgs(argv) {
980
1082
  const [firstToken, ...remaining] = argv;
981
1083
  const command = firstToken?.startsWith('--') ? undefined : firstToken;
@@ -1104,10 +1206,15 @@ function usage() {
1104
1206
  brainbase ontology:show
1105
1207
  brainbase ontology:audit [--dir path] [--ontology-version 0.0.0|1.0.0|2.0.0]
1106
1208
  brainbase graph:upgrade [--dir path]
1209
+ brainbase graph:bundles [--format text|json]
1210
+ brainbase memory:list [--dir path] [--format text|json]
1211
+ brainbase memory:register --id id [--id id...] [--dir path] [--write] [--format text|json]
1107
1212
  brainbase ontology:migrate [--dir path] [--write --expected-input-digest digest]
1108
1213
  brainbase judgment:install --target codex [--autonomy-mode off|canary|on] [--autonomy-project code] [--dry-run] [--output path]
1109
1214
  brainbase judgment:hook [--autonomy-mode off|canary|on] [--autonomy-project code]
1110
1215
  brainbase doctor [--dir path] [--judgment-hooks path]
1216
+ brainbase web:serve [--dir path] [--journal path] [--port n]
1217
+ brainbase review:serve [--dir path] [--journal path] [--port n] (web:serveの別名)
1111
1218
  `;
1112
1219
  }
1113
1220
  function shellArg(value) {
@@ -13,6 +13,11 @@ export interface ObjectiveReadiness {
13
13
  /** Definition and referenced Variable issues; observations are not inferred. */
14
14
  issues: readonly ObjectiveReadinessIssue[];
15
15
  }
16
+ /** The canonical read surface needed to check an Objective and its criteria. */
17
+ export interface ObjectiveReadinessReader {
18
+ read(reference: FoundationRevision, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
19
+ readLatest?(type: 'objective' | 'variable', id: string, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
20
+ }
16
21
  export type StoryObjectiveLinkKind = 'contribution' | 'execution_dependency' | 'time_condition';
17
22
  export interface StoryObjectiveLinkInput {
18
23
  storyId: string;
@@ -49,4 +54,10 @@ export declare class CompanyOsObjectives {
49
54
  linkObjectiveStory(input: StoryObjectiveLinkInput, context: FoundationStoreContext): Promise<void>;
50
55
  private readTyped;
51
56
  }
57
+ /**
58
+ * Check an Objective and every Variable revision named by its criteria.
59
+ * Both the Objective service and judgment snapshot providers call this
60
+ * function so readiness cannot vary by caller.
61
+ */
62
+ export declare function checkObjectiveReadiness(reader: ObjectiveReadinessReader, id: string, context: FoundationStoreContext, revision?: string): Promise<ObjectiveReadiness>;
52
63
  export declare function createCompanyOsObjectives(store: FoundationRevisionStore): CompanyOsObjectives;
@@ -27,53 +27,7 @@ export class CompanyOsObjectives {
27
27
  }, context);
28
28
  }
29
29
  async checkObjectiveReadiness(id, context, revision) {
30
- const record = await this.readObjective(id, context, revision);
31
- if (!record) {
32
- return {
33
- ready: false,
34
- issues: [{ code: 'NOT_FOUND', path: 'id', message: `Objective ${id} was not found.` }]
35
- };
36
- }
37
- const objective = record.definition;
38
- const issues = validationIssues(objective, 'judgment');
39
- const criteria = Array.isArray(objective.criteria) ? objective.criteria : [];
40
- for (const [index, criterion] of criteria.entries()) {
41
- if (!criterion || typeof criterion !== 'object' || !criterion.variableRef)
42
- continue;
43
- if (!isVariableRevisionReference(criterion.variableRef)) {
44
- issues.push({
45
- code: 'INVALID_VARIABLE_REFERENCE',
46
- path: `criteria[${index}].variableRef`,
47
- message: 'Criterion must reference a Variable with a positive revision.'
48
- });
49
- continue;
50
- }
51
- const variable = await this.store.read(criterion.variableRef, context);
52
- if (!variable) {
53
- issues.push({
54
- code: 'MISSING_VARIABLE_REVISION',
55
- path: `criteria[${index}].variableRef`,
56
- message: `Variable ${criterion.variableRef.id}@${criterion.variableRef.revision} was not found.`
57
- });
58
- continue;
59
- }
60
- if (variable.definition.type !== 'variable') {
61
- issues.push({
62
- code: 'INVALID_VARIABLE_REFERENCE',
63
- path: `criteria[${index}].variableRef`,
64
- message: `Criterion ${criterion.variableRef.id}@${criterion.variableRef.revision} is not a Variable.`
65
- });
66
- continue;
67
- }
68
- for (const issue of validationIssues(variable.definition, 'judgment')) {
69
- issues.push({
70
- ...issue,
71
- path: `criteria[${index}].variableRef.${issue.path}`
72
- });
73
- }
74
- issues.push(...criterionCompatibilityIssues(criterion, variable.definition, index));
75
- }
76
- return { ready: issues.length === 0, issues };
30
+ return checkObjectiveReadiness(this.store, id, context, revision);
77
31
  }
78
32
  createVariable(definition, context) {
79
33
  assertType(definition, 'variable');
@@ -130,6 +84,69 @@ export class CompanyOsObjectives {
130
84
  return this.store.readLatest(type, id, context);
131
85
  }
132
86
  }
87
+ /**
88
+ * Check an Objective and every Variable revision named by its criteria.
89
+ * Both the Objective service and judgment snapshot providers call this
90
+ * function so readiness cannot vary by caller.
91
+ */
92
+ export async function checkObjectiveReadiness(reader, id, context, revision) {
93
+ const record = revision === undefined
94
+ ? reader.readLatest === undefined ? null : await reader.readLatest('objective', id, context)
95
+ : await reader.read({ id, type: 'objective', revision }, context);
96
+ if (!record) {
97
+ return {
98
+ ready: false,
99
+ issues: [{ code: 'NOT_FOUND', path: 'id', message: `Objective ${id} was not found.` }]
100
+ };
101
+ }
102
+ if (record.definition.type !== 'objective') {
103
+ return {
104
+ ready: false,
105
+ issues: [{ code: 'INVALID_OBJECTIVE_REFERENCE', path: 'type', message: `Foundation ${id} is not an Objective.` }]
106
+ };
107
+ }
108
+ const objective = record.definition;
109
+ const issues = validationIssues(objective, 'judgment');
110
+ const criteria = Array.isArray(objective.criteria) ? objective.criteria : [];
111
+ for (const [index, criterion] of criteria.entries()) {
112
+ if (!criterion || typeof criterion !== 'object' || !criterion.variableRef)
113
+ continue;
114
+ if (!isVariableRevisionReference(criterion.variableRef)) {
115
+ issues.push({
116
+ code: 'INVALID_VARIABLE_REFERENCE',
117
+ path: `criteria[${index}].variableRef`,
118
+ message: 'Criterion must reference a Variable with a positive revision.'
119
+ });
120
+ continue;
121
+ }
122
+ const variable = await reader.read(criterion.variableRef, context);
123
+ if (!variable) {
124
+ issues.push({
125
+ code: 'MISSING_VARIABLE_REVISION',
126
+ path: `criteria[${index}].variableRef`,
127
+ message: `Variable ${criterion.variableRef.id}@${criterion.variableRef.revision} was not found.`
128
+ });
129
+ continue;
130
+ }
131
+ if (variable.definition.type !== 'variable') {
132
+ issues.push({
133
+ code: 'INVALID_VARIABLE_REFERENCE',
134
+ path: `criteria[${index}].variableRef`,
135
+ message: `Criterion ${criterion.variableRef.id}@${criterion.variableRef.revision} is not a Variable.`
136
+ });
137
+ continue;
138
+ }
139
+ for (const issue of validationIssues(variable.definition, 'judgment')) {
140
+ issues.push({
141
+ ...issue,
142
+ path: `criteria[${index}].variableRef.${issue.path}`
143
+ });
144
+ }
145
+ issues.push(...criterionCompatibilityIssues(criterion, variable.definition, index));
146
+ issues.push(...criterionPeriodIssues(objective, variable.definition, index));
147
+ }
148
+ return { ready: issues.length === 0, issues };
149
+ }
133
150
  export function createCompanyOsObjectives(store) {
134
151
  return new CompanyOsObjectives(store);
135
152
  }
@@ -195,3 +212,23 @@ function criterionCompatibilityIssues(criterion, variable, index) {
195
212
  }
196
213
  return [];
197
214
  }
215
+ function criterionPeriodIssues(objective, variable, index) {
216
+ const objectiveFrom = Date.parse(objective.evaluationPeriod?.from ?? '');
217
+ const objectiveUntil = Date.parse(objective.evaluationPeriod?.until ?? '');
218
+ const variableFrom = Date.parse(variable.scope?.validFrom ?? '');
219
+ const variableUntil = variable.scope?.validUntil === undefined
220
+ ? Number.POSITIVE_INFINITY
221
+ : Date.parse(variable.scope.validUntil);
222
+ if (!Number.isFinite(objectiveFrom) || !Number.isFinite(objectiveUntil)
223
+ || !Number.isFinite(variableFrom)
224
+ || (variable.scope?.validUntil !== undefined && !Number.isFinite(variableUntil))) {
225
+ return [];
226
+ }
227
+ if (variableFrom <= objectiveFrom && variableUntil >= objectiveUntil)
228
+ return [];
229
+ return [{
230
+ code: 'CRITERION_PERIOD_MISMATCH',
231
+ path: `criteria[${index}].variableRef`,
232
+ message: `Variable ${variable.id}@${variable.revision} does not cover the Objective evaluation period.`
233
+ }];
234
+ }
@@ -0,0 +1,56 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export type FoundationAuthenticatedProjectContext = {
3
+ token: string;
4
+ scope: string[];
5
+ };
6
+ export type FoundationAuthenticatedToolResult = {
7
+ status: 'ok' | 'error' | 'unavailable';
8
+ scope: {
9
+ project_codes: string[];
10
+ };
11
+ data?: unknown;
12
+ error?: {
13
+ code: string;
14
+ message: string;
15
+ http_status?: number;
16
+ details?: unknown;
17
+ };
18
+ };
19
+ export type FoundationAuthenticatedRequest = {
20
+ path: string;
21
+ method: string;
22
+ body?: unknown;
23
+ headers?: Record<string, string>;
24
+ };
25
+ export type FoundationAuthenticatedJsonResponse = {
26
+ ok: true;
27
+ response: Response;
28
+ payload: unknown;
29
+ payloadParsed: boolean;
30
+ } | {
31
+ ok: false;
32
+ result: FoundationAuthenticatedToolResult;
33
+ };
34
+ /**
35
+ * Narrow host port for authentication and authenticated Graph requests.
36
+ * The OSS adapter owns request validation, CSRF/session binding, and response
37
+ * validation; a connected Host supplies its canonical auth implementation.
38
+ */
39
+ export type FoundationAuthenticatedToolPort = {
40
+ authenticateProject(args: Record<string, unknown>, options?: {
41
+ requireProject?: boolean;
42
+ }): Promise<FoundationAuthenticatedProjectContext | FoundationAuthenticatedToolResult>;
43
+ fetchAuthenticatedJson(context: FoundationAuthenticatedProjectContext, request: FoundationAuthenticatedRequest): Promise<FoundationAuthenticatedJsonResponse>;
44
+ toolError(status: 'error' | 'unavailable', code: string, message: string, scope: string[], httpStatus?: number, details?: unknown): FoundationAuthenticatedToolResult;
45
+ };
46
+ export type FoundationAuthenticatedToolDependencies = {
47
+ apiUrl: string;
48
+ fetch?: typeof globalThis.fetch;
49
+ auth: FoundationAuthenticatedToolPort;
50
+ };
51
+ type FoundationPublicToolName = 'foundation_describe' | 'foundation_read' | 'foundation_validate_reference' | 'foundation_validate_problem';
52
+ export type FoundationPublicToolDependencies = FoundationAuthenticatedToolDependencies;
53
+ export declare const foundationPublicTools: Tool[];
54
+ export declare function isFoundationPublicToolName(name: string): name is FoundationPublicToolName;
55
+ export declare function handleFoundationPublicToolCall(name: string, args: Record<string, unknown>, dependencies: FoundationPublicToolDependencies): Promise<FoundationAuthenticatedToolResult | null>;
56
+ export {};